/** * (The MIT License) * * Copyright (c) 2008 - 2011: * * * {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; import static nokogiri.internals.NokogiriHelpers.getLocalNameForNamespace; import static nokogiri.internals.NokogiriHelpers.getNokogiriClass; import static nokogiri.internals.NokogiriHelpers.getPrefix; import static nokogiri.internals.NokogiriHelpers.isNamespace; import static nokogiri.internals.NokogiriHelpers.rubyStringToString; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyString; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.javasupport.util.RuntimeHelpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; /** * Class for Nokogiri::XML::DocumentFragment * * @author sergio * @author Yoko Harada <yokolet@gmail.com> */ @JRubyClass(name="Nokogiri::XML::DocumentFragment", parent="Nokogiri::XML::Node") public class XmlDocumentFragment extends XmlNode { private XmlElement fragmentContext = null; public XmlDocumentFragment(Ruby ruby) { this(ruby, getNokogiriClass(ruby, "Nokogiri::XML::DocumentFragment")); } public XmlDocumentFragment(Ruby ruby, RubyClass klazz) { super(ruby, klazz); } @JRubyMethod(name="new", meta = true, required=1, optional=2) public static IRubyObject rbNew(ThreadContext context, IRubyObject cls, IRubyObject[] args) { if(args.length < 1) { throw context.getRuntime().newArgumentError(args.length, 1); } if(!(args[0] instanceof XmlDocument)){ throw context.getRuntime().newArgumentError("first parameter must be a Nokogiri::XML::Document instance"); } XmlDocument doc = (XmlDocument) args[0]; // make wellformed fragment, ignore invalid namespace, or add appropriate namespace to parse if (args.length > 1 && args[1] instanceof RubyString) { args[1] = trim(context, doc, (RubyString)args[1]); if (XmlDocumentFragment.isTag((RubyString)args[1])) { args[1] = RubyString.newString(context.getRuntime(), addNamespaceDeclIfNeeded(doc, rubyStringToString(args[1]))); } } XmlDocumentFragment fragment = (XmlDocumentFragment) NokogiriService.XML_DOCUMENT_FRAGMENT_ALLOCATOR.allocate(context.getRuntime(), (RubyClass)cls); fragment.setDocument(context, doc); fragment.setNode(context, doc.getDocument().createDocumentFragment()); //TODO: Get namespace definitions from doc. if (args.length == 3 && args[2] != null && args[2] instanceof XmlElement) { fragment.fragmentContext = (XmlElement)args[2]; } RuntimeHelpers.invoke(context, fragment, "initialize", args); return fragment; } private static IRubyObject trim(ThreadContext context, XmlDocument xmlDocument, RubyString str) { // checks whether schema is given. if exists, allows whitespace processing to a parser Document document = (Document)xmlDocument.node; if (document.getDoctype() != null) return str; // strips trailing \n off forcefully // not to return new object in case of no chomp needed, chomp! is used here. IRubyObject result; if (context.getRuntime().is1_9()) result = str.chomp_bang19(context); else result = str.chomp_bang(context); return result.isNil() ? str : result; } private static boolean isTag(RubyString ruby_string) { String str = rubyStringToString(ruby_string); if (str.startsWith("<") && str.endsWith(">")) return true; return false; } private static Pattern qname_pattern = Pattern.compile("[^</:>\\s]+:[^</:>=\\s]+"); private static Pattern starttag_pattern = Pattern.compile("<[^</>]+>"); private static boolean isNamespaceDefined(String qName, NamedNodeMap nodeMap) { if (isNamespace(qName.intern())) return true; for (int i=0; i < nodeMap.getLength(); i++) { Attr attr = (Attr)nodeMap.item(i); if (isNamespace(attr.getNodeName())) { String localPart = getLocalNameForNamespace(attr.getNodeName()); if (getPrefix(qName).equals(localPart)) { return true; } } } return false; } private static String addNamespaceDeclIfNeeded(XmlDocument doc, String tags) { if (doc.getDocument() == null) return tags; if (doc.getDocument().getDocumentElement() == null) return tags; Matcher matcher = starttag_pattern.matcher(tags); Map<String, String> rewriteTable = new HashMap<String, String>(); while(matcher.find()) { String start_tag = matcher.group(); Matcher matcher2 = qname_pattern.matcher(start_tag); while(matcher2.find()) { String qName = matcher2.group(); NamedNodeMap nodeMap = doc.getDocument().getDocumentElement().getAttributes(); if (isNamespaceDefined(qName, nodeMap)) { String namespaceDecl = getNamespceDecl(getPrefix(qName), nodeMap); if (namespaceDecl != null) { rewriteTable.put("<"+qName+">", "<"+qName + " " + namespaceDecl+">"); } } } } Set<String> keys = rewriteTable.keySet(); for (String key : keys) { tags = tags.replace(key, rewriteTable.get(key)); } return tags; } private static String getNamespceDecl(String prefix, NamedNodeMap nodeMap) { for (int i=0; i < nodeMap.getLength(); i++) { Attr attr = (Attr)nodeMap.item(i); if (prefix.equals(attr.getLocalName())) { return attr.getName() + "=\"" + attr.getValue() + "\""; } } return null; } public XmlElement getFragmentContext() { return fragmentContext; } //@Override public void add_child(ThreadContext context, XmlNode child) { // Some magic for DocumentFragment Ruby ruby = context.getRuntime(); XmlNodeSet children = (XmlNodeSet) child.children(context); long length = children.length(); RubyArray childrenArray = children.convertToArray(); if(length != 0) { for(int i = 0; i < length; i++) { XmlNode item = (XmlNode) ((XmlNode) childrenArray.aref(ruby.newFixnum(i))).dup_implementation(context, true); add_child(context, item); } } } @Override public void relink_namespace(ThreadContext context) { ((XmlNodeSet) children(context)).relink_namespace(context); } }