nokogiri-backport 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (239) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE-DEPENDENCIES.md +1682 -0
  4. data/LICENSE.md +9 -0
  5. data/README.md +272 -0
  6. data/bin/nokogiri +118 -0
  7. data/dependencies.yml +74 -0
  8. data/ext/java/nokogiri/EncodingHandler.java +124 -0
  9. data/ext/java/nokogiri/HtmlDocument.java +178 -0
  10. data/ext/java/nokogiri/HtmlElementDescription.java +148 -0
  11. data/ext/java/nokogiri/HtmlEntityLookup.java +79 -0
  12. data/ext/java/nokogiri/HtmlSaxParserContext.java +282 -0
  13. data/ext/java/nokogiri/HtmlSaxPushParser.java +222 -0
  14. data/ext/java/nokogiri/NokogiriService.java +597 -0
  15. data/ext/java/nokogiri/XmlAttr.java +162 -0
  16. data/ext/java/nokogiri/XmlAttributeDecl.java +129 -0
  17. data/ext/java/nokogiri/XmlCdata.java +82 -0
  18. data/ext/java/nokogiri/XmlComment.java +97 -0
  19. data/ext/java/nokogiri/XmlDocument.java +633 -0
  20. data/ext/java/nokogiri/XmlDocumentFragment.java +185 -0
  21. data/ext/java/nokogiri/XmlDtd.java +481 -0
  22. data/ext/java/nokogiri/XmlElement.java +68 -0
  23. data/ext/java/nokogiri/XmlElementContent.java +382 -0
  24. data/ext/java/nokogiri/XmlElementDecl.java +147 -0
  25. data/ext/java/nokogiri/XmlEntityDecl.java +157 -0
  26. data/ext/java/nokogiri/XmlEntityReference.java +101 -0
  27. data/ext/java/nokogiri/XmlNamespace.java +199 -0
  28. data/ext/java/nokogiri/XmlNode.java +1684 -0
  29. data/ext/java/nokogiri/XmlNodeSet.java +434 -0
  30. data/ext/java/nokogiri/XmlProcessingInstruction.java +100 -0
  31. data/ext/java/nokogiri/XmlReader.java +531 -0
  32. data/ext/java/nokogiri/XmlRelaxng.java +151 -0
  33. data/ext/java/nokogiri/XmlSaxParserContext.java +374 -0
  34. data/ext/java/nokogiri/XmlSaxPushParser.java +286 -0
  35. data/ext/java/nokogiri/XmlSchema.java +388 -0
  36. data/ext/java/nokogiri/XmlSyntaxError.java +138 -0
  37. data/ext/java/nokogiri/XmlText.java +110 -0
  38. data/ext/java/nokogiri/XmlXpathContext.java +301 -0
  39. data/ext/java/nokogiri/XsltStylesheet.java +347 -0
  40. data/ext/java/nokogiri/internals/ClosedStreamException.java +10 -0
  41. data/ext/java/nokogiri/internals/HtmlDomParserContext.java +252 -0
  42. data/ext/java/nokogiri/internals/IgnoreSchemaErrorsErrorHandler.java +20 -0
  43. data/ext/java/nokogiri/internals/NokogiriBlockingQueueInputStream.java +151 -0
  44. data/ext/java/nokogiri/internals/NokogiriDomParser.java +116 -0
  45. data/ext/java/nokogiri/internals/NokogiriEntityResolver.java +121 -0
  46. data/ext/java/nokogiri/internals/NokogiriErrorHandler.java +69 -0
  47. data/ext/java/nokogiri/internals/NokogiriHandler.java +327 -0
  48. data/ext/java/nokogiri/internals/NokogiriHelpers.java +734 -0
  49. data/ext/java/nokogiri/internals/NokogiriNamespaceCache.java +217 -0
  50. data/ext/java/nokogiri/internals/NokogiriNamespaceContext.java +127 -0
  51. data/ext/java/nokogiri/internals/NokogiriNonStrictErrorHandler.java +100 -0
  52. data/ext/java/nokogiri/internals/NokogiriNonStrictErrorHandler4NekoHtml.java +121 -0
  53. data/ext/java/nokogiri/internals/NokogiriStrictErrorHandler.java +78 -0
  54. data/ext/java/nokogiri/internals/NokogiriXPathFunction.java +180 -0
  55. data/ext/java/nokogiri/internals/NokogiriXPathFunctionResolver.java +72 -0
  56. data/ext/java/nokogiri/internals/NokogiriXPathVariableResolver.java +60 -0
  57. data/ext/java/nokogiri/internals/NokogiriXsltErrorListener.java +87 -0
  58. data/ext/java/nokogiri/internals/ParserContext.java +259 -0
  59. data/ext/java/nokogiri/internals/ReaderNode.java +488 -0
  60. data/ext/java/nokogiri/internals/SaveContextVisitor.java +778 -0
  61. data/ext/java/nokogiri/internals/SchemaErrorHandler.java +73 -0
  62. data/ext/java/nokogiri/internals/XalanDTMManagerPatch.java +168 -0
  63. data/ext/java/nokogiri/internals/XmlDeclHandler.java +42 -0
  64. data/ext/java/nokogiri/internals/XmlDomParserContext.java +274 -0
  65. data/ext/java/nokogiri/internals/XmlSaxParser.java +65 -0
  66. data/ext/java/nokogiri/internals/c14n/AttrCompare.java +119 -0
  67. data/ext/java/nokogiri/internals/c14n/C14nHelper.java +159 -0
  68. data/ext/java/nokogiri/internals/c14n/CanonicalFilter.java +37 -0
  69. data/ext/java/nokogiri/internals/c14n/CanonicalizationException.java +93 -0
  70. data/ext/java/nokogiri/internals/c14n/Canonicalizer.java +252 -0
  71. data/ext/java/nokogiri/internals/c14n/Canonicalizer11.java +639 -0
  72. data/ext/java/nokogiri/internals/c14n/Canonicalizer11_OmitComments.java +38 -0
  73. data/ext/java/nokogiri/internals/c14n/Canonicalizer11_WithComments.java +38 -0
  74. data/ext/java/nokogiri/internals/c14n/Canonicalizer20010315.java +367 -0
  75. data/ext/java/nokogiri/internals/c14n/Canonicalizer20010315Excl.java +295 -0
  76. data/ext/java/nokogiri/internals/c14n/Canonicalizer20010315ExclOmitComments.java +40 -0
  77. data/ext/java/nokogiri/internals/c14n/Canonicalizer20010315ExclWithComments.java +44 -0
  78. data/ext/java/nokogiri/internals/c14n/Canonicalizer20010315OmitComments.java +44 -0
  79. data/ext/java/nokogiri/internals/c14n/Canonicalizer20010315WithComments.java +43 -0
  80. data/ext/java/nokogiri/internals/c14n/CanonicalizerBase.java +630 -0
  81. data/ext/java/nokogiri/internals/c14n/CanonicalizerPhysical.java +173 -0
  82. data/ext/java/nokogiri/internals/c14n/CanonicalizerSpi.java +76 -0
  83. data/ext/java/nokogiri/internals/c14n/Constants.java +42 -0
  84. data/ext/java/nokogiri/internals/c14n/ElementProxy.java +293 -0
  85. data/ext/java/nokogiri/internals/c14n/HelperNodeList.java +93 -0
  86. data/ext/java/nokogiri/internals/c14n/IgnoreAllErrorHandler.java +79 -0
  87. data/ext/java/nokogiri/internals/c14n/InclusiveNamespaces.java +166 -0
  88. data/ext/java/nokogiri/internals/c14n/InvalidCanonicalizerException.java +76 -0
  89. data/ext/java/nokogiri/internals/c14n/NameSpaceSymbTable.java +402 -0
  90. data/ext/java/nokogiri/internals/c14n/NodeFilter.java +51 -0
  91. data/ext/java/nokogiri/internals/c14n/UtfHelpper.java +179 -0
  92. data/ext/java/nokogiri/internals/c14n/XMLUtils.java +507 -0
  93. data/ext/java/nokogiri/internals/dom2dtm/DOM2DTM.java +1745 -0
  94. data/ext/java/nokogiri/internals/dom2dtm/DOM2DTMdefaultNamespaceDeclarationNode.java +685 -0
  95. data/ext/nokogiri/depend +477 -0
  96. data/ext/nokogiri/extconf.rb +836 -0
  97. data/ext/nokogiri/html_document.c +171 -0
  98. data/ext/nokogiri/html_document.h +10 -0
  99. data/ext/nokogiri/html_element_description.c +279 -0
  100. data/ext/nokogiri/html_element_description.h +10 -0
  101. data/ext/nokogiri/html_entity_lookup.c +32 -0
  102. data/ext/nokogiri/html_entity_lookup.h +8 -0
  103. data/ext/nokogiri/html_sax_parser_context.c +116 -0
  104. data/ext/nokogiri/html_sax_parser_context.h +11 -0
  105. data/ext/nokogiri/html_sax_push_parser.c +87 -0
  106. data/ext/nokogiri/html_sax_push_parser.h +9 -0
  107. data/ext/nokogiri/nokogiri.c +135 -0
  108. data/ext/nokogiri/nokogiri.h +130 -0
  109. data/ext/nokogiri/xml_attr.c +103 -0
  110. data/ext/nokogiri/xml_attr.h +9 -0
  111. data/ext/nokogiri/xml_attribute_decl.c +70 -0
  112. data/ext/nokogiri/xml_attribute_decl.h +9 -0
  113. data/ext/nokogiri/xml_cdata.c +62 -0
  114. data/ext/nokogiri/xml_cdata.h +9 -0
  115. data/ext/nokogiri/xml_comment.c +69 -0
  116. data/ext/nokogiri/xml_comment.h +9 -0
  117. data/ext/nokogiri/xml_document.c +622 -0
  118. data/ext/nokogiri/xml_document.h +23 -0
  119. data/ext/nokogiri/xml_document_fragment.c +48 -0
  120. data/ext/nokogiri/xml_document_fragment.h +10 -0
  121. data/ext/nokogiri/xml_dtd.c +202 -0
  122. data/ext/nokogiri/xml_dtd.h +10 -0
  123. data/ext/nokogiri/xml_element_content.c +123 -0
  124. data/ext/nokogiri/xml_element_content.h +10 -0
  125. data/ext/nokogiri/xml_element_decl.c +69 -0
  126. data/ext/nokogiri/xml_element_decl.h +9 -0
  127. data/ext/nokogiri/xml_encoding_handler.c +79 -0
  128. data/ext/nokogiri/xml_encoding_handler.h +8 -0
  129. data/ext/nokogiri/xml_entity_decl.c +110 -0
  130. data/ext/nokogiri/xml_entity_decl.h +10 -0
  131. data/ext/nokogiri/xml_entity_reference.c +52 -0
  132. data/ext/nokogiri/xml_entity_reference.h +9 -0
  133. data/ext/nokogiri/xml_io.c +63 -0
  134. data/ext/nokogiri/xml_io.h +11 -0
  135. data/ext/nokogiri/xml_libxml2_hacks.c +112 -0
  136. data/ext/nokogiri/xml_libxml2_hacks.h +12 -0
  137. data/ext/nokogiri/xml_namespace.c +111 -0
  138. data/ext/nokogiri/xml_namespace.h +14 -0
  139. data/ext/nokogiri/xml_node.c +1773 -0
  140. data/ext/nokogiri/xml_node.h +13 -0
  141. data/ext/nokogiri/xml_node_set.c +486 -0
  142. data/ext/nokogiri/xml_node_set.h +12 -0
  143. data/ext/nokogiri/xml_processing_instruction.c +56 -0
  144. data/ext/nokogiri/xml_processing_instruction.h +9 -0
  145. data/ext/nokogiri/xml_reader.c +657 -0
  146. data/ext/nokogiri/xml_reader.h +10 -0
  147. data/ext/nokogiri/xml_relax_ng.c +179 -0
  148. data/ext/nokogiri/xml_relax_ng.h +9 -0
  149. data/ext/nokogiri/xml_sax_parser.c +305 -0
  150. data/ext/nokogiri/xml_sax_parser.h +39 -0
  151. data/ext/nokogiri/xml_sax_parser_context.c +262 -0
  152. data/ext/nokogiri/xml_sax_parser_context.h +10 -0
  153. data/ext/nokogiri/xml_sax_push_parser.c +159 -0
  154. data/ext/nokogiri/xml_sax_push_parser.h +9 -0
  155. data/ext/nokogiri/xml_schema.c +276 -0
  156. data/ext/nokogiri/xml_schema.h +9 -0
  157. data/ext/nokogiri/xml_syntax_error.c +64 -0
  158. data/ext/nokogiri/xml_syntax_error.h +13 -0
  159. data/ext/nokogiri/xml_text.c +52 -0
  160. data/ext/nokogiri/xml_text.h +9 -0
  161. data/ext/nokogiri/xml_xpath_context.c +374 -0
  162. data/ext/nokogiri/xml_xpath_context.h +10 -0
  163. data/ext/nokogiri/xslt_stylesheet.c +263 -0
  164. data/ext/nokogiri/xslt_stylesheet.h +14 -0
  165. data/lib/isorelax.jar +0 -0
  166. data/lib/jing.jar +0 -0
  167. data/lib/nekodtd.jar +0 -0
  168. data/lib/nekohtml.jar +0 -0
  169. data/lib/nokogiri/css/node.rb +53 -0
  170. data/lib/nokogiri/css/parser.rb +751 -0
  171. data/lib/nokogiri/css/parser.y +272 -0
  172. data/lib/nokogiri/css/parser_extras.rb +94 -0
  173. data/lib/nokogiri/css/syntax_error.rb +8 -0
  174. data/lib/nokogiri/css/tokenizer.rb +154 -0
  175. data/lib/nokogiri/css/tokenizer.rex +55 -0
  176. data/lib/nokogiri/css/xpath_visitor.rb +260 -0
  177. data/lib/nokogiri/css.rb +28 -0
  178. data/lib/nokogiri/decorators/slop.rb +43 -0
  179. data/lib/nokogiri/html/builder.rb +36 -0
  180. data/lib/nokogiri/html/document.rb +322 -0
  181. data/lib/nokogiri/html/document_fragment.rb +50 -0
  182. data/lib/nokogiri/html/element_description.rb +24 -0
  183. data/lib/nokogiri/html/element_description_defaults.rb +672 -0
  184. data/lib/nokogiri/html/entity_lookup.rb +14 -0
  185. data/lib/nokogiri/html/sax/parser.rb +63 -0
  186. data/lib/nokogiri/html/sax/parser_context.rb +17 -0
  187. data/lib/nokogiri/html/sax/push_parser.rb +37 -0
  188. data/lib/nokogiri/html.rb +38 -0
  189. data/lib/nokogiri/jruby/dependencies.rb +20 -0
  190. data/lib/nokogiri/syntax_error.rb +5 -0
  191. data/lib/nokogiri/version/constant.rb +5 -0
  192. data/lib/nokogiri/version/info.rb +182 -0
  193. data/lib/nokogiri/version.rb +3 -0
  194. data/lib/nokogiri/xml/attr.rb +15 -0
  195. data/lib/nokogiri/xml/attribute_decl.rb +19 -0
  196. data/lib/nokogiri/xml/builder.rb +447 -0
  197. data/lib/nokogiri/xml/cdata.rb +12 -0
  198. data/lib/nokogiri/xml/character_data.rb +8 -0
  199. data/lib/nokogiri/xml/document.rb +290 -0
  200. data/lib/nokogiri/xml/document_fragment.rb +159 -0
  201. data/lib/nokogiri/xml/dtd.rb +33 -0
  202. data/lib/nokogiri/xml/element_content.rb +37 -0
  203. data/lib/nokogiri/xml/element_decl.rb +14 -0
  204. data/lib/nokogiri/xml/entity_decl.rb +20 -0
  205. data/lib/nokogiri/xml/entity_reference.rb +19 -0
  206. data/lib/nokogiri/xml/namespace.rb +14 -0
  207. data/lib/nokogiri/xml/node/save_options.rb +62 -0
  208. data/lib/nokogiri/xml/node.rb +1240 -0
  209. data/lib/nokogiri/xml/node_set.rb +372 -0
  210. data/lib/nokogiri/xml/notation.rb +7 -0
  211. data/lib/nokogiri/xml/parse_options.rb +127 -0
  212. data/lib/nokogiri/xml/pp/character_data.rb +19 -0
  213. data/lib/nokogiri/xml/pp/node.rb +57 -0
  214. data/lib/nokogiri/xml/pp.rb +3 -0
  215. data/lib/nokogiri/xml/processing_instruction.rb +9 -0
  216. data/lib/nokogiri/xml/reader.rb +116 -0
  217. data/lib/nokogiri/xml/relax_ng.rb +37 -0
  218. data/lib/nokogiri/xml/sax/document.rb +172 -0
  219. data/lib/nokogiri/xml/sax/parser.rb +123 -0
  220. data/lib/nokogiri/xml/sax/parser_context.rb +17 -0
  221. data/lib/nokogiri/xml/sax/push_parser.rb +61 -0
  222. data/lib/nokogiri/xml/sax.rb +5 -0
  223. data/lib/nokogiri/xml/schema.rb +72 -0
  224. data/lib/nokogiri/xml/searchable.rb +239 -0
  225. data/lib/nokogiri/xml/syntax_error.rb +71 -0
  226. data/lib/nokogiri/xml/text.rb +10 -0
  227. data/lib/nokogiri/xml/xpath/syntax_error.rb +12 -0
  228. data/lib/nokogiri/xml/xpath.rb +11 -0
  229. data/lib/nokogiri/xml/xpath_context.rb +17 -0
  230. data/lib/nokogiri/xml.rb +76 -0
  231. data/lib/nokogiri/xslt/stylesheet.rb +26 -0
  232. data/lib/nokogiri/xslt.rb +57 -0
  233. data/lib/nokogiri.rb +144 -0
  234. data/lib/serializer.jar +0 -0
  235. data/lib/xalan.jar +0 -0
  236. data/lib/xercesImpl.jar +0 -0
  237. data/lib/xml-apis.jar +0 -0
  238. data/lib/xsd/xmlparser/nokogiri.rb +103 -0
  239. metadata +531 -0
@@ -0,0 +1,1684 @@
1
+ /**
2
+ * (The MIT License)
3
+ *
4
+ * Copyright (c) 2008 - 2014:
5
+ *
6
+ * * {Aaron Patterson}[http://tenderlovemaking.com]
7
+ * * {Mike Dalessio}[http://mike.daless.io]
8
+ * * {Charles Nutter}[http://blog.headius.com]
9
+ * * {Sergio Arbeo}[http://www.serabe.com]
10
+ * * {Patrick Mahoney}[http://polycrystal.org]
11
+ * * {Yoko Harada}[http://yokolet.blogspot.com]
12
+ *
13
+ * Permission is hereby granted, free of charge, to any person obtaining
14
+ * a copy of this software and associated documentation files (the
15
+ * 'Software'), to deal in the Software without restriction, including
16
+ * without limitation the rights to use, copy, modify, merge, publish,
17
+ * distribute, sublicense, and/or sell copies of the Software, and to
18
+ * permit persons to whom the Software is furnished to do so, subject to
19
+ * the following conditions:
20
+ *
21
+ * The above copyright notice and this permission notice shall be
22
+ * included in all copies or substantial portions of the Software.
23
+ *
24
+ * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
25
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
27
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
28
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
29
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
30
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ */
32
+
33
+ package nokogiri;
34
+
35
+ import static java.lang.Math.max;
36
+ import static nokogiri.internals.NokogiriHelpers.*;
37
+
38
+ import java.io.ByteArrayInputStream;
39
+ import java.io.InputStream;
40
+ import java.nio.ByteBuffer;
41
+ import java.nio.charset.Charset;
42
+ import java.util.*;
43
+
44
+ import org.apache.xerces.dom.CoreDocumentImpl;
45
+ import org.jruby.Ruby;
46
+ import org.jruby.RubyArray;
47
+ import org.jruby.RubyClass;
48
+ import org.jruby.RubyFixnum;
49
+ import org.jruby.RubyInteger;
50
+ import org.jruby.RubyModule;
51
+ import org.jruby.RubyObject;
52
+ import org.jruby.RubyString;
53
+ import org.jruby.anno.JRubyClass;
54
+ import org.jruby.anno.JRubyMethod;
55
+ import org.jruby.exceptions.RaiseException;
56
+ import org.jruby.runtime.Block;
57
+ import org.jruby.runtime.Helpers;
58
+ import org.jruby.runtime.ThreadContext;
59
+ import org.jruby.runtime.Visibility;
60
+ import org.jruby.runtime.builtin.IRubyObject;
61
+ import org.jruby.util.ByteList;
62
+ import org.w3c.dom.Attr;
63
+ import org.w3c.dom.Document;
64
+ import org.w3c.dom.DocumentFragment;
65
+ import org.w3c.dom.Element;
66
+ import org.w3c.dom.NamedNodeMap;
67
+ import org.w3c.dom.Node;
68
+ import org.w3c.dom.NodeList;
69
+ import org.w3c.dom.Text;
70
+
71
+ import nokogiri.internals.HtmlDomParserContext;
72
+ import nokogiri.internals.NokogiriHelpers;
73
+ import nokogiri.internals.NokogiriNamespaceCache;
74
+ import nokogiri.internals.SaveContextVisitor;
75
+ import nokogiri.internals.XmlDomParserContext;
76
+
77
+ /**
78
+ * Class for Nokogiri::XML::Node
79
+ *
80
+ * @author sergio
81
+ * @author Patrick Mahoney <pat@polycrystal.org>
82
+ * @author Yoko Harada <yokolet@gmail.com>
83
+ * @author John Shahid <jvshahid@gmail.com>
84
+ */
85
+ @JRubyClass(name="Nokogiri::XML::Node")
86
+ public class XmlNode extends RubyObject {
87
+ protected static final String TEXT_WRAPPER_NAME = "nokogiri_text_wrapper";
88
+
89
+ /** The underlying Node object. */
90
+ protected Node node;
91
+
92
+ /* Cached objects */
93
+ protected IRubyObject content = null;
94
+ private transient XmlDocument doc;
95
+ protected transient RubyString name;
96
+
97
+ /*
98
+ * Taken from http://ejohn.org/blog/comparing-document-position/
99
+ * Used for compareDocumentPosition.
100
+ * <ironic>Thanks to both java api and w3 doc for its helpful documentation</ironic>
101
+ */
102
+
103
+ protected static final int IDENTICAL_ELEMENTS = 0;
104
+ protected static final int IN_DIFFERENT_DOCUMENTS = 1;
105
+ protected static final int SECOND_PRECEDES_FIRST = 2;
106
+ protected static final int FIRST_PRECEDES_SECOND = 4;
107
+ protected static final int SECOND_CONTAINS_FIRST = 8;
108
+ protected static final int FIRST_CONTAINS_SECOND = 16;
109
+
110
+ /**
111
+ * Cast <code>node</code> to an XmlNode or raise a type error
112
+ * in <code>context</code>.
113
+ */
114
+ protected static XmlNode asXmlNode(ThreadContext context, IRubyObject node) {
115
+ if ( !(node instanceof XmlNode) ) {
116
+ final Ruby runtime = context.runtime;
117
+ throw runtime.newTypeError(node == null ? runtime.getNil() : node, getNokogiriClass(runtime, "Nokogiri::XML::Node"));
118
+ }
119
+ return (XmlNode) node;
120
+ }
121
+
122
+ /**
123
+ * Cast <code>node</code> to an XmlNode, or null if RubyNil, or
124
+ * raise a type error in <code>context</code>.
125
+ */
126
+ protected static XmlNode asXmlNodeOrNull(ThreadContext context, IRubyObject node) {
127
+ if (node == null || node.isNil()) return null;
128
+ return asXmlNode(context, node);
129
+ }
130
+
131
+ /**
132
+ * Coalesce to adjacent TextNodes.
133
+ * @param context
134
+ * @param prev Previous node to cur.
135
+ * @param cur Next node to prev.
136
+ */
137
+ public static void coalesceTextNodes(ThreadContext context, IRubyObject prev, IRubyObject cur) {
138
+ XmlNode p = asXmlNode(context, prev);
139
+ XmlNode c = asXmlNode(context, cur);
140
+
141
+ Node pNode = p.node;
142
+ Node cNode = c.node;
143
+
144
+ pNode.setNodeValue(pNode.getNodeValue()+cNode.getNodeValue());
145
+ p.content = null; // clear cached content
146
+
147
+ c.assimilateXmlNode(context, p);
148
+ }
149
+
150
+ /**
151
+ * Coalesce text nodes around <code>anchorNode</code>. If
152
+ * <code>anchorNode</code> has siblings (previous or next) that
153
+ * are text nodes, the content will be merged into
154
+ * <code>anchorNode</code> and the redundant nodes will be removed
155
+ * from the DOM.
156
+ *
157
+ * To match libxml behavior (?) the final content of
158
+ * <code>anchorNode</code> and any removed nodes will be
159
+ * identical.
160
+ *
161
+ * @param context
162
+ * @param anchorNode
163
+ */
164
+ protected static void coalesceTextNodes(ThreadContext context,
165
+ IRubyObject anchorNode,
166
+ AdoptScheme scheme) {
167
+ XmlNode xa = asXmlNode(context, anchorNode);
168
+
169
+ XmlNode xp = asXmlNodeOrNull(context, xa.previous_sibling(context));
170
+ XmlNode xn = asXmlNodeOrNull(context, xa.next_sibling(context));
171
+
172
+ Node p = xp == null ? null : xp.node;
173
+ Node a = xa.node;
174
+ Node n = xn == null ? null : xn.node;
175
+
176
+ Node parent = a.getParentNode();
177
+
178
+ boolean shouldMergeP = scheme == AdoptScheme.NEXT_SIBLING || scheme == AdoptScheme.CHILD || scheme == AdoptScheme.REPLACEMENT;
179
+ boolean shouldMergeN = scheme == AdoptScheme.PREV_SIBLING || scheme == AdoptScheme.REPLACEMENT;
180
+
181
+ // apply the merge right to left
182
+ if (shouldMergeN && n != null && n.getNodeType() == Node.TEXT_NODE) {
183
+ xa.setContent(a.getNodeValue() + n.getNodeValue());
184
+ parent.removeChild(n);
185
+ xn.assimilateXmlNode(context, xa);
186
+ }
187
+ if (shouldMergeP && p != null && p.getNodeType() == Node.TEXT_NODE) {
188
+ xp.setContent(p.getNodeValue() + a.getNodeValue());
189
+ parent.removeChild(a);
190
+ xa.assimilateXmlNode(context, xp);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * This is the allocator for XmlNode class. It should only be
196
+ * called from Ruby code.
197
+ */
198
+ public XmlNode(Ruby runtime, RubyClass klass) {
199
+ super(runtime, klass);
200
+ }
201
+
202
+ /**
203
+ * This is a constructor to create an XmlNode from an already
204
+ * existing node. It may be called by Java code.
205
+ */
206
+ public XmlNode(Ruby runtime, RubyClass klass, Node node) {
207
+ super(runtime, klass);
208
+ setNode(runtime, node);
209
+ }
210
+
211
+ protected void decorate(final Ruby runtime) {
212
+ if (node != null) {
213
+ resetCache();
214
+
215
+ if (node.getNodeType() != Node.DOCUMENT_NODE) {
216
+ setDocumentAndDecorate(runtime.getCurrentContext(), this, document(runtime));
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Create and return a copy of this object.
223
+ *
224
+ * @return a clone of this object
225
+ */
226
+ @Override
227
+ public Object clone() throws CloneNotSupportedException {
228
+ return super.clone();
229
+ }
230
+
231
+ protected void resetCache() {
232
+ node.setUserData(NokogiriHelpers.CACHED_NODE, this, null);
233
+ }
234
+
235
+ /**
236
+ * Allocate a new object, perform initialization, call that
237
+ * object's initialize method, and call any block passing the
238
+ * object as the only argument. If <code>cls</code> is
239
+ * Nokogiri::XML::Node, creates a new Nokogiri::XML::Element
240
+ * instead.
241
+ *
242
+ * This static method seems to be inherited, strangely enough.
243
+ * E.g. creating a new XmlAttr from Ruby code calls this method if
244
+ * XmlAttr does not define its own 'new' method.
245
+ *
246
+ * Since there is some Java bookkeeping that always needs to
247
+ * happen, we don't define the 'initialize' method in Java because
248
+ * we'd have to count on subclasses calling 'super'.
249
+ *
250
+ * The main consequence of this is that every subclass needs to
251
+ * define its own 'new' method.
252
+ *
253
+ * As a convenience, this method does the following:
254
+ *
255
+ * <ul>
256
+ *
257
+ * <li>allocates a new object using the allocator assigned to
258
+ * <code>cls</code></li>
259
+ *
260
+ * <li>calls the Java method init(); subclasses can override this,
261
+ * otherwise they should implement a specific 'new' method</li>
262
+ *
263
+ * <li>invokes the Ruby initializer</li>
264
+ *
265
+ * <li>if a block is given, calls the block with the new node as
266
+ * the argument</li>
267
+ *
268
+ * </ul>
269
+ *
270
+ * -pmahoney
271
+ */
272
+ @JRubyMethod(name = "new", meta = true, rest = true)
273
+ public static IRubyObject rbNew(ThreadContext context, IRubyObject cls,
274
+ IRubyObject[] args, Block block) {
275
+ Ruby ruby = context.runtime;
276
+ RubyClass klazz = (RubyClass) cls;
277
+
278
+ if ("Nokogiri::XML::Node".equals(klazz.getName())) {
279
+ klazz = getNokogiriClass(ruby, "Nokogiri::XML::Element");
280
+ }
281
+
282
+ XmlNode xmlNode = (XmlNode) klazz.allocate();
283
+ xmlNode.init(context, args);
284
+ xmlNode.callInit(args, block);
285
+ assert xmlNode.node != null;
286
+ if (block.isGiven()) block.call(context, xmlNode);
287
+ return xmlNode;
288
+ }
289
+
290
+ /**
291
+ * Initialize the object from Ruby arguments. Should be
292
+ * overridden by subclasses. Should check for a minimum number of
293
+ * args but not for an exact number. Any extra args will then be
294
+ * passed to 'initialize'. The way 'new' and this 'init' function
295
+ * interact means that subclasses cannot arbitrarily change the
296
+ * require aruments by defining an 'initialize' method. This is
297
+ * how the C libxml wrapper works also.
298
+ *
299
+ * As written it performs initialization for a new Element with
300
+ * the given <code>name</code> within the document
301
+ * <code>doc</code>. So XmlElement need not override this. This
302
+ * implementation cannot be moved to XmlElement however, because
303
+ * subclassing XmlNode must result in something that behaves much
304
+ * like XmlElement.
305
+ */
306
+ protected void init(ThreadContext context, IRubyObject[] args) {
307
+ if (args.length < 2)
308
+ throw context.runtime.newArgumentError(args.length, 2);
309
+
310
+ IRubyObject name = args[0];
311
+ IRubyObject doc = args[1];
312
+
313
+ Document document = asXmlNode(context, doc).getOwnerDocument();
314
+ if (document == null) {
315
+ throw context.runtime.newArgumentError("node must have owner document");
316
+ }
317
+
318
+ Element element;
319
+ String node_name = rubyStringToString(name);
320
+ String prefix = NokogiriHelpers.getPrefix(node_name);
321
+ String namespace_uri = null;
322
+ if (document.getDocumentElement() != null) {
323
+ namespace_uri = document.getDocumentElement().lookupNamespaceURI(prefix);
324
+ }
325
+ element = document.createElementNS(namespace_uri, node_name);
326
+ setNode(context.runtime, element);
327
+ }
328
+
329
+ /**
330
+ * Set the underlying node of this node to the underlying node of
331
+ * <code>otherNode</code>.
332
+ *
333
+ * FIXME: also update the cached node?
334
+ */
335
+ protected void assimilateXmlNode(ThreadContext context, IRubyObject otherNode) {
336
+ XmlNode toAssimilate = asXmlNode(context, otherNode);
337
+
338
+ this.node = toAssimilate.node;
339
+ content = null; // clear cache
340
+ }
341
+
342
+ /**
343
+ * See org.w3.dom.Node#normalize.
344
+ */
345
+ public void normalize() {
346
+ node.normalize();
347
+ }
348
+
349
+ public Node getNode() {
350
+ return node;
351
+ }
352
+
353
+ public boolean isComment() { return false; }
354
+
355
+ public boolean isElement() {
356
+ if (node instanceof Element) return true; // in case of subclassing
357
+ else return false;
358
+ }
359
+
360
+ public boolean isProcessingInstruction() { return false; }
361
+
362
+ /**
363
+ * Return the string value of the attribute <code>key</code> or
364
+ * nil.
365
+ *
366
+ * Only applies where the underlying Node is an Element node, but
367
+ * implemented here in XmlNode because not all nodes with
368
+ * underlying Element nodes subclass XmlElement, such as the DTD
369
+ * declarations like XmlElementDecl.
370
+ */
371
+ protected IRubyObject getAttribute(ThreadContext context, String key) {
372
+ return getAttribute(context.runtime, key);
373
+ }
374
+
375
+ protected IRubyObject getAttribute(Ruby runtime, String key) {
376
+ String value = getAttribute(key);
377
+ return nonEmptyStringOrNil(runtime, value);
378
+ }
379
+
380
+ protected String getAttribute(String key) {
381
+ if (node.getNodeType() != Node.ELEMENT_NODE) return null;
382
+
383
+ String value = ((Element)node).getAttribute(key);
384
+ return value.length() == 0 ? null : value;
385
+ }
386
+
387
+ /**
388
+ * This method should be called after a node has been adopted in a new
389
+ * document. This method will ensure that the node is renamed with the
390
+ * appriopriate NS uri. First the prefix of the node is extracted, then is
391
+ * used to lookup the namespace uri in the new document starting at the
392
+ * current node and traversing the ancestors. If the namespace uri wasn't
393
+ * empty (or null) all children and the node has attributes and/or children
394
+ * then the algorithm is recursively applied to the children.
395
+ */
396
+ public void relink_namespace(ThreadContext context) {
397
+ if (!(node instanceof Element)) {
398
+ return;
399
+ }
400
+
401
+ Element e = (Element) node;
402
+
403
+ // disable error checking to prevent lines like the following
404
+ // from throwing a `NAMESPACE_ERR' exception:
405
+ // Nokogiri::XML::DocumentFragment.parse("<o:div>a</o:div>")
406
+ // since the `o' prefix isn't defined anywhere.
407
+ e.getOwnerDocument().setStrictErrorChecking(false);
408
+
409
+ String prefix = e.getPrefix();
410
+ String nsURI = e.lookupNamespaceURI(prefix);
411
+ this.node = NokogiriHelpers.renameNode(e, nsURI, e.getNodeName());
412
+
413
+ if (nsURI == null || nsURI.isEmpty()) return;
414
+
415
+ String currentPrefix = e.getParentNode().lookupPrefix(nsURI);
416
+ String currentURI = e.getParentNode().lookupNamespaceURI(prefix);
417
+ boolean isDefault = e.getParentNode().isDefaultNamespace(nsURI);
418
+
419
+ // add xmlns attribute if this is a new root node or if the node's
420
+ // namespace isn't a default namespace in the new document
421
+ if (e.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
422
+ // this is the root node, so we must set the namespaces attributes anyway
423
+ e.setAttribute(prefix == null ? "xmlns" : "xmlns:" + prefix, nsURI);
424
+ } else if (prefix == null) {
425
+ // this is a default namespace but isn't the default where this node is being added
426
+ if (!isDefault) e.setAttribute("xmlns", nsURI);
427
+ } else if (!prefix.equals(currentPrefix) || nsURI.equals(currentURI)) {
428
+ // this is a prefixed namespace
429
+ // but doesn't have the same prefix or the prefix is set to a different URI
430
+ e.setAttribute("xmlns:" + prefix, nsURI);
431
+ }
432
+
433
+ if (e.hasAttributes()) {
434
+ NamedNodeMap attrs = e.getAttributes();
435
+
436
+ for (int i = 0; i < attrs.getLength(); i++) {
437
+ Attr attr = (Attr) attrs.item(i);
438
+ String attrPrefix = attr.getPrefix();
439
+ if (attrPrefix == null) {
440
+ attrPrefix = NokogiriHelpers.getPrefix(attr.getNodeName());
441
+ }
442
+ String nodeName = attr.getNodeName();
443
+ String nsUri;
444
+ if ("xml".equals(attrPrefix)) {
445
+ nsUri = "http://www.w3.org/XML/1998/namespace";
446
+ } else if ("xmlns".equals(attrPrefix) || nodeName.equals("xmlns")) {
447
+ nsUri = "http://www.w3.org/2000/xmlns/";
448
+ } else {
449
+ nsUri = attr.lookupNamespaceURI(attrPrefix);
450
+ }
451
+
452
+ if (nsUri != null && nsUri.equals(e.getNamespaceURI())) {
453
+ nsUri = null;
454
+ }
455
+
456
+ if (!(nsUri == null || "".equals(nsUri) || "http://www.w3.org/XML/1998/namespace".equals(nsUri))) {
457
+ // Create a new namespace object and add it to the document namespace cache.
458
+ // TODO: why do we need the namespace cache ?
459
+ XmlNamespace.createFromAttr(context.runtime, attr);
460
+ }
461
+ NokogiriHelpers.renameNode(attr, nsUri, nodeName);
462
+ }
463
+ }
464
+
465
+ if (this.node.hasChildNodes()) {
466
+ relink_namespace(context, getChildren());
467
+ }
468
+ }
469
+
470
+ static void relink_namespace(ThreadContext context, IRubyObject[] nodes) {
471
+ for (int i = 0; i < nodes.length; i++) {
472
+ if (nodes[i] instanceof XmlNode) {
473
+ ((XmlNode) nodes[i]).relink_namespace(context);
474
+ }
475
+ }
476
+ }
477
+
478
+ // Users might extend XmlNode. This method works for such a case.
479
+ public void accept(ThreadContext context, SaveContextVisitor visitor) {
480
+ visitor.enter(node);
481
+ acceptChildren(context, getChildren(), visitor);
482
+ visitor.leave(node);
483
+ }
484
+
485
+ void acceptChildren(ThreadContext context, IRubyObject[] nodes, SaveContextVisitor visitor) {
486
+ if (nodes.length > 0) {
487
+ for (int i = 0; i < nodes.length; i++) {
488
+ Object item = nodes[i];
489
+ if (item instanceof XmlNode) {
490
+ ((XmlNode) item).accept(context, visitor);
491
+ } else if (item instanceof XmlNamespace) {
492
+ ((XmlNamespace) item).accept(context, visitor);
493
+ }
494
+ }
495
+ }
496
+ }
497
+
498
+ RubyString doSetName(IRubyObject name) {
499
+ if (name.isNil()) return this.name = null;
500
+ return this.name = name.convertToString();
501
+ }
502
+
503
+ public void setDocument(ThreadContext context, XmlDocument doc) {
504
+ this.doc = doc;
505
+
506
+ setDocumentAndDecorate(context, this, doc);
507
+ }
508
+
509
+ // shared logic with XmlNodeSet
510
+ static void setDocumentAndDecorate(ThreadContext context, RubyObject self, XmlDocument doc) {
511
+ self.setInstanceVariable("@document", doc == null ? context.nil : doc);
512
+ if (doc != null) Helpers.invoke(context, doc, "decorate", self);
513
+ }
514
+
515
+ public void setNode(Ruby runtime, Node node) {
516
+ this.node = node;
517
+
518
+ decorate(runtime);
519
+
520
+ if (this instanceof XmlAttr) {
521
+ ((XmlAttr) this).setNamespaceIfNecessary(runtime);
522
+ }
523
+ }
524
+
525
+ protected IRubyObject getNodeName(ThreadContext context) {
526
+ if (name != null) return name;
527
+
528
+ String str = null;
529
+ if (node != null) {
530
+ str = NokogiriHelpers.getLocalPart(node.getNodeName());
531
+ }
532
+ if (str == null) str = "";
533
+ if (str.startsWith("#")) str = str.substring(1); // eliminates '#'
534
+ return name = context.runtime.newString(str);
535
+ }
536
+
537
+ /**
538
+ * Add a namespace definition to this node. To the underlying
539
+ * node, add an attribute of the form
540
+ * <code>xmlns:prefix="uri"</code>.
541
+ */
542
+ @JRubyMethod(name = {"add_namespace_definition", "add_namespace"})
543
+ public IRubyObject add_namespace_definition(ThreadContext context, IRubyObject prefix, IRubyObject href) {
544
+ String hrefStr, prefixStr = prefix.isNil() ? null : prefix.convertToString().decodeString();
545
+
546
+ // try to search the namespace first
547
+ if (href.isNil()) {
548
+ hrefStr = findNamespaceHref(context, prefixStr);
549
+ if (hrefStr == null) return context.nil;
550
+ href = context.runtime.newString(hrefStr);
551
+ } else {
552
+ hrefStr = rubyStringToString(href.convertToString());
553
+ }
554
+
555
+ NokogiriNamespaceCache nsCache = NokogiriHelpers.getNamespaceCache(node);
556
+ XmlNamespace cachedNamespace = nsCache.get(prefixStr, hrefStr);
557
+ if (cachedNamespace != null) return cachedNamespace;
558
+
559
+ Node namespaceOwner;
560
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
561
+ namespaceOwner = node;
562
+ Element element = (Element) node;
563
+ // adds namespace as node's attribute
564
+ String qName = prefix.isNil() ? "xmlns" : "xmlns:" + prefixStr;
565
+ element.setAttributeNS("http://www.w3.org/2000/xmlns/", qName, hrefStr);
566
+ }
567
+ else if (node.getNodeType() == Node.ATTRIBUTE_NODE) namespaceOwner = ((Attr) node).getOwnerElement();
568
+ else namespaceOwner = node.getParentNode();
569
+
570
+ XmlNamespace ns = XmlNamespace.createImpl(namespaceOwner, prefix, prefixStr, href, hrefStr);
571
+
572
+ if (node != namespaceOwner) {
573
+ node = NokogiriHelpers.renameNode(node, ns.getHref(), ns.getPrefix() + ':' + node.getLocalName());
574
+ }
575
+ updateNodeNamespaceIfNecessary(ns);
576
+
577
+ return ns;
578
+ }
579
+
580
+ private void updateNodeNamespaceIfNecessary(XmlNamespace ns) {
581
+ String oldPrefix = this.node.getPrefix();
582
+
583
+ /*
584
+ * Update if both prefixes are null or equal
585
+ */
586
+ boolean update =
587
+ (oldPrefix == null && ns.getPrefix() == null) ||
588
+ (oldPrefix != null && oldPrefix.equals(ns.getPrefix()));
589
+
590
+ if (update) {
591
+ this.node = NokogiriHelpers.renameNode(this.node, ns.getHref(), this.node.getNodeName());
592
+ }
593
+ }
594
+
595
+ @JRubyMethod(name = {"attribute", "attr"})
596
+ public IRubyObject attribute(ThreadContext context, IRubyObject name){
597
+ NamedNodeMap attrs = this.node.getAttributes();
598
+ Node attr = attrs.getNamedItem(rubyStringToString(name));
599
+ if (attr == null) return context.nil;
600
+ return getCachedNodeOrCreate(context.runtime, attr);
601
+ }
602
+
603
+ @JRubyMethod
604
+ public IRubyObject attribute_nodes(ThreadContext context) {
605
+ final Ruby runtime = context.runtime;
606
+
607
+ NamedNodeMap nodeMap = this.node.getAttributes();
608
+
609
+ if (nodeMap == null) return runtime.newEmptyArray();
610
+ RubyArray attr = runtime.newArray(nodeMap.getLength());
611
+
612
+ final XmlDocument doc = document(context.runtime);
613
+ for (int i = 0; i < nodeMap.getLength(); i++) {
614
+ if ((doc instanceof HtmlDocument) || !NokogiriHelpers.isNamespace(nodeMap.item(i))) {
615
+ attr.append(getCachedNodeOrCreate(runtime, nodeMap.item(i)));
616
+ }
617
+ }
618
+
619
+ return attr;
620
+ }
621
+
622
+ @JRubyMethod
623
+ public IRubyObject attribute_with_ns(ThreadContext context, IRubyObject name, IRubyObject namespace) {
624
+ String namej = rubyStringToString(name);
625
+ String nsj = (namespace.isNil()) ? null : rubyStringToString(namespace);
626
+
627
+ Node el = this.node.getAttributes().getNamedItemNS(nsj, namej);
628
+
629
+ if (el == null) return context.nil;
630
+
631
+ return NokogiriHelpers.getCachedNodeOrCreate(context.runtime, el);
632
+ }
633
+
634
+ @JRubyMethod(name = "blank?")
635
+ public IRubyObject blank_p(ThreadContext context) {
636
+ // according to libxml doc,
637
+ // a node is blank if if it is a Text or CDATA node consisting of whitespace only
638
+ if (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE) {
639
+ String data = node.getTextContent();
640
+ return context.runtime.newBoolean(data == null || isBlank(data));
641
+ }
642
+ return context.runtime.getFalse();
643
+ }
644
+
645
+ @JRubyMethod
646
+ public IRubyObject child(ThreadContext context) {
647
+ return getCachedNodeOrCreate(context.getRuntime(), node.getFirstChild());
648
+ }
649
+
650
+ @JRubyMethod
651
+ public IRubyObject children(ThreadContext context) {
652
+ final IRubyObject[] nodes = getChildren();
653
+ if (nodes.length == 0) {
654
+ return XmlNodeSet.newEmptyNodeSet(context, this);
655
+ }
656
+ return XmlNodeSet.newNodeSet(context.runtime, nodes);
657
+ }
658
+
659
+ IRubyObject[] getChildren() {
660
+ NodeList nodeList = node.getChildNodes();
661
+ if (nodeList.getLength() > 0) {
662
+ return nodeListToRubyArray(getRuntime(), nodeList);
663
+ }
664
+ return IRubyObject.NULL_ARRAY;
665
+ }
666
+
667
+ @JRubyMethod
668
+ public IRubyObject first_element_child(ThreadContext context) {
669
+ List<Node> elementNodes = getElements(node, true);
670
+ if (elementNodes.size() == 0) return context.nil;
671
+ return getCachedNodeOrCreate(context.runtime, elementNodes.get(0));
672
+ }
673
+
674
+ @JRubyMethod
675
+ public IRubyObject last_element_child(ThreadContext context) {
676
+ List<Node> elementNodes = getElements(node, false);
677
+ if (elementNodes.size() == 0) return context.nil;
678
+ return getCachedNodeOrCreate(context.runtime, elementNodes.get(elementNodes.size() - 1));
679
+ }
680
+
681
+ @JRubyMethod(name = {"element_children", "elements"})
682
+ public IRubyObject element_children(ThreadContext context) {
683
+ List<Node> elementNodes = getElements(node, false);
684
+ IRubyObject[] array = NokogiriHelpers.nodeListToArray(context.runtime, elementNodes);
685
+ return XmlNodeSet.newNodeSet(context.runtime, array, this);
686
+ }
687
+
688
+ private static List<Node> getElements(Node node, final boolean firstOnly) {
689
+ NodeList children = node.getChildNodes();
690
+ if (children.getLength() == 0) {
691
+ return Collections.emptyList();
692
+ }
693
+ ArrayList<Node> elements = firstOnly ? null : new ArrayList<Node>(children.getLength());
694
+ for (int i=0; i< children.getLength(); i++) {
695
+ Node child = children.item(i);
696
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
697
+ if (firstOnly) {
698
+ return Collections.singletonList(child);
699
+ }
700
+ elements.add(child);
701
+ }
702
+ }
703
+ return elements;
704
+ }
705
+
706
+ /**
707
+ * call-seq:
708
+ * compare(other)
709
+ *
710
+ * Compare this Node to +other+ with respect to their Document
711
+ */
712
+ @JRubyMethod(visibility=Visibility.PRIVATE)
713
+ public IRubyObject compare(ThreadContext context, IRubyObject other) {
714
+ if (!(other instanceof XmlNode)) {
715
+ return context.runtime.newFixnum(-2);
716
+ }
717
+
718
+ Node otherNode = asXmlNode(context, other).node;
719
+
720
+ // Do not touch this if, if it's not for a good reason.
721
+ if (node.getNodeType() == Node.DOCUMENT_NODE ||
722
+ otherNode.getNodeType() == Node.DOCUMENT_NODE) {
723
+ return context.runtime.newFixnum(1);
724
+ }
725
+
726
+ try{
727
+ int res = node.compareDocumentPosition(otherNode);
728
+ if ((res & FIRST_PRECEDES_SECOND) == FIRST_PRECEDES_SECOND) {
729
+ return context.runtime.newFixnum(-1);
730
+ } else if ((res & SECOND_PRECEDES_FIRST) == SECOND_PRECEDES_FIRST) {
731
+ return context.runtime.newFixnum(1);
732
+ } else if (res == IDENTICAL_ELEMENTS) {
733
+ return context.runtime.newFixnum(0);
734
+ }
735
+
736
+ return context.runtime.newFixnum(-2);
737
+ } catch (Exception ex) {
738
+ return context.runtime.newFixnum(-2);
739
+ }
740
+ }
741
+
742
+ /**
743
+ * TODO: this is a stub implementation. It's not clear what
744
+ * 'in_context' is supposed to do. Also should take
745
+ * <code>options</code> into account.
746
+ */
747
+ @JRubyMethod(required = 2, visibility = Visibility.PRIVATE)
748
+ public IRubyObject in_context(ThreadContext context, IRubyObject str, IRubyObject options) {
749
+ RubyClass klass;
750
+ XmlDomParserContext ctx;
751
+ InputStream istream;
752
+
753
+ final Ruby runtime = context.runtime;
754
+
755
+ XmlDocument document = document(runtime);
756
+ if (document == null) return context.nil;
757
+
758
+ if (document instanceof HtmlDocument) {
759
+ klass = getNokogiriClass(runtime, "Nokogiri::HTML::Document");
760
+ ctx = new HtmlDomParserContext(runtime, options);
761
+ ((HtmlDomParserContext) ctx).enableDocumentFragment();
762
+ ctx.setStringInputSource(context, str, context.nil);
763
+ } else {
764
+ klass = getNokogiriClass(runtime, "Nokogiri::XML::Document");
765
+ ctx = new XmlDomParserContext(runtime, options);
766
+ ctx.setStringInputSource(context, str, context.nil);
767
+ }
768
+
769
+ // TODO: for some reason, document.getEncoding() can be null or nil (don't know why)
770
+ // run `test_parse_with_unparented_html_text_context_node' few times to see this happen
771
+ if (document instanceof HtmlDocument && !(document.getEncoding() == null || document.getEncoding().isNil())) {
772
+ HtmlDomParserContext htmlCtx= (HtmlDomParserContext) ctx;
773
+ htmlCtx.setEncoding(document.getEncoding().asJavaString());
774
+ }
775
+
776
+ XmlDocument doc = ctx.parse(context, klass, context.nil);
777
+
778
+ RubyArray documentErrors = getErrors(document);
779
+ RubyArray docErrors = getErrors(doc);
780
+ if (checkNewErrors(documentErrors, docErrors)) {
781
+ for (int i = 0; i < docErrors.getLength(); i++) {
782
+ documentErrors.append(docErrors.entry(i));
783
+ }
784
+ document.setInstanceVariable("@errors", documentErrors);
785
+ return XmlNodeSet.newNodeSet(context.runtime, IRubyObject.NULL_ARRAY, this);
786
+ }
787
+
788
+ // The first child might be document type node (dtd declaration).
789
+ // XmlNodeSet to be return should not have dtd decl in its list.
790
+ Node first;
791
+ if (doc.node.getFirstChild().getNodeType() == Node.DOCUMENT_TYPE_NODE) {
792
+ first = doc.node.getFirstChild().getNextSibling();
793
+ } else {
794
+ first = doc.node.getFirstChild();
795
+ }
796
+
797
+ IRubyObject[] nodes = new IRubyObject[] { NokogiriHelpers.getCachedNodeOrCreate(runtime, first) };
798
+ return XmlNodeSet.newNodeSet(context.runtime, nodes, this);
799
+ }
800
+
801
+ private static RubyArray getErrors(XmlDocument document) {
802
+ IRubyObject obj = document.getInstanceVariable("@errors");
803
+ if (obj instanceof RubyArray) return (RubyArray) obj;
804
+ return RubyArray.newEmptyArray(document.getRuntime());
805
+ }
806
+
807
+ private static boolean checkNewErrors(RubyArray baseErrors, RubyArray newErrors) {
808
+ int length = ((RubyArray) newErrors.op_diff(baseErrors)).size();
809
+ return length > 0;
810
+ }
811
+
812
+ @JRubyMethod(name = {"content", "text", "inner_text"})
813
+ public IRubyObject content(ThreadContext context) {
814
+ return stringOrNil(context.runtime, getContentImpl());
815
+ }
816
+
817
+ public CharSequence getContentImpl() {
818
+ if (!node.hasChildNodes() && node.getNodeValue() == null &&
819
+ (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE)) {
820
+ return null;
821
+ }
822
+ CharSequence textContent;
823
+ if (this instanceof XmlDocument) {
824
+ Node node = ((Document) this.node).getDocumentElement();
825
+ if (node == null) {
826
+ textContent = "";
827
+ } else {
828
+ Node documentElement = ((Document) this.node).getDocumentElement();
829
+ textContent = getTextContentRecursively(new StringBuilder(), documentElement);
830
+ }
831
+ } else {
832
+ textContent = getTextContentRecursively(new StringBuilder(), node);
833
+ }
834
+ // textContent = NokogiriHelpers.convertEncodingByNKFIfNecessary(context, (XmlDocument) document(context), textContent);
835
+ return textContent;
836
+ }
837
+
838
+ private static StringBuilder getTextContentRecursively(StringBuilder buffer, Node currentNode) {
839
+ CharSequence textContent = currentNode.getNodeValue();
840
+ if (textContent != null && NokogiriHelpers.shouldDecode(currentNode)) {
841
+ textContent = NokogiriHelpers.decodeJavaString(textContent);
842
+ }
843
+ if (textContent != null) buffer.append(textContent);
844
+ NodeList children = currentNode.getChildNodes();
845
+ for (int i = 0; i < children.getLength(); i++) {
846
+ Node child = children.item(i);
847
+ if (hasTextContent(child)) getTextContentRecursively(buffer, child);
848
+ }
849
+ return buffer;
850
+ }
851
+
852
+ private static boolean hasTextContent(Node child) {
853
+ return child.getNodeType() != Node.COMMENT_NODE && child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE;
854
+ }
855
+
856
+ @JRubyMethod
857
+ public final IRubyObject document(ThreadContext context) {
858
+ return document(context.runtime);
859
+ }
860
+
861
+ XmlDocument document(final Ruby runtime) {
862
+ return document(runtime, true);
863
+ }
864
+
865
+ XmlDocument document(final Ruby runtime, boolean create) {
866
+ if (doc == null) {
867
+ doc = (XmlDocument) node.getOwnerDocument().getUserData(NokogiriHelpers.CACHED_NODE);
868
+ if (doc == null && create) {
869
+ doc = (XmlDocument) getCachedNodeOrCreate(runtime, node.getOwnerDocument());
870
+ node.getOwnerDocument().setUserData(NokogiriHelpers.CACHED_NODE, doc, null);
871
+ }
872
+ }
873
+ return doc;
874
+ }
875
+
876
+ public IRubyObject dup() {
877
+ return dup_implementation(getMetaClass().getClassRuntime(), true);
878
+ }
879
+
880
+ @JRubyMethod
881
+ public IRubyObject dup(ThreadContext context) {
882
+ return dup_implementation(context, true);
883
+ }
884
+
885
+ @JRubyMethod
886
+ public IRubyObject dup(ThreadContext context, IRubyObject depth) {
887
+ boolean deep = depth instanceof RubyInteger && RubyFixnum.fix2int(depth) != 0;
888
+ return dup_implementation(context, deep);
889
+ }
890
+
891
+ protected final IRubyObject dup_implementation(ThreadContext context, boolean deep) {
892
+ return dup_implementation(context.runtime, deep);
893
+ }
894
+
895
+ protected IRubyObject dup_implementation(Ruby runtime, boolean deep) {
896
+ XmlNode clone;
897
+ try {
898
+ clone = (XmlNode) clone();
899
+ } catch (CloneNotSupportedException e) {
900
+ throw runtime.newRuntimeError(e.toString());
901
+ }
902
+ Node newNode = node.cloneNode(deep);
903
+ clone.node = newNode;
904
+ return clone;
905
+ }
906
+
907
+ public static RubyString encode_special_chars(ThreadContext context, IRubyObject string) {
908
+ CharSequence str = NokogiriHelpers.encodeJavaString( rubyStringToString(string) );
909
+ return RubyString.newString(context.runtime, str);
910
+ }
911
+
912
+ /**
913
+ * Instance method version of the above static method.
914
+ */
915
+ @JRubyMethod(name="encode_special_chars")
916
+ public IRubyObject i_encode_special_chars(ThreadContext context, IRubyObject string) {
917
+ return encode_special_chars(context, string);
918
+ }
919
+
920
+ /**
921
+ * Get the attribute at the given key, <code>key</code>.
922
+ * Assumes that this node has attributes (i.e. that key? returned
923
+ * true).
924
+ */
925
+ @JRubyMethod(visibility = Visibility.PRIVATE)
926
+ public IRubyObject get(ThreadContext context, IRubyObject rbkey) {
927
+ if (node instanceof Element) {
928
+ if (rbkey == null || rbkey.isNil()) return context.nil;
929
+ String key = rubyStringToString(rbkey);
930
+ Element element = (Element) node;
931
+ if (!element.hasAttribute(key)) return context.nil;
932
+ String value = element.getAttribute(key);
933
+ return stringOrNil(context.runtime, value);
934
+ }
935
+ return context.nil;
936
+ }
937
+
938
+ /**
939
+ * Returns the owner document, checking if this node is the
940
+ * document, or returns null if there is no owner.
941
+ */
942
+ protected Document getOwnerDocument() {
943
+ if (node.getNodeType() == Node.DOCUMENT_NODE) {
944
+ return (Document) node;
945
+ } else {
946
+ return node.getOwnerDocument();
947
+ }
948
+ }
949
+
950
+ @JRubyMethod
951
+ public IRubyObject internal_subset(ThreadContext context) {
952
+ Document document = getOwnerDocument();
953
+
954
+ if(document == null) {
955
+ return context.getRuntime().getNil();
956
+ }
957
+
958
+ XmlDocument xdoc =
959
+ (XmlDocument) getCachedNodeOrCreate(context.getRuntime(), document);
960
+ IRubyObject xdtd = xdoc.getInternalSubset(context);
961
+ return xdtd;
962
+ }
963
+
964
+ @JRubyMethod
965
+ public IRubyObject create_internal_subset(ThreadContext context,
966
+ IRubyObject name,
967
+ IRubyObject external_id,
968
+ IRubyObject system_id) {
969
+ IRubyObject subset = internal_subset(context);
970
+ if (!subset.isNil()) {
971
+ throw context.runtime.newRuntimeError("Document already has internal subset");
972
+ }
973
+
974
+ Document document = getOwnerDocument();
975
+ if(document == null) {
976
+ return context.getRuntime().getNil();
977
+ }
978
+
979
+ XmlDocument xdoc =
980
+ (XmlDocument) getCachedNodeOrCreate(context.getRuntime(), document);
981
+ IRubyObject xdtd = xdoc.createInternalSubset(context, name,
982
+ external_id, system_id);
983
+ return xdtd;
984
+ }
985
+
986
+ @JRubyMethod
987
+ public IRubyObject external_subset(ThreadContext context) {
988
+ Document document = getOwnerDocument();
989
+
990
+ if (document == null) {
991
+ return context.getRuntime().getNil();
992
+ }
993
+
994
+ XmlDocument xdoc =
995
+ (XmlDocument) getCachedNodeOrCreate(context.getRuntime(), document);
996
+ IRubyObject xdtd = xdoc.getExternalSubset(context);
997
+ return xdtd;
998
+ }
999
+
1000
+ @JRubyMethod
1001
+ public IRubyObject create_external_subset(ThreadContext context,
1002
+ IRubyObject name,
1003
+ IRubyObject external_id,
1004
+ IRubyObject system_id) {
1005
+ IRubyObject subset = external_subset(context);
1006
+ if (!subset.isNil()) {
1007
+ throw context.runtime.newRuntimeError("Document already has external subset");
1008
+ }
1009
+
1010
+ Document document = getOwnerDocument();
1011
+ if(document == null) {
1012
+ return context.getRuntime().getNil();
1013
+ }
1014
+ XmlDocument xdoc = (XmlDocument) getCachedNodeOrCreate(context.getRuntime(), document);
1015
+ IRubyObject xdtd = xdoc.createExternalSubset(context, name, external_id, system_id);
1016
+ return xdtd;
1017
+ }
1018
+
1019
+ /**
1020
+ * Test if this node has an attribute named <code>rbkey</code>.
1021
+ * Overridden in XmlElement.
1022
+ */
1023
+ @JRubyMethod(name = {"key?", "has_attribute?"})
1024
+ public IRubyObject key_p(ThreadContext context, IRubyObject rbkey) {
1025
+ if (node instanceof Element) {
1026
+ String key = rubyStringToString(rbkey);
1027
+ Element element = (Element) node;
1028
+ if (element.hasAttribute(key)) {
1029
+ return context.runtime.getTrue();
1030
+ } else {
1031
+ NamedNodeMap namedNodeMap = element.getAttributes();
1032
+ for (int i=0; i<namedNodeMap.getLength(); i++) {
1033
+ Node n = namedNodeMap.item(i);
1034
+ if (key.equals(n.getLocalName())) {
1035
+ return context.runtime.getTrue();
1036
+ }
1037
+ }
1038
+ }
1039
+ return context.runtime.getFalse();
1040
+ }
1041
+ return context.nil;
1042
+ }
1043
+
1044
+ @JRubyMethod
1045
+ public IRubyObject namespace(ThreadContext context) {
1046
+ final XmlDocument doc = document(context.runtime);
1047
+ if (doc instanceof HtmlDocument) return context.nil;
1048
+
1049
+ String namespaceURI = node.getNamespaceURI();
1050
+ if (namespaceURI == null || namespaceURI.isEmpty()) {
1051
+ return context.nil;
1052
+ }
1053
+
1054
+ String prefix = node.getPrefix();
1055
+ NokogiriNamespaceCache nsCache = NokogiriHelpers.getNamespaceCache(node);
1056
+ XmlNamespace namespace = nsCache.get(prefix, namespaceURI);
1057
+
1058
+ if (namespace == null || namespace.isEmpty()) {
1059
+ // if it's not in the cache, create an unowned, uncached namespace and
1060
+ // return that. XmlReader can't insert namespaces into the cache, so
1061
+ // this is necessary for XmlReader to work correctly.
1062
+ namespace = new XmlNamespace(context.runtime, null, prefix, namespaceURI, doc);
1063
+ }
1064
+
1065
+ return namespace;
1066
+ }
1067
+
1068
+ /**
1069
+ * Return an array of XmlNamespace nodes based on the attributes
1070
+ * of this node.
1071
+ */
1072
+ @JRubyMethod
1073
+ public IRubyObject namespace_definitions(ThreadContext context) {
1074
+ // don't use namespace_definitions cache anymore since
1075
+ // namespaces might be deleted. Reflecting the result of
1076
+ // namespace removals is complicated, so the cache might not be
1077
+ // updated.
1078
+ final XmlDocument doc = document(context.runtime);
1079
+ if (doc == null) return context.runtime.newEmptyArray();
1080
+ if (doc instanceof HtmlDocument) return context.runtime.newEmptyArray();
1081
+
1082
+ List<XmlNamespace> namespaces = doc.getNamespaceCache().get(node);
1083
+ return context.runtime.newArray((List) namespaces);
1084
+ }
1085
+
1086
+ /**
1087
+ * Return an array of XmlNamespace nodes defined on this node and
1088
+ * on any ancestor node.
1089
+ */
1090
+ @JRubyMethod
1091
+ public RubyArray namespace_scopes(ThreadContext context) {
1092
+ final XmlDocument doc = document(context.runtime);
1093
+ if (doc == null) return context.runtime.newEmptyArray();
1094
+ if (doc instanceof HtmlDocument) return context.runtime.newEmptyArray();
1095
+
1096
+ Node previousNode;
1097
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
1098
+ previousNode = node;
1099
+ } else if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
1100
+ previousNode = ((Attr)node).getOwnerElement();
1101
+ } else {
1102
+ previousNode = findPreviousElement(node);
1103
+ }
1104
+ if (previousNode == null) return context.runtime.newEmptyArray();
1105
+
1106
+ final RubyArray scoped_namespaces = context.runtime.newArray();
1107
+ final HashSet<String> prefixes_in_scope = new HashSet<String>(8);
1108
+ NokogiriNamespaceCache nsCache = NokogiriHelpers.getNamespaceCache(previousNode);
1109
+ for (Node previous = previousNode; previous != null; ) {
1110
+ List<XmlNamespace> namespaces = nsCache.get(previous);
1111
+ for (XmlNamespace namespace : namespaces) {
1112
+ if (prefixes_in_scope.contains(namespace.getPrefix())) continue;
1113
+ scoped_namespaces.append(namespace);
1114
+ prefixes_in_scope.add(namespace.getPrefix());
1115
+ }
1116
+ previous = findPreviousElement(previous);
1117
+ }
1118
+ return scoped_namespaces;
1119
+ }
1120
+
1121
+ private Node findPreviousElement(Node n) {
1122
+ Node previous = n.getPreviousSibling() == null ? n.getParentNode() : n.getPreviousSibling();
1123
+ if (previous == null || previous.getNodeType() == Node.DOCUMENT_NODE) return null;
1124
+ if (previous.getNodeType() == Node.ELEMENT_NODE) {
1125
+ return previous;
1126
+ } else {
1127
+ return findPreviousElement(previous);
1128
+ }
1129
+ }
1130
+
1131
+ @JRubyMethod(name="namespaced_key?")
1132
+ public IRubyObject namespaced_key_p(ThreadContext context, IRubyObject elementLName, IRubyObject namespaceUri) {
1133
+ return this.attribute_with_ns(context, elementLName, namespaceUri).isNil() ?
1134
+ context.runtime.getFalse() : context.runtime.getTrue();
1135
+ }
1136
+
1137
+ protected void setContent(IRubyObject content) {
1138
+ String javaContent = rubyStringToString(content);
1139
+ node.setTextContent(javaContent);
1140
+ if (javaContent == null || javaContent.length() == 0) return;
1141
+ if (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE) return;
1142
+ if (node.getFirstChild() != null) {
1143
+ node.getFirstChild().setUserData(NokogiriHelpers.ENCODED_STRING, true, null);
1144
+ }
1145
+ }
1146
+
1147
+ private void setContent(String content) {
1148
+ node.setTextContent(content);
1149
+ this.content = null; // clear cache
1150
+ }
1151
+
1152
+ @JRubyMethod(name = "native_content=")
1153
+ public IRubyObject native_content_set(ThreadContext context, IRubyObject content) {
1154
+ setContent(content);
1155
+ return content;
1156
+ }
1157
+
1158
+ @JRubyMethod
1159
+ public IRubyObject lang(ThreadContext context) {
1160
+ IRubyObject currentObj = this ;
1161
+ while (!currentObj.isNil()) {
1162
+ XmlNode currentNode = asXmlNode(context, currentObj);
1163
+ IRubyObject lang = currentNode.getAttribute(context.runtime, "xml:lang");
1164
+ if (!lang.isNil()) { return lang ; }
1165
+
1166
+ currentObj = currentNode.parent(context);
1167
+ }
1168
+ return context.nil;
1169
+ }
1170
+
1171
+ @JRubyMethod(name = "lang=")
1172
+ public IRubyObject set_lang(ThreadContext context, IRubyObject lang) {
1173
+ setAttribute(context, "xml:lang", rubyStringToString(lang));
1174
+ return context.nil ;
1175
+ }
1176
+
1177
+ /**
1178
+ * @param args {IRubyObject io,
1179
+ * IRubyObject encoding,
1180
+ * IRubyObject indentString,
1181
+ * IRubyObject options}
1182
+ */
1183
+ @JRubyMethod(required=4, visibility=Visibility.PRIVATE)
1184
+ public IRubyObject native_write_to(ThreadContext context, IRubyObject[] args) {
1185
+
1186
+ IRubyObject io = args[0];
1187
+ IRubyObject encoding = args[1];
1188
+ IRubyObject indentString = args[2];
1189
+ IRubyObject options = args[3];
1190
+
1191
+ String encString = rubyStringToString(encoding);
1192
+
1193
+ SaveContextVisitor visitor =
1194
+ new SaveContextVisitor(RubyFixnum.fix2int(options), rubyStringToString(indentString), encString, isHtmlDoc(context), isFragment(), 0);
1195
+ accept(context, visitor);
1196
+
1197
+ final IRubyObject rubyString;
1198
+ if (NokogiriHelpers.isUTF8(encString)) {
1199
+ rubyString = convertString(context.runtime, visitor.getInternalBuffer());
1200
+ } else {
1201
+ ByteBuffer bytes = convertEncoding(Charset.forName(encString), visitor.getInternalBuffer());
1202
+ ByteList str = new ByteList(bytes.array(), bytes.arrayOffset(), bytes.remaining());
1203
+ rubyString = RubyString.newString(context.runtime, str);
1204
+ }
1205
+ Helpers.invoke(context, io, "write", rubyString);
1206
+
1207
+ return io;
1208
+ }
1209
+
1210
+ private boolean isHtmlDoc(ThreadContext context) {
1211
+ return document(context).getMetaClass().isKindOfModule(getNokogiriClass(context.runtime, "Nokogiri::HTML::Document"));
1212
+ }
1213
+
1214
+ private boolean isFragment() {
1215
+ if (node instanceof DocumentFragment) return true;
1216
+ if (node.getParentNode() != null && node.getParentNode() instanceof DocumentFragment) return true;
1217
+ return false;
1218
+ }
1219
+
1220
+ @JRubyMethod(name = {"next_sibling", "next"})
1221
+ public IRubyObject next_sibling(ThreadContext context) {
1222
+ return getCachedNodeOrCreate(context.getRuntime(), node.getNextSibling());
1223
+ }
1224
+
1225
+ @JRubyMethod(name = {"previous_sibling", "previous"})
1226
+ public IRubyObject previous_sibling(ThreadContext context) {
1227
+ return getCachedNodeOrCreate(context.getRuntime(), node.getPreviousSibling());
1228
+ }
1229
+
1230
+ @JRubyMethod(name = {"node_name", "name"})
1231
+ public IRubyObject node_name(ThreadContext context) {
1232
+ return getNodeName(context);
1233
+ }
1234
+
1235
+ @JRubyMethod(name = {"node_name=", "name="})
1236
+ public IRubyObject node_name_set(ThreadContext context, IRubyObject nodeName) {
1237
+ nodeName = doSetName(nodeName);
1238
+ String newName = nodeName == null ? null : rubyStringToString((RubyString) nodeName);
1239
+ this.node = NokogiriHelpers.renameNode(node, null, newName);
1240
+ return this;
1241
+ }
1242
+
1243
+ @JRubyMethod(visibility = Visibility.PRIVATE)
1244
+ public IRubyObject set(ThreadContext context, IRubyObject rbkey, IRubyObject rbval) {
1245
+ if (node instanceof Element) {
1246
+ setAttribute(context, rubyStringToString(rbkey), rubyStringToString(rbval));
1247
+ return this;
1248
+ } else {
1249
+ return rbval;
1250
+ }
1251
+ }
1252
+
1253
+ private void setAttribute(ThreadContext context, String key, String val) {
1254
+ Element element = (Element) node;
1255
+
1256
+ String uri = null;
1257
+ int colonIndex = key.indexOf(":");
1258
+ if (colonIndex > 0) {
1259
+ String prefix = key.substring(0, colonIndex);
1260
+ if (prefix.equals("xml")) {
1261
+ uri = "http://www.w3.org/XML/1998/namespace";
1262
+ } else if (prefix.equals("xmlns")) {
1263
+ uri = "http://www.w3.org/2000/xmlns/";
1264
+ } else {
1265
+ uri = node.lookupNamespaceURI(prefix);
1266
+ }
1267
+ }
1268
+
1269
+ if (uri != null) {
1270
+ element.setAttributeNS(uri, key, val);
1271
+ } else {
1272
+ element.setAttribute(key, val);
1273
+ }
1274
+ clearXpathContext(node);
1275
+ }
1276
+
1277
+ private String findNamespaceHref(ThreadContext context, String prefix) {
1278
+ XmlNode currentNode = this;
1279
+ final XmlDocument doc = document(context.runtime);
1280
+ while (currentNode != doc) {
1281
+ RubyArray namespaces = currentNode.namespace_scopes(context);
1282
+ for (int i = 0; i<namespaces.size(); i++) {
1283
+ XmlNamespace namespace = (XmlNamespace) namespaces.eltInternal(i);
1284
+ if (namespace.hasPrefix(prefix)) return namespace.getHref();
1285
+ }
1286
+ IRubyObject parent = currentNode.parent(context);
1287
+ if (parent == context.nil) break;
1288
+ currentNode = (XmlNode) parent;
1289
+ }
1290
+ return null;
1291
+ }
1292
+
1293
+ @JRubyMethod
1294
+ public IRubyObject parent(ThreadContext context) {
1295
+ /*
1296
+ * Check if this node is the root node of the document.
1297
+ * If so, parent is the document.
1298
+ */
1299
+ if (node.getOwnerDocument() != null &&
1300
+ node.getOwnerDocument().getDocumentElement() == node) {
1301
+ return document(context);
1302
+ }
1303
+ return getCachedNodeOrCreate(context.runtime, node.getParentNode());
1304
+ }
1305
+
1306
+ @JRubyMethod
1307
+ public IRubyObject path(ThreadContext context) {
1308
+ return RubyString.newString(context.runtime, NokogiriHelpers.getNodeCompletePath(this.node));
1309
+ }
1310
+
1311
+ @JRubyMethod
1312
+ public IRubyObject pointer_id(ThreadContext context) {
1313
+ return RubyFixnum.newFixnum(context.runtime, this.node.hashCode());
1314
+ }
1315
+
1316
+ @JRubyMethod(visibility=Visibility.PRIVATE)
1317
+ public IRubyObject set_namespace(ThreadContext context, IRubyObject namespace) {
1318
+ if (namespace.isNil()) {
1319
+ XmlDocument doc = document(context.runtime);
1320
+ if (doc != null) {
1321
+ Node node = this.node;
1322
+ doc.getNamespaceCache().remove(node);
1323
+ this.node = NokogiriHelpers.renameNode(node, null, NokogiriHelpers.getLocalPart(node.getNodeName()));
1324
+ }
1325
+ } else {
1326
+ XmlNamespace ns = (XmlNamespace) namespace;
1327
+
1328
+ // Assigning node = ...renameNode() or not seems to make no
1329
+ // difference. Why not? -pmahoney
1330
+
1331
+ // It actually makes a great deal of difference. renameNode()
1332
+ // will operate in place if it can, but sometimes it can't.
1333
+ // The node you passed in *might* come back as you expect, but
1334
+ // it might not. It's much safer to throw away the original
1335
+ // and keep the return value. -mbklein
1336
+ String new_name = NokogiriHelpers.newQName(ns.getPrefix(), node);
1337
+ this.node = NokogiriHelpers.renameNode(node, ns.getHref(), new_name);
1338
+ }
1339
+
1340
+ clearXpathContext(getNode());
1341
+
1342
+ return this;
1343
+ }
1344
+
1345
+ @JRubyMethod(name = {"unlink", "remove"})
1346
+ public IRubyObject unlink(ThreadContext context) {
1347
+ final Node parent = node.getParentNode();
1348
+ if (parent != null) {
1349
+ parent.removeChild(node);
1350
+ clearXpathContext(parent);
1351
+ }
1352
+ return this;
1353
+ }
1354
+
1355
+ /**
1356
+ * The C-library simply returns libxml2 magic numbers. Here we
1357
+ * convert Java Xml nodes to the appropriate constant defined in
1358
+ * xml/node.rb.
1359
+ */
1360
+ @JRubyMethod(name = {"node_type", "type"})
1361
+ public IRubyObject node_type(ThreadContext context) {
1362
+ String type;
1363
+ switch (node.getNodeType()) {
1364
+ case Node.ELEMENT_NODE:
1365
+ if (this instanceof XmlElementDecl)
1366
+ type = "ELEMENT_DECL";
1367
+ else if (this instanceof XmlAttributeDecl)
1368
+ type = "ATTRIBUTE_DECL";
1369
+ else if (this instanceof XmlEntityDecl)
1370
+ type = "ENTITY_DECL";
1371
+ else
1372
+ type = "ELEMENT_NODE";
1373
+ break;
1374
+ case Node.ATTRIBUTE_NODE: type = "ATTRIBUTE_NODE"; break;
1375
+ case Node.TEXT_NODE: type = "TEXT_NODE"; break;
1376
+ case Node.CDATA_SECTION_NODE: type = "CDATA_SECTION_NODE"; break;
1377
+ case Node.ENTITY_REFERENCE_NODE: type = "ENTITY_REF_NODE"; break;
1378
+ case Node.ENTITY_NODE: type = "ENTITY_NODE"; break;
1379
+ case Node.PROCESSING_INSTRUCTION_NODE: type = "PI_NODE"; break;
1380
+ case Node.COMMENT_NODE: type = "COMMENT_NODE"; break;
1381
+ case Node.DOCUMENT_NODE:
1382
+ if (this instanceof HtmlDocument)
1383
+ type = "HTML_DOCUMENT_NODE";
1384
+ else
1385
+ type = "DOCUMENT_NODE";
1386
+ break;
1387
+ case Node.DOCUMENT_TYPE_NODE: type = "DOCUMENT_TYPE_NODE"; break;
1388
+ case Node.DOCUMENT_FRAGMENT_NODE: type = "DOCUMENT_FRAG_NODE"; break;
1389
+ case Node.NOTATION_NODE: type = "NOTATION_NODE"; break;
1390
+ default:
1391
+ return context.runtime.newFixnum(0);
1392
+ }
1393
+
1394
+ return getNokogiriClass(context.runtime, "Nokogiri::XML::Node").getConstant(type);
1395
+ }
1396
+
1397
+ @JRubyMethod
1398
+ public IRubyObject line(ThreadContext context) {
1399
+ Node root = getOwnerDocument();
1400
+ int[] counter = new int[1];
1401
+ count(root, counter);
1402
+ return RubyFixnum.newFixnum(context.runtime, counter[0]+1);
1403
+ }
1404
+
1405
+ private boolean count(Node node, int[] counter) {
1406
+ if (node == this.node) {
1407
+ return true;
1408
+ }
1409
+ NodeList list = node.getChildNodes();
1410
+ for (int i=0; i<list.getLength(); i++) {
1411
+ Node n = list.item(i);
1412
+ if (n instanceof Text
1413
+ && ((Text)n).getData().contains("\n")) {
1414
+ counter[0] += 1;
1415
+ }
1416
+ if (count(n, counter)) return true;
1417
+ }
1418
+ return false;
1419
+ }
1420
+
1421
+ @JRubyMethod
1422
+ public IRubyObject next_element(ThreadContext context) {
1423
+ Node nextNode = node.getNextSibling();
1424
+ if (nextNode == null) return context.nil;
1425
+ if (nextNode instanceof Element) {
1426
+ return getCachedNodeOrCreate(context.runtime, nextNode);
1427
+ }
1428
+ Node deeper = nextNode.getNextSibling();
1429
+ if (deeper == null) return context.nil;
1430
+ return getCachedNodeOrCreate(context.runtime, deeper);
1431
+ }
1432
+
1433
+ @JRubyMethod
1434
+ public IRubyObject previous_element(ThreadContext context) {
1435
+ Node prevNode = node.getPreviousSibling();
1436
+ if (prevNode == null) return context.nil;
1437
+ if (prevNode instanceof Element) {
1438
+ return getCachedNodeOrCreate(context.runtime, prevNode);
1439
+ }
1440
+ Node shallower = prevNode.getPreviousSibling();
1441
+ if (shallower == null) return context.nil;
1442
+ return getCachedNodeOrCreate(context.runtime, shallower);
1443
+ }
1444
+
1445
+ protected enum AdoptScheme {
1446
+ CHILD, PREV_SIBLING, NEXT_SIBLING, REPLACEMENT
1447
+ }
1448
+
1449
+ /**
1450
+ * Adopt XmlNode <code>other</code> into the document of
1451
+ * <code>this</code> using the specified scheme.
1452
+ */
1453
+ protected IRubyObject adoptAs(ThreadContext context, AdoptScheme scheme, IRubyObject other_) {
1454
+ final XmlNode other = asXmlNode(context, other_);
1455
+ // this.doc might be null since this node can be empty node.
1456
+ if (doc != null) other.setDocument(context, doc);
1457
+
1458
+ IRubyObject nodeOrTags = other;
1459
+ Node thisNode = node;
1460
+ Node otherNode = other.node;
1461
+
1462
+ try {
1463
+ Document prev = otherNode.getOwnerDocument();
1464
+ Document doc = thisNode.getOwnerDocument();
1465
+ if (doc == null && thisNode instanceof Document) {
1466
+ // we are adding the new node to a new empty document
1467
+ doc = (Document) thisNode;
1468
+ }
1469
+ clearXpathContext(prev);
1470
+ clearXpathContext(doc);
1471
+ if (doc != null && doc != otherNode.getOwnerDocument()) {
1472
+ Node ret = doc.adoptNode(otherNode);
1473
+ if (ret == null) {
1474
+ throw context.runtime.newRuntimeError("Failed to take ownership of node");
1475
+ }
1476
+ // FIXME: this is really a hack, see documentation of fixUserData() for more details.
1477
+ fixUserData(prev, ret);
1478
+ otherNode = ret;
1479
+ }
1480
+
1481
+ Node parent = thisNode.getParentNode();
1482
+
1483
+ switch (scheme) {
1484
+ case CHILD:
1485
+ Node[] children = adoptAsChild(thisNode, otherNode);
1486
+ if (children.length == 1 && otherNode == children[0]) {
1487
+ break;
1488
+ } else {
1489
+ nodeOrTags = nodeArrayToRubyArray(context.runtime, children);
1490
+ }
1491
+ break;
1492
+ case PREV_SIBLING:
1493
+ adoptAsPrevSibling(context, parent, thisNode, otherNode);
1494
+ break;
1495
+ case NEXT_SIBLING:
1496
+ adoptAsNextSibling(context, parent, thisNode, otherNode);
1497
+ break;
1498
+ case REPLACEMENT:
1499
+ adoptAsReplacement(context, parent, thisNode, otherNode);
1500
+ break;
1501
+ }
1502
+ } catch (Exception e) {
1503
+ throw context.runtime.newRuntimeError(e.toString());
1504
+ }
1505
+
1506
+ if (otherNode.getNodeType() == Node.TEXT_NODE) {
1507
+ coalesceTextNodes(context, other, scheme);
1508
+ }
1509
+
1510
+ if (this instanceof XmlDocument) {
1511
+ ((XmlDocument) this).resetNamespaceCache(context);
1512
+ }
1513
+
1514
+ other.relink_namespace(context);
1515
+
1516
+ return nodeOrTags;
1517
+ }
1518
+
1519
+ /**
1520
+ * This is a hack to fix #839. We should submit a patch to Xerces.
1521
+ * It looks like CoreDocumentImpl.adoptNode() doesn't copy
1522
+ * the user data associated with child nodes (recursively).
1523
+ */
1524
+ private static void fixUserData(Document previous, Node ret) {
1525
+ final String key = NokogiriHelpers.ENCODED_STRING;
1526
+ for (Node child = ret.getFirstChild(); child != null; child = child.getNextSibling()) {
1527
+ CoreDocumentImpl previousDocument = (CoreDocumentImpl) previous;
1528
+ child.setUserData(key, previousDocument.getUserData(child, key), null);
1529
+ fixUserData(previous, child);
1530
+ }
1531
+ }
1532
+
1533
+ private Node[] adoptAsChild(final Node parent, Node otherNode) {
1534
+ /*
1535
+ * This is a bit of a hack. C-Nokogiri allows adding a bare text node as the root element.
1536
+ * Java (and XML spec?) does not. So we wrap the text node in an element.
1537
+ */
1538
+ if (parent.getNodeType() == Node.DOCUMENT_NODE && otherNode.getNodeType() == Node.TEXT_NODE) {
1539
+ Element e = (Element) parent.getFirstChild();
1540
+ if (e == null || !e.getNodeName().equals(TEXT_WRAPPER_NAME)) {
1541
+ e = ((Document) parent).createElement(TEXT_WRAPPER_NAME);
1542
+ adoptAsChild(parent, e);
1543
+ }
1544
+ e.appendChild(otherNode);
1545
+ otherNode = e;
1546
+ } else {
1547
+ addNamespaceURIIfNeeded(otherNode);
1548
+ parent.appendChild(otherNode);
1549
+ }
1550
+ return new Node[] { otherNode };
1551
+ }
1552
+
1553
+ private void addNamespaceURIIfNeeded(Node child) {
1554
+ if (this instanceof XmlDocumentFragment && ((XmlDocumentFragment) this).getFragmentContext() != null) {
1555
+ XmlElement fragmentContext = ((XmlDocumentFragment) this).getFragmentContext();
1556
+ String namespace_uri = fragmentContext.node.getNamespaceURI();
1557
+ if (namespace_uri != null && namespace_uri.length() > 0) {
1558
+ NokogiriHelpers.renameNode(child, namespace_uri, child.getNodeName());
1559
+ }
1560
+ }
1561
+ }
1562
+
1563
+ protected void adoptAsPrevSibling(ThreadContext context,
1564
+ Node parent,
1565
+ Node thisNode, Node otherNode) {
1566
+ if (parent == null) {
1567
+ /* I'm not sure what do do here... A node with no
1568
+ * parent can't exactly have a 'sibling', so we make
1569
+ * otherNode parentless also. */
1570
+ if (otherNode.getParentNode() != null)
1571
+ otherNode.getParentNode().removeChild(otherNode);
1572
+ return;
1573
+ }
1574
+
1575
+ parent.insertBefore(otherNode, thisNode);
1576
+ }
1577
+
1578
+ protected void adoptAsNextSibling(ThreadContext context,
1579
+ Node parent,
1580
+ Node thisNode, Node otherNode) {
1581
+ if (parent == null) {
1582
+ /* I'm not sure what do do here... A node with no
1583
+ * parent can't exactly have a 'sibling', so we make
1584
+ * otherNode parentless also. */
1585
+ if (otherNode.getParentNode() != null)
1586
+ otherNode.getParentNode().removeChild(otherNode);
1587
+
1588
+ return;
1589
+ }
1590
+
1591
+ Node nextSib = thisNode.getNextSibling();
1592
+
1593
+ if (nextSib != null) {
1594
+ parent.insertBefore(otherNode, nextSib);
1595
+ } else {
1596
+ parent.appendChild(otherNode);
1597
+ }
1598
+ }
1599
+
1600
+ protected void adoptAsReplacement(ThreadContext context,
1601
+ Node parentNode,
1602
+ Node thisNode, Node otherNode) {
1603
+ if (parentNode == null) {
1604
+ /* nothing to replace? */
1605
+ return;
1606
+ }
1607
+
1608
+ try {
1609
+ parentNode.replaceChild(otherNode, thisNode);
1610
+ } catch (Exception e) {
1611
+ String prefix = "could not replace child: ";
1612
+ throw context.runtime.newRuntimeError(prefix + e.toString());
1613
+ }
1614
+ }
1615
+
1616
+ /**
1617
+ * Add <code>other</code> as a child of <code>this</code>.
1618
+ */
1619
+ @JRubyMethod(visibility=Visibility.PRIVATE)
1620
+ public IRubyObject add_child_node(ThreadContext context, IRubyObject other) {
1621
+ return adoptAs(context, AdoptScheme.CHILD, other);
1622
+ }
1623
+
1624
+ /**
1625
+ * Replace <code>this</code> with <code>other</code>.
1626
+ */
1627
+ @JRubyMethod(visibility=Visibility.PRIVATE)
1628
+ public IRubyObject replace_node(ThreadContext context, IRubyObject other) {
1629
+ return adoptAs(context, AdoptScheme.REPLACEMENT, other);
1630
+ }
1631
+
1632
+ /**
1633
+ * Add <code>other</code> as a sibling before <code>this</code>.
1634
+ */
1635
+ @JRubyMethod(visibility=Visibility.PRIVATE)
1636
+ public IRubyObject add_previous_sibling_node(ThreadContext context, IRubyObject other) {
1637
+ return adoptAs(context, AdoptScheme.PREV_SIBLING, other);
1638
+ }
1639
+
1640
+ /**
1641
+ * Add <code>other</code> as a sibling after <code>this</code>.
1642
+ */
1643
+ @JRubyMethod(visibility=Visibility.PRIVATE)
1644
+ public IRubyObject add_next_sibling_node(ThreadContext context, IRubyObject other) {
1645
+ return adoptAs(context, AdoptScheme.NEXT_SIBLING, other);
1646
+ }
1647
+
1648
+ /**
1649
+ * call-seq:
1650
+ * process_xincludes(options)
1651
+ *
1652
+ * Loads and substitutes all xinclude elements below the node. The
1653
+ * parser context will be initialized with +options+.
1654
+ *
1655
+ */
1656
+ @JRubyMethod(visibility=Visibility.PRIVATE)
1657
+ public IRubyObject process_xincludes(ThreadContext context, IRubyObject options) {
1658
+ XmlDocument xmlDocument = (XmlDocument)document(context);
1659
+ RubyArray errors = (RubyArray)xmlDocument.getInstanceVariable("@errors");
1660
+ while(errors.getLength() > 0) {
1661
+ XmlSyntaxError error = (XmlSyntaxError)errors.shift(context);
1662
+ if (error.toString().contains("Include operation failed")) {
1663
+ throw error.toThrowable();
1664
+ }
1665
+ }
1666
+ return this;
1667
+ }
1668
+
1669
+ @JRubyMethod(visibility=Visibility.PRIVATE)
1670
+ public IRubyObject clear_xpath_context(ThreadContext context) {
1671
+ clearXpathContext(getNode());
1672
+ return context.nil ;
1673
+ }
1674
+
1675
+ @SuppressWarnings("unchecked")
1676
+ @Override
1677
+ public Object toJava(final Class target) {
1678
+ if (target == Object.class || Node.class.isAssignableFrom(target)) {
1679
+ return getNode();
1680
+ }
1681
+ return super.toJava(target);
1682
+ }
1683
+
1684
+ }