How to change default registration in Java Transformer

I have embedded code to allow 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 XML, I get the following output - excellent, except for the output [Fatal Error]

 [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 conversion function throws an exception and also sends [Fatal Error] to stderr / stdout. Is there a way to prevent the [Fatal Error] log from appearing?

+6
source share
3 answers

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

 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; } } 
+4
source

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

The first error is that it is a "fatal" error, but it will raise ErrorListener.error() instead of ErrorListener.fatalError() . If you put the println operator in error() in your example, you will see that it is being called.

The second mistake is that ignoring the first one above what you do should work.

But this is not so.

Throwing your example into the debugger and delving into the JDK, I found that the error listener does not propagate down to the underlying XMLScanner and XMLErrorReporter .

What happens is that XMLErrorReporter creates an instance of com.sun.org.apache.xerces.internal.util.DefaultErrorHandler and calls its method fatalError() , which is what splashes the [fatal error] message to stderr .

In particular, this happens on line 422 com.sun.org.apache.xerces.internal.impl.XMLErrorReporter

After that, it jumps the exception onto the stack, and TransformerImpl fires it for your listener.

What should happen is that these base classes must either have a proxy server for the higher level listener you were in, or create a local no-op listener to disable output at lower levels. I suspect this is the last one, because otherwise you will receive a notification twice.

I need to take a closer look at the abstraction tree and debug the build chain to find out why this is not happening, but unfortunately this is a bug in the JDK and there is no way to control / prevent this. (This is testing in Java 1.7.0_25-b15).

+6
source

Directly from the documentation:

To provide individual error handling, use the ErrorListener interface and use the setErrorListener method to register the implantation instance with Transformer. Transformer then reports all errors and warnings through this interface.

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

For conversion errors, Transformer should use this interface instead of throwing an exception: the application needs to decide whether to exclude exceptions for various types of errors and warnings. Note, however, that Transformer is not required to continue the conversion after calling fatalError (TransformerException exception).

Now in your case, since your TransformThis class implements ErrorListener. You should be able to convert errors with the following parameters:

 transformer.setErrorListener(this); 
-3
source

All Articles