Nokogiri_precompiled_aarch64_dedshit 1.14.5

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