nokogiri 1.11.0.rc3-java → 1.11.4-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of nokogiri might be problematic. Click here for more details.

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