blob: 312a2f07ab3c146785c70e683954a151f282adcb [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xerces" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, International
* Business Machines, Inc., http://www.apache.org. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.xml.dtm.ref;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.Attributes;
import org.xml.sax.XMLReader;
import java.io.IOException;
import org.apache.xml.dtm.ref.IncrementalSAXSource;
import org.apache.xml.utils.ThreadControllerWrapper;
import org.apache.xml.res.XMLErrorResources;
import org.apache.xml.res.XMLMessages;
/** <p>IncrementalSAXSource_Filter implements IncrementalSAXSource, using a
* standard SAX2 event source as its input and parcelling out those
* events gradually in reponse to deliverMoreNodes() requests. Output from the
* filter will be passed along to a SAX handler registered as our
* listener, but those callbacks will pass through a counting stage
* which periodically yields control back to the controller coroutine.
* </p>
*
* <p>%REVIEW%: This filter is not currenly intended to be reusable
* for parsing additional streams/documents. We may want to consider
* making it resettable at some point in the future. But it's a
* small object, so that'd be mostly a convenience issue; the cost
* of allocating each time is trivial compared to the cost of processing
* any nontrival stream.</p>
*
* <p>For a brief usage example, see the unit-test main() method.</p>
*
* <p>This is a simplification of the old CoroutineSAXParser, focusing
* specifically on filtering. The resulting controller protocol is _far_
* simpler and less error-prone; the only controller operation is deliverMoreNodes(),
* and the only requirement is that deliverMoreNodes(false) be called if you want to
* discard the rest of the stream and the previous deliverMoreNodes() didn't return
* false.
* */
public class IncrementalSAXSource_Filter
implements IncrementalSAXSource, ContentHandler, DTDHandler, LexicalHandler, ErrorHandler, Runnable
{
boolean DEBUG=false; //Internal status report
//
// Data
//
private CoroutineManager fCoroutineManager = null;
private int fControllerCoroutineID = -1;
private int fSourceCoroutineID = -1;
private ContentHandler clientContentHandler=null; // %REVIEW% support multiple?
private LexicalHandler clientLexicalHandler=null; // %REVIEW% support multiple?
private DTDHandler clientDTDHandler=null; // %REVIEW% support multiple?
private ErrorHandler clientErrorHandler=null; // %REVIEW% support multiple?
private int eventcounter;
private int frequency=5;
// Flag indicating that no more events should be delivered -- either
// because input stream ran to completion (endDocument), or because
// the user requested an early stop via deliverMoreNodes(false).
private boolean fNoMoreEvents=false;
// Support for startParse()
private XMLReader fXMLReader=null;
private InputSource fXMLReaderInputSource=null;
//
// Constructors
//
public IncrementalSAXSource_Filter() {
this.init( new CoroutineManager(), -1, -1);
}
/** Create a IncrementalSAXSource_Filter which is not yet bound to a specific
* SAX event source.
* */
public IncrementalSAXSource_Filter(CoroutineManager co, int controllerCoroutineID)
{
this.init( co, controllerCoroutineID, -1 );
}
//
// Factories
//
static public IncrementalSAXSource createIncrementalSAXSource(CoroutineManager co, int controllerCoroutineID) {
return new IncrementalSAXSource_Filter(co, controllerCoroutineID);
}
//
// Public methods
//
public void init( CoroutineManager co, int controllerCoroutineID,
int sourceCoroutineID)
{
if(co==null)
co = new CoroutineManager();
fCoroutineManager = co;
fControllerCoroutineID = co.co_joinCoroutineSet(controllerCoroutineID);
fSourceCoroutineID = co.co_joinCoroutineSet(sourceCoroutineID);
if (fControllerCoroutineID == -1 || fSourceCoroutineID == -1)
throw new RuntimeException(XMLMessages.createXMLMessage(XMLErrorResources.ER_COJOINROUTINESET_FAILED, null)); //"co_joinCoroutineSet() failed");
fNoMoreEvents=false;
eventcounter=frequency;
}
/** Bind our input streams to an XMLReader.
*
* Just a convenience routine; obviously you can explicitly register
* this as a listener with the same effect.
* */
public void setXMLReader(XMLReader eventsource)
{
fXMLReader=eventsource;
eventsource.setContentHandler(this);
eventsource.setDTDHandler(this);
eventsource.setErrorHandler(this); // to report fatal errors in filtering mode
// Not supported by all SAX2 filters:
try
{
eventsource.
setProperty("http://xml.org/sax/properties/lexical-handler",
this);
}
catch(SAXNotRecognizedException e)
{
// Nothing we can do about it
}
catch(SAXNotSupportedException e)
{
// Nothing we can do about it
}
// Should we also bind as other varieties of handler?
// (DTDHandler and so on)
}
// Register a content handler for us to output to
public void setContentHandler(ContentHandler handler)
{
clientContentHandler=handler;
}
// Register a DTD handler for us to output to
public void setDTDHandler(DTDHandler handler)
{
clientDTDHandler=handler;
}
// Register a lexical handler for us to output to
// Not all filters support this...
// ??? Should we register directly on the filter?
// NOTE NAME -- subclassing issue in the Xerces version
public void setLexicalHandler(LexicalHandler handler)
{
clientLexicalHandler=handler;
}
// Register an error handler for us to output to
// NOTE NAME -- subclassing issue in the Xerces version
public void setErrHandler(ErrorHandler handler)
{
clientErrorHandler=handler;
}
// Set the number of events between resumes of our coroutine
// Immediately resets number of events before _next_ resume as well.
public void setReturnFrequency(int events)
{
if(events<1) events=1;
frequency=eventcounter=events;
}
//
// ContentHandler methods
// These pass the data to our client ContentHandler...
// but they also count the number of events passing through,
// and resume our coroutine each time that counter hits zero and
// is reset.
//
// Note that for everything except endDocument and fatalError, we do the count-and-yield
// BEFORE passing the call along. I'm hoping that this will encourage JIT
// compilers to realize that these are tail-calls, reducing the expense of
// the additional layer of data flow.
//
// %REVIEW% Glenn suggests that pausing after endElement, endDocument,
// and characters may be sufficient. I actually may not want to
// stop after characters, since in our application these wind up being
// concatenated before they're processed... but that risks huge blocks of
// text causing greater than usual readahead. (Unlikely? Consider the
// possibility of a large base-64 block in a SOAP stream.)
//
public void characters(char[] ch, int start, int length)
throws org.xml.sax.SAXException
{
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.characters(ch,start,length);
}
public void endDocument()
throws org.xml.sax.SAXException
{
// EXCEPTION: In this case we need to run the event BEFORE we yield.
if(clientContentHandler!=null)
clientContentHandler.endDocument();
eventcounter=0;
co_yield(false);
}
public void endElement(java.lang.String namespaceURI, java.lang.String localName,
java.lang.String qName)
throws org.xml.sax.SAXException
{
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.endElement(namespaceURI,localName,qName);
}
public void endPrefixMapping(java.lang.String prefix)
throws org.xml.sax.SAXException
{
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.endPrefixMapping(prefix);
}
public void ignorableWhitespace(char[] ch, int start, int length)
throws org.xml.sax.SAXException
{
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.ignorableWhitespace(ch,start,length);
}
public void processingInstruction(java.lang.String target, java.lang.String data)
throws org.xml.sax.SAXException
{
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.processingInstruction(target,data);
}
public void setDocumentLocator(Locator locator)
{
if(--eventcounter<=0)
{
// This can cause a hang. -sb
// co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.setDocumentLocator(locator);
}
public void skippedEntity(java.lang.String name)
throws org.xml.sax.SAXException
{
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.skippedEntity(name);
}
public void startDocument()
throws org.xml.sax.SAXException
{
co_entry_pause();
// Otherwise, begin normal event delivery
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.startDocument();
}
public void startElement(java.lang.String namespaceURI, java.lang.String localName,
java.lang.String qName, Attributes atts)
throws org.xml.sax.SAXException
{
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.startElement(namespaceURI, localName, qName, atts);
}
public void startPrefixMapping(java.lang.String prefix, java.lang.String uri)
throws org.xml.sax.SAXException
{
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
if(clientContentHandler!=null)
clientContentHandler.startPrefixMapping(prefix,uri);
}
//
// LexicalHandler support. Not all SAX2 filters support these events
// but we may want to pass them through when they exist...
//
// %REVIEW% These do NOT currently affect the eventcounter; I'm asserting
// that they're rare enough that it makes little or no sense to
// pause after them. As such, it may make more sense for folks who
// actually want to use them to register directly with the filter.
// But I want 'em here for now, to remind us to recheck this assertion!
//
public void comment(char[] ch, int start, int length)
throws org.xml.sax.SAXException
{
if(null!=clientLexicalHandler)
clientLexicalHandler.comment(ch,start,length);
}
public void endCDATA()
throws org.xml.sax.SAXException
{
if(null!=clientLexicalHandler)
clientLexicalHandler.endCDATA();
}
public void endDTD()
throws org.xml.sax.SAXException
{
if(null!=clientLexicalHandler)
clientLexicalHandler.endDTD();
}
public void endEntity(java.lang.String name)
throws org.xml.sax.SAXException
{
if(null!=clientLexicalHandler)
clientLexicalHandler.endEntity(name);
}
public void startCDATA()
throws org.xml.sax.SAXException
{
if(null!=clientLexicalHandler)
clientLexicalHandler.startCDATA();
}
public void startDTD(java.lang.String name, java.lang.String publicId,
java.lang.String systemId)
throws org.xml.sax.SAXException
{
if(null!=clientLexicalHandler)
clientLexicalHandler. startDTD(name, publicId, systemId);
}
public void startEntity(java.lang.String name)
throws org.xml.sax.SAXException
{
if(null!=clientLexicalHandler)
clientLexicalHandler.startEntity(name);
}
//
// DTDHandler support.
public void notationDecl(String a, String b, String c) throws SAXException
{
if(null!=clientDTDHandler)
clientDTDHandler.notationDecl(a,b,c);
}
public void unparsedEntityDecl(String a, String b, String c, String d) throws SAXException
{
if(null!=clientDTDHandler)
clientDTDHandler.unparsedEntityDecl(a,b,c,d);
}
//
// ErrorHandler support.
//
// PROBLEM: Xerces is apparently _not_ calling the ErrorHandler for
// exceptions thrown by the ContentHandler, which prevents us from
// handling this properly when running in filtering mode with Xerces
// as our event source. It's unclear whether this is a Xerces bug
// or a SAX design flaw.
//
// %REVIEW% Current solution: In filtering mode, it is REQUIRED that
// event source make sure this method is invoked if the event stream
// abends before endDocument is delivered. If that means explicitly calling
// us in the exception handling code because it won't be delivered as part
// of the normal SAX ErrorHandler stream, that's fine; Not Our Problem.
//
public void error(SAXParseException exception) throws SAXException
{
if(null!=clientErrorHandler)
clientErrorHandler.error(exception);
}
public void fatalError(SAXParseException exception) throws SAXException
{
// EXCEPTION: In this case we need to run the event BEFORE we yield --
// just as with endDocument, this terminates the event stream.
if(null!=clientErrorHandler)
clientErrorHandler.error(exception);
eventcounter=0;
co_yield(false);
}
public void warning(SAXParseException exception) throws SAXException
{
if(null!=clientErrorHandler)
clientErrorHandler.error(exception);
}
//
// coroutine support
//
public int getSourceCoroutineID() {
return fSourceCoroutineID;
}
public int getControllerCoroutineID() {
return fControllerCoroutineID;
}
/** @return the CoroutineManager this CoroutineFilter object is bound to.
* If you're using the do...() methods, applications should only
* need to talk to the CoroutineManager once, to obtain the
* application's Coroutine ID.
* */
public CoroutineManager getCoroutineManager()
{
return fCoroutineManager;
}
/** <p>In the SAX delegation code, I've inlined the count-down in
* the hope of encouraging compilers to deliver better
* performance. However, if we subclass (eg to directly connect the
* output to a DTM builder), that would require calling super in
* order to run that logic... which seems inelegant. Hence this
* routine for the convenience of subclasses: every [frequency]
* invocations, issue a co_yield.</p>
*
* @param moreExepected Should always be true unless this is being called
* at the end of endDocument() handling.
* */
protected void count_and_yield(boolean moreExpected) throws SAXException
{
if(!moreExpected) eventcounter=0;
if(--eventcounter<=0)
{
co_yield(true);
eventcounter=frequency;
}
}
/**
* co_entry_pause is called in startDocument() before anything else
* happens. It causes the filter to wait for a "go ahead" request
* from the controller before delivering any events. Note that
* the very first thing the controller tells us may be "I don't
* need events after all"!
*/
private void co_entry_pause() throws SAXException
{
if(fCoroutineManager==null)
{
// Nobody called init()? Do it now...
init(null,-1,-1);
}
try
{
Object arg=fCoroutineManager.co_entry_pause(fSourceCoroutineID);
if(arg==Boolean.FALSE)
co_yield(false);
}
catch(NoSuchMethodException e)
{
// Coroutine system says we haven't registered. That's an
// application coding error, and is unrecoverable.
if(DEBUG) e.printStackTrace();
throw new SAXException(e);
}
}
/**
* Co_Yield handles coroutine interactions while a parse is in progress.
*
* When moreRemains==true, we are pausing after delivering events, to
* ask if more are needed. We will resume the controller thread with
* co_resume(Boolean.TRUE, ...)
* When control is passed back it may indicate
* Boolean.TRUE indication to continue delivering events
* Boolean.FALSE indication to discontinue events and shut down.
*
* When moreRemains==false, we shut down immediately without asking the
* controller's permission. Normally this means end of document has been
* reached.
*
* Shutting down a IncrementalSAXSource_Filter requires terminating the incoming
* SAX event stream. If we are in control of that stream (if it came
* from an XMLReader passed to our startReader() method), we can do so
* very quickly by throwing a reserved exception to it. If the stream is
* coming from another source, we can't do that because its caller may
* not be prepared for this "normal abnormal exit", and instead we put
* ourselves in a "spin" mode where events are discarded.
*/
private void co_yield(boolean moreRemains) throws SAXException
{
// Horrendous kluge to run filter to completion. See below.
if(fNoMoreEvents)
return;
try // Coroutine manager might throw no-such.
{
Object arg=Boolean.FALSE;
if(moreRemains)
{
// Yield control, resume parsing when done
arg = fCoroutineManager.co_resume(Boolean.TRUE, fSourceCoroutineID,
fControllerCoroutineID);
}
// If we're at end of document or were told to stop early
if(arg==Boolean.FALSE)
{
fNoMoreEvents=true;
if(fXMLReader!=null) // Running under startParseThread()
throw new StopException(); // We'll co_exit from there.
// Yield control. We do NOT expect anyone to ever ask us again.
fCoroutineManager.co_exit_to(Boolean.FALSE, fSourceCoroutineID,
fControllerCoroutineID);
}
}
catch(NoSuchMethodException e)
{
// Shouldn't happen unless we've miscoded our coroutine logic
// "Shut down the garbage smashers on the detention level!"
fNoMoreEvents=true;
fCoroutineManager.co_exit(fSourceCoroutineID);
throw new SAXException(e);
}
}
//
// Convenience: Run an XMLReader in a thread
//
/** Launch a thread that will run an XMLReader's parse() operation within
* a thread, feeding events to this IncrementalSAXSource_Filter. Mostly a convenience
* routine, but has the advantage that -- since we invoked parse() --
* we can halt parsing quickly via a StopException rather than waiting
* for the SAX stream to end by itself.
*
* @throws SAXException is parse thread is already in progress
* or parsing can not be started.
* */
public void startParse(InputSource source) throws SAXException
{
if(fNoMoreEvents)
throw new SAXException(XMLMessages.createXMLMessage(XMLErrorResources.ER_INCRSAXSRCFILTER_NOT_RESTARTABLE, null)); //"IncrmentalSAXSource_Filter not currently restartable.");
if(fXMLReader==null)
throw new SAXException(XMLMessages.createXMLMessage(XMLErrorResources.ER_XMLRDR_NOT_BEFORE_STARTPARSE, null)); //"XMLReader not before startParse request");
fXMLReaderInputSource=source;
// Xalan thread pooling...
// org.apache.xalan.transformer.TransformerImpl.runTransformThread(this);
ThreadControllerWrapper.runThread(this, -1);
}
/* Thread logic to support startParseThread()
*/
public void run()
{
// Guard against direct invocation of start().
if(fXMLReader==null) return;
if(DEBUG)System.out.println("IncrementalSAXSource_Filter parse thread launched");
// Initially assume we'll run successfully.
Object arg=Boolean.FALSE;
// For the duration of this operation, all coroutine handshaking
// will occur in the co_yield method. That's the nice thing about
// coroutines; they give us a way to hand off control from the
// middle of a synchronous method.
try
{
fXMLReader.parse(fXMLReaderInputSource);
}
catch(IOException ex)
{
arg=ex;
}
catch(StopException ex)
{
// Expected and harmless
if(DEBUG)System.out.println("Active IncrementalSAXSource_Filter normal stop exception");
}
catch (SAXException ex)
{
Exception inner=ex.getException();
if(inner instanceof StopException){
// Expected and harmless
if(DEBUG)System.out.println("Active IncrementalSAXSource_Filter normal stop exception");
}
else
{
// Unexpected malfunction
if(DEBUG)
{
System.out.println("Active IncrementalSAXSource_Filter UNEXPECTED SAX exception: "+inner);
inner.printStackTrace();
}
arg=ex;
}
} // end parse
// Mark as no longer running in thread.
fXMLReader=null;
try
{
// Mark as done and yield control to the controller coroutine
fNoMoreEvents=true;
fCoroutineManager.co_exit_to(arg, fSourceCoroutineID,
fControllerCoroutineID);
}
catch(java.lang.NoSuchMethodException e)
{
// Shouldn't happen unless we've miscoded our coroutine logic
// "CPO, shut down the garbage smashers on the detention level!"
e.printStackTrace(System.err);
fCoroutineManager.co_exit(fSourceCoroutineID);
}
}
/** Used to quickly terminate parse when running under a
startParse() thread. Only its type is important. */
class StopException extends RuntimeException
{
}
/** deliverMoreNodes() is a simple API which tells the coroutine
* parser that we need more nodes. This is intended to be called
* from one of our partner routines, and serves to encapsulate the
* details of how incremental parsing has been achieved.
*
* @param parsemore If true, tells the incremental filter to generate
* another chunk of output. If false, tells the filter that we're
* satisfied and it can terminate parsing of this document.
*
* @return Boolean.TRUE if there may be more events available by invoking
* deliverMoreNodes() again. Boolean.FALSE if parsing has run to completion (or been
* terminated by deliverMoreNodes(false). Or an exception object if something
* malfunctioned. %REVIEW% We _could_ actually throw the exception, but
* that would require runinng deliverMoreNodes() in a try/catch... and for many
* applications, exception will be simply be treated as "not TRUE" in
* any case.
* */
public Object deliverMoreNodes(boolean parsemore)
{
// If parsing is already done, we can immediately say so
if(fNoMoreEvents)
return Boolean.FALSE;
try
{
Object result =
fCoroutineManager.co_resume(parsemore?Boolean.TRUE:Boolean.FALSE,
fControllerCoroutineID, fSourceCoroutineID);
if(result==Boolean.FALSE)
fCoroutineManager.co_exit(fControllerCoroutineID);
return result;
}
// SHOULD NEVER OCCUR, since the coroutine number and coroutine manager
// are those previously established for this IncrementalSAXSource_Filter...
// So I'm just going to return it as a parsing exception, for now.
catch(NoSuchMethodException e)
{
return e;
}
}
//================================================================
/** Simple unit test. Attempt coroutine parsing of document indicated
* by first argument (as a URI), report progress.
*/
/*
public static void main(String args[])
{
System.out.println("Starting...");
org.xml.sax.XMLReader theSAXParser=
new org.apache.xerces.parsers.SAXParser();
for(int arg=0;arg<args.length;++arg)
{
// The filter is not currently designed to be restartable
// after a parse has ended. Generate a new one each time.
IncrementalSAXSource_Filter filter=
new IncrementalSAXSource_Filter();
// Use a serializer as our sample output
org.apache.xml.serialize.XMLSerializer trace;
trace=new org.apache.xml.serialize.XMLSerializer(System.out,null);
filter.setContentHandler(trace);
filter.setLexicalHandler(trace);
try
{
InputSource source = new InputSource(args[arg]);
Object result=null;
boolean more=true;
// init not issued; we _should_ automagically Do The Right Thing
// Bind parser, kick off parsing in a thread
filter.setXMLReader(theSAXParser);
filter.startParse(source);
for(result = filter.deliverMoreNodes(more);
(result instanceof Boolean && ((Boolean)result)==Boolean.TRUE);
result = filter.deliverMoreNodes(more))
{
System.out.println("\nSome parsing successful, trying more.\n");
// Special test: Terminate parsing early.
if(arg+1<args.length && "!".equals(args[arg+1]))
{
++arg;
more=false;
}
}
if (result instanceof Boolean && ((Boolean)result)==Boolean.FALSE)
{
System.out.println("\nFilter ended (EOF or on request).\n");
}
else if (result == null) {
System.out.println("\nUNEXPECTED: Filter says shut down prematurely.\n");
}
else if (result instanceof Exception) {
System.out.println("\nFilter threw exception:");
((Exception)result).printStackTrace();
}
}
catch(SAXException e)
{
e.printStackTrace();
}
} // end for
}
*/
} // class IncrementalSAXSource_Filter