简体   繁体   中英

How do I change the default logging in Java Transformer

I implemented the code to enable me to print formatted XML

import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

public class TransformThis implements ErrorListener {

    public static void main(String[] args) throws java.lang.Exception {

        TransformThis test = new TransformThis();
        String goodXML;
        String badXML;

       goodXML = "<root><level1>WellFormed</level1></root>";
       System.out.println(test.prettyPrint(goodXML));
       badXML = "<root><level1>Not Well Formed</level1>";
       System.out.println(test.prettyPrint(badXML));
   }

   public String prettyPrint(String xml) {

       Source xmlInput = new StreamSource(new StringReader(xml));
       StringWriter stringWriter = new StringWriter();
       StreamResult xmlOutput = new StreamResult(stringWriter);
       TransformerFactory transformerFactory = TransformerFactory.newInstance();
       transformerFactory.setAttribute("indent-number", 4);

       try {
           Transformer transformer = transformerFactory.newTransformer();
           transformer.setErrorListener(this);
           transformer.setOutputProperty(OutputKeys.INDENT, "yes");
           transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
           transformer.transform(xmlInput, xmlOutput);
       } catch (Exception ex) {
           System.out.println("My message: " + ex.getMessage());
       }

       return xmlOutput.getWriter().toString();
   }


   @Override
   public void warning(TransformerException exception) throws TransformerException {
       //throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public void error(TransformerException exception) throws TransformerException {
      //throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public void fatalError(TransformerException exception) throws TransformerException {
       //throw new UnsupportedOperationException("Not supported yet.");
   }
}

When the XML is well formed, I get the following output - exactly what I want

<root>
    <level1>WellFormed</level1>
</root>

If there is a problem with the XML I get the following output - fine, except for the [Fatal Error] output

[Fatal Error] :1:39: XML document structures must start and end within the same entity.
My message: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 39; XML document structures must start and end within the same entity.
<root>
   <level1>Not Well Formed</level1>

The transform function throws an exception as well as sends a [Fatal Error] to stderr/stdout. Is there a way to prevent the [Fatal Error] log from appearing?

You have found a bug (two, actually) in the JDK. Congratulations! (Or, condolences, I suppose).

First bug is that it's a "fatal" error, but it will call ErrorListener.error() instead of ErrorListener.fatalError() . If you put a println statement in error() in your example, you'll see it's being called.

The second bug is that ignoring the first one above, what you are doing should work.

But it doesn't.

Throwing your example in the debugger and delving down into the JDK I found that the error listener isn't propagated down into the underlying XMLScanner and XMLErrorReporter .

What is happening is that the XMLErrorReporter is instantiating a com.sun.org.apache.xerces.internal.util.DefaultErrorHandler and calling its fatalError() method which is what is spitting that [fatal error] message out to stderr .

Specifically, this happens at line 422 of com.sun.org.apache.xerces.internal.impl.XMLErrorReporter

After that it rethrows the exception up the stack and the TransformerImpl fires it to your listener.

What should be happening is that those underlying classes should either have a proxy to the higher level listener you passed in, or a local no-op listener should be created to silence the output in the lower levels. I suspect it's the latter since otherwise you'd get notified twice.

I'll need to look at the abstraction tree more closely and debug the construction chain to find out why that's not happening but in short, unfortunately this is a bug in the JDK and there isn't a way to control/prevent it. (This is testing on Java 1.7.0_25-b15).

Following on from Brian Roach's answer... there is an alternative approach which will solve the problem. If you work with a StreamSource as input to the transformer, you are stuck with whatever parser the system decides to give you, and the default error reporting mechanism the parser chooses. You can however use a SAX or DOM Source, which allows you to configure the parser yourself. I've updated your example to use a SAXSource instance.

public class TransformThis implements ErrorListener, ErrorHandler {

    public static void main(String[] args) throws java.lang.Exception {

        TransformThis test = new TransformThis();
        String goodXML;
        String badXML;

       goodXML = "<root><level1>WellFormed</level1></root>";
       System.out.println(test.prettyPrint(goodXML));
       badXML = "<root><level1>Not Well Formed</level1>";
       System.out.println(test.prettyPrint(badXML));
   }

   public String prettyPrint(String xml) throws ParserConfigurationException, SAXException {

       SAXParserFactory parserFactory = SAXParserFactory.newInstance();
       SAXParser parser = parserFactory.newSAXParser();
       parser.getXMLReader().setErrorHandler(this);       
       SAXSource xmlInput = new SAXSource(parser.getXMLReader(), new InputSource(new StringReader(xml)));

       StringWriter stringWriter = new StringWriter();
       StreamResult xmlOutput = new StreamResult(stringWriter);
       TransformerFactory transformerFactory = TransformerFactory.newInstance();
       transformerFactory.setAttribute("indent-number", 4);

       try {
           Transformer transformer = transformerFactory.newTransformer();
           transformer.setErrorListener(this);
           transformer.setOutputProperty(OutputKeys.INDENT, "yes");
           transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
           transformer.transform(xmlInput, xmlOutput);
       } catch (Exception ex) {
           System.out.println("My message: " + ex.getMessage());
       }

       return xmlOutput.getWriter().toString();
   }


   @Override
   public void warning(TransformerException exception) throws TransformerException {
       //throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public void error(TransformerException exception) throws TransformerException {
      //throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public void fatalError(TransformerException exception) throws TransformerException {
       //throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public void warning(SAXParseException exception) throws SAXException {
       // Do nothing
   }

   @Override
   public void error(SAXParseException exception) throws SAXException {
       // Do nothing
   }

   @Override
   public void fatalError(SAXParseException exception) throws SAXException {
       // Rethrow the exception
       throw exception;
   }

}

Straight from the documentation:

To provide customized error handling, implement ErrorListener interface and use the setErrorListener method to register an instance of the implmentation with the Transformer. The Transformer then reports all errors and warnings through this interface.

If an application does not register its own custom ErrorListener, the default ErrorListener is used which reports all warnings and errors to System.err and does not throw any Exceptions. Applications are strongly encouraged to register and use ErrorListeners that insure proper behavior for warnings and errors.

For transformation errors, a Transformer must use this interface instead of throwing an Exception: it is up to the application to decide whether to throw an Exception for different types of errors and warnings. Note however that the Transformer is not required to continue with the transformation after a call to fatalError(TransformerException exception).

Now in your case , since your TransformThis class is implementing ErrorListener. you should be able to transform Errors with the following :

transformer.setErrorListener(this);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM