In this section of the tutorial, you'll modify the slideshow file to generate different kinds of errors and see how the parser handles them. You'll also find out which error conditions are ignored, by default, and see how to handle them.
Note: The XML structure you'll create in this exercise is in slideSampleBad1.xml. The output is in Echo05-Bad1.log.One easy way to introduce a fatal error is to remove the final "/" from the empty item element to create a tag that does not have a corresponding end tag. That constitutes a fatal error, because all XML documents must, by definition, be well formed. Do the following:
... <!-- OVERVIEW --> <slide type="all"> <title>Overview</title> <item>Why <em>WonderWidgets</em> are great</item> <itemto produce:/> <item>Who <em>buys</em> WonderWidgets</item> </slide> ...
... <item>Why <em>WonderWidgets</em> are great</item> <item> <item>Who <em>buys</em> WonderWidgets</item> ...
When a fatal error occurs, the parser is unable to continue. So, if the application does not generate an exception (which you'll see how to do a moment), then the default error-event handler generates one. The stack trace is generated by the Throwable exception handler in your main method:... ELEMENT: <item> CHARS: The ELEMENT: <em> CHARS: Only END_ELM: </em> CHARS: Section END_ELM: </item> CHARS: END_ELM: CHARS: org.xml.sax.SAXParseException: Expected "</item>" to terminate element starting on line 20. at com.sun.xml.parser.Parser.fatal(Parser.java:2800) at com.sun.xml.parser.Parser.fatal(Parser.java:2794) at com.sun.xml.parser.Parser.maybeElement(Parser.java:1406) at com.sun.xml.parser.Parser.content(Parser.java:1499) at com.sun.xml.parser.Parser.maybeElement(Parser.java:1400) at com.sun.xml.parser.Parser.content(Parser.java:1499) at com.sun.xml.parser.Parser.maybeElement(Parser.java:1400) at com.sun.xml.parser.Parser.parseInternal(Parser.java:492) at com.sun.xml.parser.Parser.parse(Parser.java:284) at javax.xml.parsers.SAXParser.parse(SAXParser.java:168) at javax.xml.parsers.SAXParser.parse(SAXParser.java:104) at javax.xml.parsers.SAXParser.parse(SAXParser.java:131) at Echo05.main(Echo05.java:59)
That stack trace is not too useful, though. Next, you'll see how to generate better diagnostics when an error occurs.... } catch (Throwable t) { t.printStackTrace (); }
Note: The code you'll create in this exercise is in Echo06.java. The output is in Echo06-Bad1.log.Add the code highlighted below to generate a better diagnostic message when the exception occurs:
Running the program now generates an error message which is a bit more helpful, like this:... } catch (SAXParseException spe) { // Error generated by the parser System.out.println ("\n** Parsing error" + ", line " + spe.getLineNumber () + ", uri " + spe.getSystemId ()); System.out.println(" " + spe.getMessage() ); } catch (Throwable t) { t.printStackTrace (); }
** Parsing error, line 22, uri file:<path>/slideSampleBad1.xml Next character must be...Note:
Catching all throwables like this is not a good idea for production applications. We're just doing it now so we can build up to full error handling gradually.
All of the DocumentHandler methods (except for setDocumentLocator) have that signature declaration.public void startDocument () throws SAXException
A SAXException can be constructed using a message, another exception, or both. So, for example, when Echo.startDocument outputs a string using the emit method, any I/O exception that occurs is wrapped in a SAXException and sent back to the parser:
When the parser delivers the exception back to the code that invoked the parser, it makes sense to use the original exception to generate the stack trace. Add the code highlighted below to do that:private void emit (String s) throws SAXException { try { out.write (s); out.flush (); } catch (IOException e) { throw new SAXException ("I/O error", e); } }Note: If you saved the Locator object when setDocumentLocator was invoked, you could use it to generate a SAXParseException, identifying the document and location, instead of generating a SAXException.
This code tests to see if the SAXException is wrapping another exception. If so, it generates a stack trace originating from where that exception occurred to make it easier to pinpoint the code responsible for the error. If the exception contains only a message, the code prints the stack trace starting from the location where the exception was generated.... } catch (SAXParseException err) { System.out.println ("** Parsing error" + ", line " + err.getLineNumber () + ", uri " + err.getSystemId ()); System.out.println(" " + err.getMessage ()); } catch (SAXException sxe) { // Error generated by this application // (or a parser-initialization error) Exception x = sxe; if (sxe.getException() != null) x = sxe.getException(); x.printStackTrace(); } catch (Throwable t) { t.printStackTrace (); }
The program is now ready to handle any SAX parsing exceptions it sees. You've seen that the parser generates exceptions for fatal errors. But for nonfatal errors and warnings, exceptions are never generated by the default error handler, and no messages are displayed. Next, you'll learn more about errors and warnings and find out how to supply an error handler to process them.... } catch (SAXParseException err) { System.out.println ("** Parsing error" + ", line " + err.getLineNumber () + ", uri " + err.getSystemId ()); System.out.println(" " + err.getMessage ()); // Unpack the delivered exception to get the exception it contains Exception x = spe; if (spe.getException() != null) x = spe.getException(); x.printStackTrace(); } catch (SAXException e) { // Error generated by this application // (or a parser-initialization error) Exception x = e; if (e.getException () != null) x = e.getException (); x.printStackTrace (); } catch (Throwable t) { t.printStackTrace (); }
Add the code highlighted below to handle such errors:
This code, like the SAXException handler, takes into account the possibility that the reported exception might be wrapping another exception. (Admittedly, there are quite a few error handlers here. But at least now you know the kinds of exceptions that can occur.)} catch (SAXException e) { Exception x = e; if (e.getException () != null) x = e.getException (); x.printStackTrace ();} catch (ParserConfigurationException pce) { // Parser with specified options can't be built pce.printStackTrace(); } catch (Throwable t) { t.printStackTrace ();
Note:
A javax.xml.parsers.FactoryConfigurationError could also be thrown if the factory class specified by the system property cannot be found or instantiated. That is a non-trappable error, since the program is not expected to be able to recover from it.
} catch (ParserConfigurationException pce) { // Parser with specified options can't be built pce.printStackTrace();} catch (Throwable t) {t.printStackTrace ();} catch (IOException ioe) { // I/O error ioe.printStackTrace(); }
Note: The file you'll create in this exercise is slideSampleBad2.xml. The output is in Echo06-Bad2.log.The SAX specification requires an error event to be generated if the XML document uses a version of XML that the parser does not support. To generate such an error, make the changes shown below to alter your XML file so it specifies version="1.2".
Now run your version of the Echo program on that file. What happens? (See below for the answer.)<?xml version='1.02' encoding='us-ascii'?>
Answer: Nothing happens! By default, the error is ignored. The output from the Echo program looks the same as if version="1.0" had been properly specified. To do something else, you need to supply your own error handler. You'll do that next.
Note: The code for the program you'll create in this exercise is in Echo07.java. The output is in Echo07-Bad2.log.To take over error handling, you override the HandlerBase methods that handle fatal errors, nonfatal errors, and warnings as part of the ErrorHandler interface. The SAX parser delivers a SAXParseException to each of these methods, so generating an exception when an error occurs is as simple as throwing it back.
Add the code highlighted below to override the handlers for errors:
Now when you run your app on the file with the faulty version number, you get an exception, as shown here (but slightly reformatted for readability):public void processingInstruction (String target, String data) throws SAXException { nl(); emit ("PROCESS: "); emit ("<?"+target+" "+data+"?>"); }// treat validation errors as fatal public void error (SAXParseException e) throws SAXParseException { throw e; }
START DOCUMENT <?xml version='1.0' encoding='UTF-8'?> ** Parsing error, line 1, uri file:/<path>/slideSampleBad2.xml XML version "1.0" is recognized, but not "1.2". org.xml.sax.SAXParseException: XML version "1.0" is recognized, but not "1.2". at com.sun.xml.parser.Parser.error(Parser.java:2778) at com.sun.xml.parser.Parser.readVersion(Parser.java:1052) at com.sun.xml.parser.Parser.maybeXmlDecl(Parser.java:984) at com.sun.xml.parser.Parser.parseInternal(Parser.java:478) at com.sun.xml.parser.Parser.parse(Parser.java:284) at javax.xml.parsers.SAXParser.parse(SAXParser.java:168) at javax.xml.parsers.SAXParser.parse(SAXParser.java:104) at javax.xml.parsers.SAXParser.parse(SAXParser.java:131) at Echo07.main(Echo07.java:59)Note: The error actually occurs after the startDocument event has been generated. The document header that the program "echoes" is the one it creates on the assumption that everything is ok, rather than the one that is actually in the file.
Add the code highlighted below to generate a message when a warning occurs:
Since there is no good way to generate a warning without a DTD, you won't be seeing any just yet. But when one does occur, you're ready!// treat validation errors as fatal public void error (SAXParseException e) throws SAXParseException { throw e; } // dump warnings too public void warning (SAXParseException err) throws SAXParseException { System.out.println ("** Warning" + ", line " + err.getLineNumber () + ", uri " + err.getSystemId ()); System.out.println(" " + err.getMessage ()); }
Note: By default, HandlerBase throws an exception when a fatal error occurs. You could override the fatalError method to throw a different exception, if you like. But if your code doesn't, the reference implementation's SAX parser will.