package nokogiri.internals; import static nokogiri.internals.NokogiriHelpers.getNokogiriClass; import static nokogiri.internals.NokogiriHelpers.isNamespace; import static nokogiri.internals.NokogiriHelpers.isXmlBase; import static nokogiri.internals.NokogiriHelpers.stringOrBlank; import static nokogiri.internals.NokogiriHelpers.stringOrNil; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import nokogiri.XmlAttr; import nokogiri.XmlDocument; import nokogiri.XmlSyntaxError; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyBoolean; import org.jruby.RubyHash; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.w3c.dom.Attr; import org.xml.sax.Attributes; import org.xml.sax.SAXParseException; public abstract class ReaderNode { Ruby ruby; public ReaderAttributeList attributeList; public Map namespaces; public int depth, nodeType; public String lang, localName, xmlBase, prefix, name, uri, value, xmlVersion = "1.0"; public boolean hasChildren = false; public abstract String getString(); public IRubyObject getAttributeByIndex(IRubyObject index){ if(index.isNil()) return index; long i = index.convertToInteger().getLongValue(); if(i > Integer.MAX_VALUE) { throw ruby.newArgumentError("value too long to be an array index"); } if (attributeList == null) return ruby.getNil(); if (i<0 || attributeList.length <= i) return ruby.getNil(); return stringOrBlank(ruby, attributeList.values.get(((Long)i).intValue())); } public IRubyObject getAttributeByName(IRubyObject name){ if(attributeList == null) return ruby.getNil(); String value = attributeList.getByName((String)name.toJava(String.class)); return stringOrNil(ruby, value); } public IRubyObject getAttributeByName(String name){ if(attributeList == null) return ruby.getNil(); String value = attributeList.getByName(name); return stringOrNil(ruby, value); } public IRubyObject getAttributeCount(){ if(attributeList == null) return ruby.newFixnum(0); return ruby.newFixnum(attributeList.length); } public IRubyObject getAttributesNodes() { RubyArray array = RubyArray.newArray(ruby); if (attributeList != null && attributeList.length > 0) { // XmlDocument is used to create XmlAttr type of attribute nodes. Since the attribute nodes // should have name and to_s methods, using XmlAttr would be convenient. XmlDocument xmlDoc = (XmlDocument)XmlDocument.rbNew(ruby.getCurrentContext(), getNokogiriClass(ruby, "Nokogiri::XML::Document"), new IRubyObject[0]); for (int i=0; i keys = namespaces.keySet(); for (String key : keys) { String stringValue = namespaces.get(key); IRubyObject k = stringOrBlank(context.getRuntime(), key); IRubyObject v = stringOrBlank(context.getRuntime(), stringValue); if (context.getRuntime().is1_9()) hash.op_aset19(context, k, v); else hash.op_aset(context, k, v); } return hash; } public IRubyObject getXmlBase() { return stringOrNil(ruby, xmlBase); } public IRubyObject getPrefix() { return stringOrNil(ruby, prefix); } public IRubyObject getUri() { return stringOrNil(ruby, uri); } public IRubyObject getValue() { return stringOrNil(ruby, value); } public IRubyObject getXmlVersion() { return ruby.newString(xmlVersion); } public RubyBoolean hasAttributes() { if (attributeList == null || attributeList.length == 0) return ruby.getFalse(); return ruby.getTrue(); } public abstract RubyBoolean hasValue(); public RubyBoolean isDefault(){ // TODO Implement. return ruby.getFalse(); } public boolean isError() { return false; } protected void parsePrefix(String qName) { int index = qName.indexOf(':'); if(index != -1) prefix = qName.substring(0, index); } public void setLang(String lang) { lang = (lang != null) ? lang : null; } public IRubyObject toSyntaxError() { return ruby.getNil(); } public IRubyObject getNodeType() { return ruby.newFixnum(nodeType); } public static enum ReaderNodeType { NODE(0), ELEMENT(1), ATTRIBUTE(2), TEXT(3), CDATA(4), ENTITY_REFERENCE(5), ENTITY(6), PROCESSING_INSTRUCTION(7), COMMENT(8), DOCUMENT(9), DOCUMENT_TYPE(10), DOCUMENTFRAGMENT(11), NOTATION(12), WHITESPACE(13), SIGNIFICANT_WHITESPACE(14), END_ELEMENT(15), END_ENTITY(16), XML_DECLARATION(17); private final int value; ReaderNodeType(int value) { this.value = value; } public int getValue() { return value; } } public static class ClosingNode extends ReaderNode { public ClosingNode(Ruby ruby, String uri, String localName, String qName, int depth, Stack langStack, Stack xmlBaseStack) { this.ruby = ruby; nodeType = ReaderNodeType.END_ELEMENT.getValue(); this.uri = "".equals(uri) ? null : uri; this.localName = localName.trim().length() > 0 ? localName : qName; this.name = qName; parsePrefix(qName); this.depth = depth; if (!langStack.isEmpty()) this.lang = langStack.peek(); if (!xmlBaseStack.isEmpty()) this.xmlBase = xmlBaseStack.peek(); } @Override public IRubyObject getAttributeCount() { return ruby.newFixnum(0); } @Override public RubyBoolean hasValue() { return ruby.getFalse(); } @Override public String getString() { StringBuffer sb = new StringBuffer(); sb.append(""); return new String(sb); } } public static class ElementNode extends ReaderNode { private List attributeStrings = new ArrayList(); public ElementNode(Ruby ruby, String uri, String localName, String qName, Attributes attrs, int depth, Stack langStack, Stack xmlBaseStack) { this.ruby = ruby; this.nodeType = ReaderNodeType.ELEMENT.getValue(); this.uri = "".equals(uri) ? null : uri; this.localName = localName.trim().length() > 0 ? localName : qName; this.name = qName; parsePrefix(qName); this.depth = depth; hasChildren = true; parseAttributes(attrs, langStack, xmlBaseStack); } @Override public RubyBoolean hasValue() { return ruby.getFalse(); } private void parseAttributes(Attributes attrs, Stack langStack, Stack xmlBaseStack) { if (attrs.getLength() > 0) attributeList = new ReaderAttributeList(); String u, n, v; for (int i = 0; i < attrs.getLength(); i++) { u = attrs.getURI(i); n = attrs.getQName(i); v = attrs.getValue(i); if (isNamespace(n)) { if (namespaces == null) namespaces = new HashMap(); namespaces.put(n, v); } else { if (lang == null) lang = resolveLang(n, v, langStack); if (xmlBase == null) xmlBase = resolveXmlBase(n, v, xmlBaseStack); } attributeList.add(u, n, v); attributeStrings.add(n + "=\"" + v + "\""); } } private String resolveLang(String n, String v, Stack langStack) { if ("xml:lang".equals(n)) { return v; } else if (!langStack.isEmpty()) { return langStack.peek(); } else { return null; } } private String resolveXmlBase(String n, String v, Stack xmlBaseStack) { if (isXmlBase(n)) { return getXmlBaseUri(n, v, xmlBaseStack); } else if (!xmlBaseStack.isEmpty()) { return xmlBaseStack.peek(); } else { return null; } } private String getXmlBaseUri(String n, String v, Stack xmlBaseStack) { if ("xml:base".equals(n)) { if (v.startsWith("http://")) { return v; } else if (v.startsWith("/") && v.endsWith("/")) { String sub = v.substring(1, v.length() - 2); String base = xmlBaseStack.peek(); if (base.endsWith("/")) { base = base.substring(0, base.length() - 1); } int pos = base.lastIndexOf("/"); return base.substring(0, pos).concat(sub); } else { String base = xmlBaseStack.peek(); if (base.endsWith("/")) return base.concat(v); else return base.concat("/").concat(v); } } else if ("xlink:href".equals(n)) { String base = xmlBaseStack.peek(); if (base.endsWith("/")) return base.concat(v); else return base.concat("/").concat(v); } return null; } @Override public String getString() { StringBuffer sb = new StringBuffer(); sb.append("<").append(name); if (attributeList != null) { for (int i=0; i"); else sb.append("/>"); return new String(sb); } } public static class ReaderAttributeList { List namespaces = new ArrayList(); List names = new ArrayList(); List values = new ArrayList(); int length = 0; void add(String namespace, String name, String value) { namespace = namespace != null ? namespace : ""; namespaces.add(namespace); name = name != null ? name : ""; names.add(name); value = value != null ? value : ""; values.add(value); length++; } String getByName(String name) { for (int i=0; i langStack, Stack xmlBaseStack) { this.ruby = ruby; this.value = content; this.localName = "#text"; this.name = "#text"; this.depth = depth; if (content.trim().length() > 0) nodeType = ReaderNodeType.TEXT.getValue(); else nodeType = ReaderNodeType.SIGNIFICANT_WHITESPACE.getValue(); if (!langStack.isEmpty()) this.lang = langStack.peek(); if (!xmlBaseStack.isEmpty()) this.xmlBase = xmlBaseStack.peek(); } @Override public RubyBoolean hasValue() { return ruby.getTrue(); } @Override public String getString() { return value; } } }