diff options
| author | Joshua Peek <josh@joshpeek.com> | 2012-06-22 10:09:24 -0500 |
|---|---|---|
| committer | Joshua Peek <josh@joshpeek.com> | 2012-06-22 10:09:24 -0500 |
| commit | 5521dd08a062f39036b045c819201b6235fb32f8 (patch) | |
| tree | e3085d9a7a58f084eabefc19987f3acb029d03ac /samples/java/HtmlDomParserContext.java | |
| parent | b571c47a1c61394d371dcd4b8d2165b4e16f4e84 (diff) | |
Move test fixtures to samples/
Diffstat (limited to 'samples/java/HtmlDomParserContext.java')
| -rw-r--r-- | samples/java/HtmlDomParserContext.java | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/samples/java/HtmlDomParserContext.java b/samples/java/HtmlDomParserContext.java new file mode 100644 index 0000000..6c13e3d --- /dev/null +++ b/samples/java/HtmlDomParserContext.java @@ -0,0 +1,243 @@ +/** + * (The MIT License) + * + * Copyright (c) 2008 - 2012: + * + * * {Aaron Patterson}[http://tenderlovemaking.com] + * * {Mike Dalessio}[http://mike.daless.io] + * * {Charles Nutter}[http://blog.headius.com] + * * {Sergio Arbeo}[http://www.serabe.com] + * * {Patrick Mahoney}[http://polycrystal.org] + * * {Yoko Harada}[http://yokolet.blogspot.com] + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package nokogiri.internals; + +import static nokogiri.internals.NokogiriHelpers.getNokogiriClass; +import static nokogiri.internals.NokogiriHelpers.isNamespace; +import static nokogiri.internals.NokogiriHelpers.stringOrNil; +import nokogiri.HtmlDocument; +import nokogiri.NokogiriService; +import nokogiri.XmlDocument; + +import org.apache.xerces.parsers.DOMParser; +import org.apache.xerces.xni.Augmentations; +import org.apache.xerces.xni.QName; +import org.apache.xerces.xni.XMLAttributes; +import org.apache.xerces.xni.XNIException; +import org.apache.xerces.xni.parser.XMLDocumentFilter; +import org.apache.xerces.xni.parser.XMLParserConfiguration; +import org.cyberneko.html.HTMLConfiguration; +import org.cyberneko.html.filters.DefaultFilter; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.NodeList; + +/** + * Parser for HtmlDocument. This class actually parses HtmlDocument using NekoHtml. + * + * @author sergio + * @author Patrick Mahoney <pat@polycrystal.org> + * @author Yoko Harada <yokolet@gmail.com> + */ +public class HtmlDomParserContext extends XmlDomParserContext { + + public HtmlDomParserContext(Ruby runtime, IRubyObject options) { + super(runtime, options); + } + + public HtmlDomParserContext(Ruby runtime, IRubyObject encoding, IRubyObject options) { + super(runtime, encoding, options); + } + + @Override + protected void initErrorHandler() { + if (options.strict) { + errorHandler = new NokogiriStrictErrorHandler(options.noError, options.noWarning); + } else { + errorHandler = new NokogiriNonStrictErrorHandler4NekoHtml(options.noError, options.noWarning); + } + } + + @Override + protected void initParser(Ruby runtime) { + XMLParserConfiguration config = new HTMLConfiguration(); + XMLDocumentFilter removeNSAttrsFilter = new RemoveNSAttrsFilter(); + XMLDocumentFilter elementValidityCheckFilter = new ElementValidityCheckFilter(errorHandler); + //XMLDocumentFilter[] filters = { removeNSAttrsFilter, elementValidityCheckFilter}; + XMLDocumentFilter[] filters = { elementValidityCheckFilter}; + + config.setErrorHandler(this.errorHandler); + parser = new DOMParser(config); + + // see http://nekohtml.sourceforge.net/settings.html for details + setProperty("http://cyberneko.org/html/properties/default-encoding", java_encoding); + setProperty("http://cyberneko.org/html/properties/names/elems", "lower"); + setProperty("http://cyberneko.org/html/properties/names/attrs", "lower"); + setProperty("http://cyberneko.org/html/properties/filters", filters); + setFeature("http://cyberneko.org/html/features/report-errors", true); + setFeature("http://xml.org/sax/features/namespaces", false); + setFeature("http://cyberneko.org/html/features/insert-doctype", true); + } + + /** + * Enable NekoHTML feature for balancing tags in a document fragment. + * + * This method is used in XmlNode#in_context method. + */ + public void enableDocumentFragment() { + setFeature("http://cyberneko.org/html/features/balance-tags/document-fragment", true); + } + + @Override + protected XmlDocument getNewEmptyDocument(ThreadContext context) { + IRubyObject[] args = new IRubyObject[0]; + return (XmlDocument) XmlDocument.rbNew(context, getNokogiriClass(context.getRuntime(), "Nokogiri::HTML::Document"), args); + } + + @Override + protected XmlDocument wrapDocument(ThreadContext context, + RubyClass klazz, + Document document) { + HtmlDocument htmlDocument = (HtmlDocument) NokogiriService.HTML_DOCUMENT_ALLOCATOR.allocate(context.getRuntime(), klazz); + htmlDocument.setDocumentNode(context, document); + if (ruby_encoding.isNil()) { + // ruby_encoding might have detected by HtmlDocument::EncodingReader + if (detected_encoding != null && !detected_encoding.isNil()) { + ruby_encoding = detected_encoding; + } else { + // no encoding given & no encoding detected, then try to get it + String charset = tryGetCharsetFromHtml5MetaTag(document); + ruby_encoding = stringOrNil(context.getRuntime(), charset); + } + } + htmlDocument.setEncoding(ruby_encoding); + htmlDocument.setParsedEncoding(java_encoding); + return htmlDocument; + } + + // NekoHtml doesn't understand HTML5 meta tag format. This fails to detect charset + // from an HTML5 style meta tag. Luckily, the meta tag and charset exists in DOM tree + // so, this method attempts to find the charset. + private String tryGetCharsetFromHtml5MetaTag(Document document) { + if (!"html".equalsIgnoreCase(document.getDocumentElement().getNodeName())) return null; + NodeList list = document.getDocumentElement().getChildNodes(); + for (int i = 0; i < list.getLength(); i++) { + if ("head".equalsIgnoreCase(list.item(i).getNodeName())) { + NodeList headers = list.item(i).getChildNodes(); + for (int j = 0; j < headers.getLength(); j++) { + if ("meta".equalsIgnoreCase(headers.item(j).getNodeName())) { + NamedNodeMap nodeMap = headers.item(j).getAttributes(); + for (int k = 0; k < nodeMap.getLength(); k++) { + if ("charset".equalsIgnoreCase(nodeMap.item(k).getNodeName())) { + return nodeMap.item(k).getNodeValue(); + } + } + } + } + } + } + return null; + } + + /** + * Filter to strip out attributes that pertain to XML namespaces. + */ + public static class RemoveNSAttrsFilter extends DefaultFilter { + @Override + public void startElement(QName element, XMLAttributes attrs, + Augmentations augs) throws XNIException { + int i; + for (i = 0; i < attrs.getLength(); ++i) { + if (isNamespace(attrs.getQName(i))) { + attrs.removeAttributeAt(i); + --i; + } + } + + element.uri = null; + super.startElement(element, attrs, augs); + } + } + + public static class ElementValidityCheckFilter extends DefaultFilter { + private NokogiriErrorHandler errorHandler; + + private ElementValidityCheckFilter(NokogiriErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + // element names from xhtml1-strict.dtd + private static String[][] element_names = { + {"a", "abbr", "acronym", "address", "area"}, + {"b", "base", "basefont", "bdo", "big", "blockquote", "body", "br", "button"}, + {"caption", "cite", "code", "col", "colgroup"}, + {"dd", "del", "dfn", "div", "dl", "dt"}, + {"em"}, + {"fieldset", "font", "form", "frame", "frameset"}, + {}, // g + {"h1", "h2", "h3", "h4", "h5", "h6", "head", "hr", "html"}, + {"i", "iframe", "img", "input", "ins"}, + {}, // j + {"kbd"}, + {"label", "legend", "li", "link"}, + {"map", "meta"}, + {"noframes", "noscript"}, + {"object", "ol", "optgroup", "option"}, + {"p", "param", "pre"}, + {"q"}, + {}, // r + {"s", "samp", "script", "select", "small", "span", "strike", "strong", "style", "sub", "sup"}, + {"table", "tbody", "td", "textarea", "tfoot", "th", "thead", "title", "tr", "tt"}, + {"u", "ul"}, + {"var"}, + {}, // w + {}, // x + {}, // y + {} // z + }; + + private boolean isValid(String testee) { + char[] c = testee.toCharArray(); + int index = new Integer(c[0]) - 97; + if (index > 25) return false; + for (int i=0; i<element_names[index].length; i++) { + if (testee.equals(element_names[index][i])) { + return true; + } + } + return false; + } + + @Override + public void startElement(QName name, XMLAttributes attrs, Augmentations augs) throws XNIException { + if (!isValid(name.rawname)) { + errorHandler.getErrors().add(new Exception("Tag " + name.rawname + " invalid")); + } + super.startElement(name, attrs, augs); + } + } +} |
