Handling Errors with the Nonvalidating Parser
The parser can generate three kinds of errors: a fatal error, an error, and a warning. In this exercise, you'll see how the parser handles a fatal error.
This version of the Echo program uses the nonvalidating parser. So it can't tell whether the XML document contains the right tags or whether those tags are in the right sequence. In other words, it can't tell you whether the document is valid. It can, however, tell whether or not the document is well formed.
In this section, you'll modify the slide-show file to generate various kinds of errors and see how the parser handles them. You'll also find out which error conditions are ignored by default, and you'll see how to handle them.
Note: The XML file used in this exercise is
slideSampleBad1.xml
, as described in Introducing an Error . The output is inEcho05-Bad1.txt
. (The browsable versions areslideSampleBad1-xml.html
andEcho05-Bad1.html
.)
When you created
slideSampleBad1.xml
, you deliberately created an XML file that was not well formed. Run the Echo program on that file now. The output now gives you an error message that looks like this (after formatting for readability):org.xml.sax.SAXParseException: The element type "item" must be terminated by the matching end-tag "</item>".
... at org.apache.xerces.parsers.AbstractSAXParser... ... at Echo.main(...)
Note: The foregoing message was generated by Xerces, the XML parser that is part of the JAXP 1.2 implementation libraries. If you are using a different parser, the error message is likely to be somewhat different.
When a fatal error occurs, the parser cannot 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 yourmain
method:That stack trace is not very useful. Next, you'll see how to generate better diagnostics when an error occurs.
Handling a SAXParseException
When the error was encountered, the parser generated a
SAXParseException
--a subclass ofSAXException
that identifies the file and location where the error occurred.
Note: The code you'll create in this exercise is in
Echo06.java
. The output is inEcho06-Bad1.txt
. (The browsable version isEcho06-Bad1.html
.)
Add the following highlighted code to generate a better diagnostic message when the exception occurs:
... } 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(); }Running this version of the program on
slideSampleBad1.xml
generates an error message that is a bit more helpful:
Note: The text of the error message depends on the parser used. This message was generated using JAXP 1.2.
Note: Catching all throwables is not generally a great idea for production applications. We're doing it now so that we can build up to full error handling gradually. In addition, it acts as a catch-all for null pointer exceptions that can be thrown when the parser is passed a null value.
Handling a SAXException
A more general
SAXException
instance may sometimes be generated by the parser, but it more frequently occurs when an error originates in one of application's event-handling methods. For example, the signature of thestartDocument
method in theContentHandler
interface is defined as returning aSAXException
:All the
ContentHandler
methods (except forsetDocumentLocator
) have that signature declaration.A
SAXException
can be constructed using a message, another exception, or both. So, for example, whenEcho.startDocument
outputs a string using theemit
method, any I/O exception that occurs is wrapped in aSAXException
and sent back to the parser: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 whensetDocumentLocator
was invoked, you could use it to generate aSAXParseException
, identifying the document and location, instead of generating aSAXException
.
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 following highlighted code to do that:
... } catch (SAXParseException err) { System.out.println("\n** 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(); }This code tests to see whether the
SAXException
is wrapping another exception. If it is, it generates a stack trace originating where the exception occurred to make it easier to pinpoint the responsible code. If the exception contains only a message, the code prints the stack trace starting from the location where the exception was generated.Improving the SAXParseException Handler
Because the
SAXParseException
can also wrap another exception, add the following highlighted code to use the contained exception for the stack trace:... } catch (SAXParseException err) { System.out.println("\n** Parsing error" + ", line " + err.getLineNumber() + ", uri " + err.getSystemId()); System.out.println(" " + err.getMessage());// Use the contained exception, if any Exception x = spe; if (spe.getException() != null) x = spe.getException(); x.printStackTrace();
} 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. In a moment, you'll learn more about errors and warnings and will find out how to supply an error handler to process them.
Handling a ParserConfigurationException
Recall that the
SAXParserFactory
class can throw an exception if it cannot create a parser. Such an error might occur if the factory cannot find the class needed to create the parser (class not found error), is not permitted to access it (illegal access exception), or cannot instantiate it (instantiation error).Add the following highlighted code to handle such errors:
} catch (SAXException sxe) { Exception x = sxe; if (sxe.getException() != null) x = sxe.getException(); x.printStackTrace();} catch (ParserConfigurationException pce) { // Parser with specified options can't be built pce.printStackTrace();
} catch (Throwable t) { t.printStackTrace();Admittedly, there are quite a few error handlers here. But at least now you know the kinds of exceptions that can occur.
Note: A
javax.xml.parsers.FactoryConfigurationError
can also be thrown if the factory class specified by the system property cannot be found or instantiated. That is a nontrappable error, because the program is not expected to be able to recover from it.
Handling an IOException
While we're at it, let's add a handler for
IOException
s:} catch (ParserConfigurationException pce) { // Parser with specified options can't be built pce.printStackTrace();} catch (IOException ioe) { // I/O error ioe.printStackTrace(); }
} catch (Throwable t) { ...We'll leave the handler for
Throwables
to catch null pointer errors, but note that at this point it is doing the same thing as theIOException
handler. Here, we're merely illustrating the kinds of exceptions that can occur, in case there are some that your application could recover from.Handling NonFatal Errors
A nonfatal error occurs when an XML document fails a validity constraint. If the parser finds that the document is not valid, then an error event is generated. Such errors are generated by a validating parser, given a DTD or schema, when a document has an invalid tag, when a tag is found where it is not allowed, or (in the case of a schema) when the element contains invalid data.
You won't deal with validation issues until later in this tutorial. But because we're on the subject of error handling, you'll write the error-handling code now.
The most important principle to understand about nonfatal errors is that they are ignored by default. But if a validation error occurs in a document, you probably don't want to continue processing it. You probably want to treat such errors as fatal. In the code you write next, you'll set up the error handler to do just that.
Note: The code for the program you'll create in this exercise is in
Echo07.java
.
To take over error handling, you override the
DefaultHandler
methods that handle fatal errors, nonfatal errors, and warnings as part of theErrorHandler
interface. The SAX parser delivers aSAXParseException
to each of these methods, so generating an exception when an error occurs is as simple as throwing it back.Add the following highlighted code to override the handler for errors:
public void processingInstruction(String target, String data) throws SAXException { ... }// treat validation errors as fatal public void error(SAXParseException e) throws SAXParseException { throw e; }
Note: It can be instructive to examine the error-handling methods defined in
org.xml.sax.helpers.DefaultHandler
. You'll see that theerror()
andwarning()
methods do nothing, whereasfatalError()
throws an exception. Of course, you could always override thefatalError()
method to throw a different exception. But if your code doesn't throw an exception when a fatal error occurs, then the SAX parser will. The XML specification requires it.
Handling Warnings
Warnings, too, are ignored by default. Warnings are informative can only be generated in the presence of a DTD or schema. For example, if an element is defined twice in a DTD, a warning is generated. It's not illegal, and it doesn't cause problems, but it's something you might like to know about because it might not have been intentional.
Add the following highlighted code to generate a message when a warning occurs:
// 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()); }
Because there is no good way to generate a warning without a DTD or schema, you won't be seeing any just yet. But when one does occur, you're ready!