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