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