/** * (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.getLocalPart; 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 nokogiri.internals.SaveContext; 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.NamedNodeMap; /** * Class for Nokogiri::XML::DocumentFragment * * @author sergio * @author Yoko Harada */ @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] = RubyString.newString(context.getRuntime(), ignoreNamespaceIfNeeded(doc, rubyStringToString(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 Pattern qname_pattern = Pattern.compile("[^\\s]+:[^=\\s]+"); private static Pattern starttag_pattern = Pattern.compile("<[^]+>"); private static String ignoreNamespaceIfNeeded(XmlDocument doc, String tags) { if (doc.getDocument() == null) return tags; if (doc.getDocument().getDocumentElement() == null) return tags; Matcher matcher = qname_pattern.matcher(tags); Map rewriteTable = new HashMap(); while(matcher.find()) { String qName = matcher.group(); NamedNodeMap nodeMap = doc.getDocument().getDocumentElement().getAttributes(); if (!isNamespaceDefined(qName, nodeMap)) { rewriteTable.put(qName, getLocalPart(qName)); } } Set keys = rewriteTable.keySet(); for (String key : keys) { tags = tags.replace(key, rewriteTable.get(key)); } return tags; } 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 rewriteTable = new HashMap(); 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 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); } @Override public void saveContent(ThreadContext context, SaveContext ctx) { saveNodeListContent(context, (XmlNodeSet) children(context), ctx); } }