diff --git a/src/nu/validator/htmlparser/sax/HtmlParser.java b/src/nu/validator/htmlparser/sax/HtmlParser.java
index 8ff8167d..44d274b4 100644
--- a/src/nu/validator/htmlparser/sax/HtmlParser.java
+++ b/src/nu/validator/htmlparser/sax/HtmlParser.java
@@ -99,6 +99,10 @@ public class HtmlParser implements XMLReader {
private LexicalHandler lexicalHandler = null;
+ private ContentHandler treeBuildingContentHandler = null;
+
+ private LexicalHandler treeBuildingLexicalHandler = null;
+
private DTDHandler dtdHandler = null;
private EntityResolver entityResolver = null;
@@ -172,6 +176,8 @@ private void lazyInit() {
if (driver == null) {
if (streamabilityViolationPolicy == XmlViolationPolicy.ALLOW) {
this.saxTreeBuilder = new SAXTreeBuilder();
+ this.saxTreeBuilder.setWiretap(treeBuildingContentHandler,
+ treeBuildingLexicalHandler);
this.treeBuilder = this.saxTreeBuilder;
this.saxStreamer = null;
this.driver = new Driver(newTokenizer(treeBuilder, true));
@@ -571,6 +577,29 @@ public void setLexicalHandler(LexicalHandler handler) {
}
}
+ /**
+ * Sets handlers that get events while the parser builds a tree (that is,
+ * when the streamability violation policy is ALLOW): The
+ * events a streaming parse would emit, at the same points in the input,
+ * and with the tokenizer as their locator. So they report the same
+ * locations as a streaming parse does, interleaved with any errors
+ * reported to the error handler. They don't form a well-formed SAX
+ * stream, since the tree builder can move nodes after creating them;
+ * the handlers set with setContentHandler and
+ * setLexicalHandler get the events for the finished tree.
+ *
+ * @param contentHandler the content handler, or null
+ * @param lexicalHandler the lexical handler, or null
+ */
+ public void setTreeBuildingWiretap(ContentHandler contentHandler,
+ LexicalHandler lexicalHandler) {
+ treeBuildingContentHandler = contentHandler;
+ treeBuildingLexicalHandler = lexicalHandler;
+ if (saxTreeBuilder != null) {
+ saxTreeBuilder.setWiretap(contentHandler, lexicalHandler);
+ }
+ }
+
/**
* @see org.xml.sax.XMLReader#setDTDHandler(org.xml.sax.DTDHandler)
*/
diff --git a/src/nu/validator/htmlparser/sax/SAXTreeBuilder.java b/src/nu/validator/htmlparser/sax/SAXTreeBuilder.java
index 06378e2b..4a9cc2a8 100644
--- a/src/nu/validator/htmlparser/sax/SAXTreeBuilder.java
+++ b/src/nu/validator/htmlparser/sax/SAXTreeBuilder.java
@@ -23,7 +23,9 @@
package nu.validator.htmlparser.sax;
+import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
import nu.validator.htmlparser.impl.HtmlAttributes;
import nu.validator.htmlparser.impl.TreeBuilder;
@@ -45,23 +47,53 @@ class SAXTreeBuilder extends TreeBuilder {
private Node cachedTablePreviousSibling = null;
+ private ContentHandler wiretapContentHandler = null;
+
+ private LexicalHandler wiretapLexicalHandler = null;
+
SAXTreeBuilder() {
super();
}
+ /**
+ * Sets handlers that get, while the tree is being built, the events a
+ * streaming parse would emit at the same points, with the tokenizer as
+ * their locator.
+ *
+ * @param contentHandler the content handler, or null
+ * @param lexicalHandler the lexical handler, or null
+ */
+ void setWiretap(ContentHandler contentHandler,
+ LexicalHandler lexicalHandler) {
+ wiretapContentHandler = contentHandler;
+ wiretapLexicalHandler = lexicalHandler;
+ }
+
@Override
- protected void appendComment(Element parent, char[] buf, int start, int length) {
+ protected void appendComment(Element parent, char[] buf, int start, int length)
+ throws SAXException {
parent.appendChild(new Comment(tokenizer, buf, start, length));
+ if (wiretapLexicalHandler != null) {
+ wiretapLexicalHandler.comment(buf, start, length);
+ }
}
@Override
- protected void appendCommentToDocument(char[] buf, int start, int length) {
+ protected void appendCommentToDocument(char[] buf, int start, int length)
+ throws SAXException {
document.appendChild(new Comment(tokenizer, buf, start, length));
+ if (wiretapLexicalHandler != null) {
+ wiretapLexicalHandler.comment(buf, start, length);
+ }
}
@Override
- protected void appendCharacters(Element parent, char[] buf, int start, int length) {
+ protected void appendCharacters(Element parent, char[] buf, int start, int length)
+ throws SAXException {
parent.appendChild(new Characters(tokenizer, buf, start, length));
+ if (wiretapContentHandler != null) {
+ wiretapContentHandler.characters(buf, start, length);
+ }
}
@Override
@@ -91,10 +123,15 @@ protected void addAttributesToElement(Element element, HtmlAttributes attributes
* @see nu.validator.htmlparser.impl.TreeBuilder#appendDoctypeToDocument(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
- protected void appendDoctypeToDocument(String name, String publicIdentifier, String systemIdentifier) {
+ protected void appendDoctypeToDocument(String name, String publicIdentifier, String systemIdentifier)
+ throws SAXException {
DTD dtd = new DTD(tokenizer, name, publicIdentifier, systemIdentifier);
dtd.setEndLocator(tokenizer);
document.appendChild(dtd);
+ if (wiretapLexicalHandler != null) {
+ wiretapLexicalHandler.startDTD(name, publicIdentifier, systemIdentifier);
+ wiretapLexicalHandler.endDTD();
+ }
}
/**
@@ -124,16 +161,43 @@ protected void end() throws SAXException {
document.setEndLocator(tokenizer);
cachedTable = null;
cachedTablePreviousSibling = null;
+ if (wiretapContentHandler != null) {
+ wiretapContentHandler.endDocument();
+ }
}
/**
* @see nu.validator.htmlparser.impl.TreeBuilder#start()
*/
@Override
- protected void start(boolean fragment) {
+ protected void start(boolean fragment) throws SAXException {
document = new Document(tokenizer);
cachedTable = null;
cachedTablePreviousSibling = null;
+ if (wiretapContentHandler != null) {
+ wiretapContentHandler.setDocumentLocator(tokenizer);
+ if (!fragment) {
+ wiretapContentHandler.startDocument();
+ }
+ }
+ }
+
+ @Override
+ protected void elementPushed(String ns, String name, Element node)
+ throws SAXException {
+ if (wiretapContentHandler != null) {
+ wiretapContentHandler.startElement(ns, name, name,
+ node.getAttributes());
+ }
+ }
+
+ @Override
+ protected void elementPopped(String ns, String name, Element node)
+ throws SAXException {
+ node.setEndLocator(tokenizer);
+ if (wiretapContentHandler != null) {
+ wiretapContentHandler.endElement(ns, name, name);
+ }
}
@Override
@@ -172,6 +236,9 @@ protected Element createAndInsertFosterParentedElement(String ns, String name,
} else {
stackParent.appendChild(child);
}
+ if (wiretapContentHandler != null) {
+ wiretapContentHandler.characters(buf, start, length);
+ }
}
@Override protected void insertFosterParentedChild(Element child,