nokogiri 1.5.0.beta.1 → 1.5.0.beta.2
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.
- data/CHANGELOG.ja.rdoc +28 -8
- data/CHANGELOG.rdoc +23 -0
- data/Manifest.txt +63 -1
- data/README.ja.rdoc +1 -1
- data/README.rdoc +22 -4
- data/Rakefile +6 -2
- data/ext/java/nokogiri/EncodingHandler.java +92 -0
- data/ext/java/nokogiri/HtmlDocument.java +116 -0
- data/ext/java/nokogiri/HtmlElementDescription.java +111 -0
- data/ext/java/nokogiri/HtmlEntityLookup.java +45 -0
- data/ext/java/nokogiri/HtmlSaxParserContext.java +218 -0
- data/ext/java/nokogiri/NokogiriService.java +370 -0
- data/ext/java/nokogiri/XmlAttr.java +147 -0
- data/ext/java/nokogiri/XmlAttributeDecl.java +98 -0
- data/ext/java/nokogiri/XmlCdata.java +50 -0
- data/ext/java/nokogiri/XmlComment.java +47 -0
- data/ext/java/nokogiri/XmlDocument.java +463 -0
- data/ext/java/nokogiri/XmlDocumentFragment.java +207 -0
- data/ext/java/nokogiri/XmlDtd.java +427 -0
- data/ext/java/nokogiri/XmlElement.java +172 -0
- data/ext/java/nokogiri/XmlElementContent.java +350 -0
- data/ext/java/nokogiri/XmlElementDecl.java +115 -0
- data/ext/java/nokogiri/XmlEntityDecl.java +129 -0
- data/ext/java/nokogiri/XmlEntityReference.java +42 -0
- data/ext/java/nokogiri/XmlNamespace.java +77 -0
- data/ext/java/nokogiri/XmlNode.java +1399 -0
- data/ext/java/nokogiri/XmlNodeSet.java +248 -0
- data/ext/java/nokogiri/XmlProcessingInstruction.java +70 -0
- data/ext/java/nokogiri/XmlReader.java +373 -0
- data/ext/java/nokogiri/XmlRelaxng.java +166 -0
- data/ext/java/nokogiri/XmlSaxParserContext.java +308 -0
- data/ext/java/nokogiri/XmlSaxPushParser.java +146 -0
- data/ext/java/nokogiri/XmlSchema.java +142 -0
- data/ext/java/nokogiri/XmlSyntaxError.java +84 -0
- data/ext/java/nokogiri/XmlText.java +96 -0
- data/ext/java/nokogiri/XmlXpathContext.java +130 -0
- data/ext/java/nokogiri/XsltStylesheet.java +126 -0
- data/ext/java/nokogiri/internals/HtmlDomParserContext.java +181 -0
- data/ext/java/nokogiri/internals/NokogiriDocumentCache.java +39 -0
- data/ext/java/nokogiri/internals/NokogiriErrorHandler.java +42 -0
- data/ext/java/nokogiri/internals/NokogiriHandler.java +251 -0
- data/ext/java/nokogiri/internals/NokogiriHelpers.java +526 -0
- data/ext/java/nokogiri/internals/NokogiriNamespaceCache.java +136 -0
- data/ext/java/nokogiri/internals/NokogiriNamespaceContext.java +80 -0
- data/ext/java/nokogiri/internals/NokogiriNonStrictErrorHandler.java +37 -0
- data/ext/java/nokogiri/internals/NokogiriNonStrictErrorHandler4NekoHtml.java +54 -0
- data/ext/java/nokogiri/internals/NokogiriStrictErrorHandler.java +49 -0
- data/ext/java/nokogiri/internals/NokogiriXPathFunction.java +88 -0
- data/ext/java/nokogiri/internals/NokogiriXPathFunctionResolver.java +23 -0
- data/ext/java/nokogiri/internals/ParserContext.java +235 -0
- data/ext/java/nokogiri/internals/PushInputStream.java +381 -0
- data/ext/java/nokogiri/internals/ReaderNode.java +431 -0
- data/ext/java/nokogiri/internals/SaveContext.java +249 -0
- data/ext/java/nokogiri/internals/SchemaErrorHandler.java +35 -0
- data/ext/java/nokogiri/internals/XmlDeclHandler.java +10 -0
- data/ext/java/nokogiri/internals/XmlDomParser.java +45 -0
- data/ext/java/nokogiri/internals/XmlDomParserContext.java +201 -0
- data/ext/java/nokogiri/internals/XmlSaxParser.java +33 -0
- data/ext/nokogiri/depend +32 -0
- data/ext/nokogiri/extconf.rb +61 -32
- data/ext/nokogiri/nokogiri.c +0 -5
- data/ext/nokogiri/nokogiri.h +2 -2
- data/ext/nokogiri/xml_document.c +5 -0
- data/ext/nokogiri/xml_libxml2_hacks.c +112 -0
- data/ext/nokogiri/xml_libxml2_hacks.h +12 -0
- data/ext/nokogiri/xml_node.c +56 -16
- data/ext/nokogiri/xml_node_set.c +7 -7
- data/ext/nokogiri/xml_reader.c +20 -1
- data/ext/nokogiri/xml_relax_ng.c +0 -7
- data/ext/nokogiri/xml_xpath_context.c +2 -0
- data/lib/isorelax.jar +0 -0
- data/lib/jing.jar +0 -0
- data/lib/nekodtd.jar +0 -0
- data/lib/nekohtml.jar +0 -0
- data/lib/nokogiri.rb +1 -2
- data/lib/nokogiri/css/generated_parser.rb +155 -148
- data/lib/nokogiri/css/generated_tokenizer.rb +2 -1
- data/lib/nokogiri/css/parser.y +3 -0
- data/lib/nokogiri/css/xpath_visitor.rb +1 -7
- data/lib/nokogiri/html.rb +2 -2
- data/lib/nokogiri/html/document_fragment.rb +7 -4
- data/lib/nokogiri/nokogiri.jar +0 -0
- data/lib/nokogiri/version.rb +3 -6
- data/lib/nokogiri/xml/builder.rb +1 -1
- data/lib/nokogiri/xml/document.rb +1 -2
- data/lib/nokogiri/xml/document_fragment.rb +7 -0
- data/lib/nokogiri/xml/node.rb +5 -3
- data/lib/nokogiri/xml/node_set.rb +25 -0
- data/lib/nokogiri/xml/reader.rb +2 -0
- data/lib/nokogiri/xml/sax/document.rb +3 -1
- data/lib/xercesImpl.jar +0 -0
- data/spec/helper.rb +3 -0
- data/spec/xml/reader_spec.rb +307 -0
- data/tasks/test.rb +1 -1
- data/test/css/test_parser.rb +11 -1
- data/test/html/sax/test_parser_context.rb +2 -2
- data/test/html/test_document.rb +2 -2
- data/test/html/test_document_fragment.rb +34 -6
- data/test/test_memory_leak.rb +2 -2
- data/test/test_reader.rb +28 -6
- data/test/test_xslt_transforms.rb +2 -3
- data/test/xml/test_attr.rb +31 -4
- data/test/xml/test_builder.rb +5 -5
- data/test/xml/test_cdata.rb +3 -3
- data/test/xml/test_document.rb +8 -8
- data/test/xml/test_document_fragment.rb +4 -12
- data/test/xml/test_node.rb +1 -1
- data/test/xml/test_node_reparenting.rb +26 -11
- data/test/xml/test_node_set.rb +38 -2
- data/test/xml/test_text.rb +11 -2
- data/test/xml/test_unparented_node.rb +1 -1
- data/test/xml/test_xpath.rb +11 -7
- metadata +68 -5
- data/lib/nokogiri/version_warning.rb +0 -14
@@ -0,0 +1,115 @@
|
|
1
|
+
package nokogiri;
|
2
|
+
|
3
|
+
import static nokogiri.internals.NokogiriHelpers.getLocalPart;
|
4
|
+
import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
|
5
|
+
import static nokogiri.internals.NokogiriHelpers.getPrefix;
|
6
|
+
|
7
|
+
import org.jruby.Ruby;
|
8
|
+
import org.jruby.RubyArray;
|
9
|
+
import org.jruby.RubyClass;
|
10
|
+
import org.jruby.anno.JRubyClass;
|
11
|
+
import org.jruby.anno.JRubyMethod;
|
12
|
+
import org.jruby.runtime.ThreadContext;
|
13
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
14
|
+
import org.w3c.dom.Node;
|
15
|
+
|
16
|
+
/**
|
17
|
+
* DTD element declaration.
|
18
|
+
*
|
19
|
+
* @author Patrick Mahoney <pat@polycrystal.org>
|
20
|
+
*/
|
21
|
+
@JRubyClass(name="Nokogiri::XML::ElementDecl", parent="Nokogiri::XML::Node")
|
22
|
+
public class XmlElementDecl extends XmlNode {
|
23
|
+
RubyArray attrDecls;
|
24
|
+
|
25
|
+
IRubyObject contentModel;
|
26
|
+
|
27
|
+
public XmlElementDecl(Ruby ruby, RubyClass klass) {
|
28
|
+
super(ruby, klass);
|
29
|
+
throw ruby.newRuntimeError("node required");
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Initialize based on an elementDecl node from a NekoDTD parsed
|
34
|
+
* DTD.
|
35
|
+
*/
|
36
|
+
public XmlElementDecl(Ruby ruby, RubyClass klass, Node elemDeclNode) {
|
37
|
+
super(ruby, klass, elemDeclNode);
|
38
|
+
attrDecls = RubyArray.newArray(ruby);
|
39
|
+
contentModel = ruby.getNil();
|
40
|
+
}
|
41
|
+
|
42
|
+
public static IRubyObject create(ThreadContext context, Node elemDeclNode) {
|
43
|
+
XmlElementDecl self =
|
44
|
+
new XmlElementDecl(context.getRuntime(),
|
45
|
+
getNokogiriClass(context.getRuntime(), "Nokogiri::XML::ElementDecl"),
|
46
|
+
elemDeclNode);
|
47
|
+
return self;
|
48
|
+
}
|
49
|
+
|
50
|
+
public IRubyObject element_name(ThreadContext context) {
|
51
|
+
return getAttribute(context, "ename");
|
52
|
+
}
|
53
|
+
|
54
|
+
public void setContentModel(IRubyObject cm) {
|
55
|
+
contentModel = cm;
|
56
|
+
}
|
57
|
+
|
58
|
+
@Override
|
59
|
+
@JRubyMethod
|
60
|
+
public IRubyObject content(ThreadContext context) {
|
61
|
+
return contentModel;
|
62
|
+
}
|
63
|
+
|
64
|
+
public boolean isEmpty() {
|
65
|
+
return "EMPTY".equals(getAttribute("model"));
|
66
|
+
}
|
67
|
+
|
68
|
+
@JRubyMethod
|
69
|
+
public IRubyObject prefix(ThreadContext context) {
|
70
|
+
String enamePrefix = getPrefix(getAttribute("ename"));
|
71
|
+
if (enamePrefix == null)
|
72
|
+
return context.getRuntime().getNil();
|
73
|
+
else
|
74
|
+
return context.getRuntime().newString(enamePrefix);
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Returns the local part of the element name.
|
79
|
+
*/
|
80
|
+
@Override
|
81
|
+
@JRubyMethod
|
82
|
+
public IRubyObject node_name(ThreadContext context) {
|
83
|
+
String ename = getLocalPart(getAttribute("ename"));
|
84
|
+
return context.getRuntime().newString(ename);
|
85
|
+
}
|
86
|
+
|
87
|
+
@Override
|
88
|
+
@JRubyMethod(name = "node_name=")
|
89
|
+
public IRubyObject node_name_set(ThreadContext context, IRubyObject name) {
|
90
|
+
throw context.getRuntime()
|
91
|
+
.newRuntimeError("cannot change name of DTD decl");
|
92
|
+
}
|
93
|
+
|
94
|
+
@Override
|
95
|
+
@JRubyMethod
|
96
|
+
public IRubyObject attribute_nodes(ThreadContext context) {
|
97
|
+
return attrDecls;
|
98
|
+
}
|
99
|
+
|
100
|
+
@Override
|
101
|
+
@JRubyMethod
|
102
|
+
public IRubyObject attribute(ThreadContext context, IRubyObject name) {
|
103
|
+
throw context.getRuntime()
|
104
|
+
.newRuntimeError("attribute by name not implemented");
|
105
|
+
}
|
106
|
+
|
107
|
+
public void appendAttrDecl(XmlAttributeDecl decl) {
|
108
|
+
attrDecls.append(decl);
|
109
|
+
}
|
110
|
+
|
111
|
+
@JRubyMethod
|
112
|
+
public IRubyObject element_type(ThreadContext context) {
|
113
|
+
return context.getRuntime().newFixnum(node.getNodeType());
|
114
|
+
}
|
115
|
+
}
|
@@ -0,0 +1,129 @@
|
|
1
|
+
package nokogiri;
|
2
|
+
|
3
|
+
import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
|
4
|
+
|
5
|
+
import org.jruby.Ruby;
|
6
|
+
import org.jruby.RubyClass;
|
7
|
+
import org.jruby.RubyFixnum;
|
8
|
+
import org.jruby.RubyNil;
|
9
|
+
import org.jruby.anno.JRubyClass;
|
10
|
+
import org.jruby.anno.JRubyMethod;
|
11
|
+
import org.jruby.runtime.ThreadContext;
|
12
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
13
|
+
import org.w3c.dom.Node;
|
14
|
+
|
15
|
+
/**
|
16
|
+
* DTD entity declaration.
|
17
|
+
*
|
18
|
+
* @author Patrick Mahoney <pat@polycrystal.org>
|
19
|
+
*/
|
20
|
+
@JRubyClass(name="Nokogiri::XML::EntityDecl", parent="Nokogiri::XML::Node")
|
21
|
+
public class XmlEntityDecl extends XmlNode {
|
22
|
+
public static final int INTERNAL_GENERAL = 1;
|
23
|
+
public static final int EXTERNAL_GENERAL_PARSED = 2;
|
24
|
+
public static final int EXTERNAL_GENERAL_UNPARSED = 3;
|
25
|
+
public static final int INTERNAL_PARAMETER = 4;
|
26
|
+
public static final int EXTERNAL_PARAMETER = 5;
|
27
|
+
public static final int INTERNAL_PREDEFINED = 6;
|
28
|
+
|
29
|
+
private IRubyObject entityType;
|
30
|
+
private IRubyObject name;
|
31
|
+
private IRubyObject external_id;
|
32
|
+
private IRubyObject system_id;
|
33
|
+
private IRubyObject content;
|
34
|
+
|
35
|
+
public XmlEntityDecl(Ruby ruby, RubyClass klass) {
|
36
|
+
super(ruby, klass);
|
37
|
+
throw ruby.newRuntimeError("node required");
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Initialize based on an entityDecl node from a NekoDTD parsed
|
42
|
+
* DTD.
|
43
|
+
*/
|
44
|
+
public XmlEntityDecl(Ruby ruby, RubyClass klass, Node entDeclNode) {
|
45
|
+
super(ruby, klass, entDeclNode);
|
46
|
+
entityType = RubyFixnum.newFixnum(ruby, XmlEntityDecl.INTERNAL_GENERAL);
|
47
|
+
name = external_id = system_id = content = ruby.getNil();
|
48
|
+
}
|
49
|
+
|
50
|
+
public XmlEntityDecl(Ruby ruby, RubyClass klass, Node entDeclNode, IRubyObject[] argv) {
|
51
|
+
super(ruby, klass, entDeclNode);
|
52
|
+
name = argv[0];
|
53
|
+
entityType = RubyFixnum.newFixnum(ruby, XmlEntityDecl.INTERNAL_GENERAL);
|
54
|
+
external_id = system_id = content = ruby.getNil();
|
55
|
+
if (argv.length > 1) entityType = argv[1];
|
56
|
+
if (argv.length > 4) {
|
57
|
+
external_id = argv[2];
|
58
|
+
system_id = argv[3];
|
59
|
+
content = argv[4];
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
public static IRubyObject create(ThreadContext context, Node entDeclNode) {
|
64
|
+
XmlEntityDecl self =
|
65
|
+
new XmlEntityDecl(context.getRuntime(),
|
66
|
+
getNokogiriClass(context.getRuntime(), "Nokogiri::XML::EntityDecl"),
|
67
|
+
entDeclNode);
|
68
|
+
return self;
|
69
|
+
}
|
70
|
+
|
71
|
+
// when entity is created by create_entity method
|
72
|
+
public static IRubyObject create(ThreadContext context, Node entDeclNode, IRubyObject[] argv) {
|
73
|
+
XmlEntityDecl self =
|
74
|
+
new XmlEntityDecl(context.getRuntime(),
|
75
|
+
getNokogiriClass(context.getRuntime(), "Nokogiri::XML::EntityDecl"),
|
76
|
+
entDeclNode, argv);
|
77
|
+
return self;
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Returns the local part of the element name.
|
82
|
+
*/
|
83
|
+
@Override
|
84
|
+
@JRubyMethod
|
85
|
+
public IRubyObject node_name(ThreadContext context) {
|
86
|
+
IRubyObject value = getAttribute(context, "name");
|
87
|
+
if (value instanceof RubyNil) value = name;
|
88
|
+
return value;
|
89
|
+
}
|
90
|
+
|
91
|
+
@Override
|
92
|
+
@JRubyMethod(name = "node_name=")
|
93
|
+
public IRubyObject node_name_set(ThreadContext context, IRubyObject name) {
|
94
|
+
throw context.getRuntime()
|
95
|
+
.newRuntimeError("cannot change name of DTD decl");
|
96
|
+
}
|
97
|
+
|
98
|
+
@JRubyMethod
|
99
|
+
public IRubyObject content(ThreadContext context) {
|
100
|
+
IRubyObject value = getAttribute(context, "value");
|
101
|
+
if (value instanceof RubyNil) value = content;
|
102
|
+
return value;
|
103
|
+
}
|
104
|
+
|
105
|
+
// TODO: what is content vs. original_content?
|
106
|
+
@JRubyMethod
|
107
|
+
public IRubyObject original_content(ThreadContext context) {
|
108
|
+
return getAttribute(context, "value");
|
109
|
+
}
|
110
|
+
|
111
|
+
@JRubyMethod
|
112
|
+
public IRubyObject system_id(ThreadContext context) {
|
113
|
+
IRubyObject value = getAttribute(context, "sysid");
|
114
|
+
if (value instanceof RubyNil) value = system_id;
|
115
|
+
return value;
|
116
|
+
}
|
117
|
+
|
118
|
+
@JRubyMethod
|
119
|
+
public IRubyObject external_id(ThreadContext context) {
|
120
|
+
IRubyObject value = getAttribute(context, "pubid");
|
121
|
+
if (value instanceof RubyNil) value = external_id;
|
122
|
+
return value;
|
123
|
+
}
|
124
|
+
|
125
|
+
@JRubyMethod
|
126
|
+
public IRubyObject entity_type(ThreadContext context) {
|
127
|
+
return entityType;
|
128
|
+
}
|
129
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
package nokogiri;
|
2
|
+
|
3
|
+
import static nokogiri.internals.NokogiriHelpers.rubyStringToString;
|
4
|
+
|
5
|
+
import org.jruby.Ruby;
|
6
|
+
import org.jruby.RubyClass;
|
7
|
+
import org.jruby.anno.JRubyClass;
|
8
|
+
import org.jruby.runtime.ThreadContext;
|
9
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
10
|
+
import org.w3c.dom.Document;
|
11
|
+
import org.w3c.dom.Node;
|
12
|
+
|
13
|
+
/**
|
14
|
+
*
|
15
|
+
* @author sergio
|
16
|
+
* @author Patrick Mahoney <pat@polycrystal.org>
|
17
|
+
*/
|
18
|
+
@JRubyClass(name="Nokogiri::XML::EntityReference", parent="Nokogiri::XML::Node")
|
19
|
+
public class XmlEntityReference extends XmlNode{
|
20
|
+
|
21
|
+
public XmlEntityReference(Ruby ruby, RubyClass klazz) {
|
22
|
+
super(ruby, klazz);
|
23
|
+
}
|
24
|
+
|
25
|
+
public XmlEntityReference(Ruby ruby, RubyClass klass, Node node) {
|
26
|
+
super(ruby, klass, node);
|
27
|
+
}
|
28
|
+
|
29
|
+
protected void init(ThreadContext context, IRubyObject[] args) {
|
30
|
+
if (args.length < 2) {
|
31
|
+
throw getRuntime().newArgumentError(args.length, 2);
|
32
|
+
}
|
33
|
+
|
34
|
+
IRubyObject doc = args[0];
|
35
|
+
IRubyObject name = args[1];
|
36
|
+
|
37
|
+
Document document = ((XmlNode) doc).getOwnerDocument();
|
38
|
+
Node node = document.createEntityReference(rubyStringToString(name));
|
39
|
+
setNode(node);
|
40
|
+
}
|
41
|
+
|
42
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
package nokogiri;
|
2
|
+
|
3
|
+
import static nokogiri.internals.NokogiriHelpers.getLocalNameForNamespace;
|
4
|
+
import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
|
5
|
+
import nokogiri.internals.SaveContext;
|
6
|
+
|
7
|
+
import org.jruby.Ruby;
|
8
|
+
import org.jruby.RubyClass;
|
9
|
+
import org.jruby.RubyObject;
|
10
|
+
import org.jruby.anno.JRubyClass;
|
11
|
+
import org.jruby.anno.JRubyMethod;
|
12
|
+
import org.jruby.runtime.ThreadContext;
|
13
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
14
|
+
import org.w3c.dom.Node;
|
15
|
+
|
16
|
+
/**
|
17
|
+
*
|
18
|
+
* @author serabe
|
19
|
+
*/
|
20
|
+
@JRubyClass(name="Nokogiri::XML::Namespace")
|
21
|
+
public class XmlNamespace extends RubyObject {
|
22
|
+
|
23
|
+
private IRubyObject prefix;
|
24
|
+
private IRubyObject href;
|
25
|
+
|
26
|
+
public XmlNamespace(Ruby ruby, RubyClass klazz) {
|
27
|
+
super(ruby, klazz);
|
28
|
+
}
|
29
|
+
|
30
|
+
public XmlNamespace(Ruby ruby, String prefix, String href) {
|
31
|
+
this(ruby, getNokogiriClass(ruby, "Nokogiri::XML::Namespace"), prefix, href);
|
32
|
+
}
|
33
|
+
|
34
|
+
public XmlNamespace(Ruby ruby, RubyClass klazz, String prefix, String href) {
|
35
|
+
super(ruby, klazz);
|
36
|
+
this.prefix = (prefix == null) ? ruby.getNil() : ruby.newString(prefix);
|
37
|
+
this.href = (href == null) ? ruby.getNil() : ruby.newString(href);
|
38
|
+
}
|
39
|
+
|
40
|
+
public XmlNamespace(Ruby ruby, IRubyObject prefix, IRubyObject href) {
|
41
|
+
this(ruby, getNokogiriClass(ruby, "Nokogiri::XML::Namespace"), prefix, href);
|
42
|
+
}
|
43
|
+
|
44
|
+
public XmlNamespace(Ruby ruby, RubyClass klazz, IRubyObject prefix, IRubyObject href) {
|
45
|
+
super(ruby, klazz);
|
46
|
+
this.prefix = prefix;
|
47
|
+
this.href = href;
|
48
|
+
}
|
49
|
+
|
50
|
+
public static XmlNamespace fromNode(Ruby ruby, Node node) {
|
51
|
+
String localName = getLocalNameForNamespace(node.getNodeName());
|
52
|
+
|
53
|
+
return new XmlNamespace(ruby, getNokogiriClass(ruby, "Nokogiri::XML::Namespace"), localName, node.getNodeValue());
|
54
|
+
}
|
55
|
+
|
56
|
+
public boolean isEmpty() {
|
57
|
+
return this.prefix.isNil() && this.href.isNil();
|
58
|
+
}
|
59
|
+
|
60
|
+
public void setDocument(IRubyObject doc) {
|
61
|
+
this.setInstanceVariable("@document", doc);
|
62
|
+
}
|
63
|
+
|
64
|
+
@JRubyMethod
|
65
|
+
public IRubyObject href(ThreadContext context) {
|
66
|
+
return this.href;
|
67
|
+
}
|
68
|
+
|
69
|
+
@JRubyMethod
|
70
|
+
public IRubyObject prefix(ThreadContext context) {
|
71
|
+
return this.prefix;
|
72
|
+
}
|
73
|
+
|
74
|
+
public void saveContent(ThreadContext context, SaveContext ctx) {
|
75
|
+
ctx.append(" " + prefix + "=\"" + href + "\"");
|
76
|
+
}
|
77
|
+
}
|
@@ -0,0 +1,1399 @@
|
|
1
|
+
package nokogiri;
|
2
|
+
|
3
|
+
import static java.lang.Math.max;
|
4
|
+
import static nokogiri.internals.NokogiriHelpers.getCachedNodeOrCreate;
|
5
|
+
import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
|
6
|
+
import static nokogiri.internals.NokogiriHelpers.nodeArrayToRubyArray;
|
7
|
+
import static nokogiri.internals.NokogiriHelpers.nonEmptyStringOrNil;
|
8
|
+
import static nokogiri.internals.NokogiriHelpers.rubyStringToString;
|
9
|
+
import static nokogiri.internals.NokogiriHelpers.stringOrNil;
|
10
|
+
|
11
|
+
import java.io.ByteArrayInputStream;
|
12
|
+
import java.io.InputStream;
|
13
|
+
import java.util.ArrayList;
|
14
|
+
import java.util.List;
|
15
|
+
import java.util.regex.Matcher;
|
16
|
+
|
17
|
+
import nokogiri.internals.HtmlDomParserContext;
|
18
|
+
import nokogiri.internals.NokogiriHelpers;
|
19
|
+
import nokogiri.internals.NokogiriNamespaceCache;
|
20
|
+
import nokogiri.internals.NokogiriNamespaceContext;
|
21
|
+
import nokogiri.internals.SaveContext;
|
22
|
+
import nokogiri.internals.XmlDomParserContext;
|
23
|
+
|
24
|
+
import org.jruby.Ruby;
|
25
|
+
import org.jruby.RubyArray;
|
26
|
+
import org.jruby.RubyClass;
|
27
|
+
import org.jruby.RubyFixnum;
|
28
|
+
import org.jruby.RubyModule;
|
29
|
+
import org.jruby.RubyObject;
|
30
|
+
import org.jruby.RubyString;
|
31
|
+
import org.jruby.anno.JRubyClass;
|
32
|
+
import org.jruby.anno.JRubyMethod;
|
33
|
+
import org.jruby.javasupport.JavaUtil;
|
34
|
+
import org.jruby.javasupport.util.RuntimeHelpers;
|
35
|
+
import org.jruby.runtime.Block;
|
36
|
+
import org.jruby.runtime.ThreadContext;
|
37
|
+
import org.jruby.runtime.Visibility;
|
38
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
39
|
+
import org.w3c.dom.Attr;
|
40
|
+
import org.w3c.dom.Document;
|
41
|
+
import org.w3c.dom.Element;
|
42
|
+
import org.w3c.dom.NamedNodeMap;
|
43
|
+
import org.w3c.dom.Node;
|
44
|
+
import org.w3c.dom.NodeList;
|
45
|
+
import org.w3c.dom.Text;
|
46
|
+
|
47
|
+
@JRubyClass(name="Nokogiri::XML::Node")
|
48
|
+
public class XmlNode extends RubyObject {
|
49
|
+
|
50
|
+
/** The underlying Node object. */
|
51
|
+
protected Node node;
|
52
|
+
|
53
|
+
/* Cached objects */
|
54
|
+
protected IRubyObject content = null;
|
55
|
+
protected IRubyObject doc = null;
|
56
|
+
protected IRubyObject name = null;
|
57
|
+
|
58
|
+
/*
|
59
|
+
* Taken from http://ejohn.org/blog/comparing-document-position/
|
60
|
+
* Used for compareDocumentPosition.
|
61
|
+
* <ironic>Thanks to both java api and w3 doc for its helpful documentation</ironic>
|
62
|
+
*/
|
63
|
+
|
64
|
+
protected static final int IDENTICAL_ELEMENTS = 0;
|
65
|
+
protected static final int IN_DIFFERENT_DOCUMENTS = 1;
|
66
|
+
protected static final int SECOND_PRECEDES_FIRST = 2;
|
67
|
+
protected static final int FIRST_PRECEDES_SECOND = 4;
|
68
|
+
protected static final int SECOND_CONTAINS_FIRST = 8;
|
69
|
+
protected static final int FIRST_CONTAINS_SECOND = 16;
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Cast <code>node</code> to an XmlNode or raise a type error
|
73
|
+
* in <code>context</code>.
|
74
|
+
*/
|
75
|
+
protected static XmlNode asXmlNode(ThreadContext context, IRubyObject node) {
|
76
|
+
if (node == null || !(node instanceof XmlNode)) {
|
77
|
+
Ruby ruby = context.getRuntime();
|
78
|
+
throw ruby.newTypeError(node, getNokogiriClass(ruby, "Nokogiri::XML::Node"));
|
79
|
+
} else {
|
80
|
+
return (XmlNode) node;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Cast <code>node</code> to an XmlNode, or null if RubyNil, or
|
86
|
+
* raise a type error in <code>context</code>.
|
87
|
+
*/
|
88
|
+
protected static XmlNode asXmlNodeOrNull(ThreadContext context, IRubyObject node) {
|
89
|
+
if (node == null || node.isNil()) {
|
90
|
+
return null;
|
91
|
+
} else {
|
92
|
+
return asXmlNode(context, node);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Coalesce to adjacent TextNodes.
|
98
|
+
* @param context
|
99
|
+
* @param prev Previous node to cur.
|
100
|
+
* @param cur Next node to prev.
|
101
|
+
*/
|
102
|
+
public static void coalesceTextNodes(ThreadContext context, IRubyObject prev, IRubyObject cur) {
|
103
|
+
XmlNode p = asXmlNode(context, prev);
|
104
|
+
XmlNode c = asXmlNode(context, cur);
|
105
|
+
|
106
|
+
Node pNode = p.node;
|
107
|
+
Node cNode = c.node;
|
108
|
+
|
109
|
+
pNode.setNodeValue(pNode.getNodeValue()+cNode.getNodeValue());
|
110
|
+
p.content = null; // clear cached content
|
111
|
+
|
112
|
+
c.assimilateXmlNode(context, p);
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Coalesce text nodes around <code>anchorNode</code>. If
|
117
|
+
* <code>anchorNode</code> has siblings (previous or next) that
|
118
|
+
* are text nodes, the content will be merged into
|
119
|
+
* <code>anchorNode</code> and the redundant nodes will be removed
|
120
|
+
* from the DOM.
|
121
|
+
*
|
122
|
+
* To match libxml behavior (?) the final content of
|
123
|
+
* <code>anchorNode</code> and any removed nodes will be
|
124
|
+
* identical.
|
125
|
+
*
|
126
|
+
* @param context
|
127
|
+
* @param anchorNode
|
128
|
+
*/
|
129
|
+
protected static void coalesceTextNodes(ThreadContext context,
|
130
|
+
IRubyObject anchorNode) {
|
131
|
+
XmlNode xa = asXmlNode(context, anchorNode);
|
132
|
+
|
133
|
+
XmlNode xp = asXmlNodeOrNull(context, xa.previous_sibling(context));
|
134
|
+
XmlNode xn = asXmlNodeOrNull(context, xa.next_sibling(context));
|
135
|
+
|
136
|
+
Node p = xp == null ? null : xp.node;
|
137
|
+
Node a = xa.node;
|
138
|
+
Node n = xn == null ? null : xn.node;
|
139
|
+
|
140
|
+
Node parent = a.getParentNode();
|
141
|
+
|
142
|
+
if (p != null && p.getNodeType() == Node.TEXT_NODE) {
|
143
|
+
xa.setContent(p.getNodeValue() + a.getNodeValue());
|
144
|
+
parent.removeChild(p);
|
145
|
+
xp.assimilateXmlNode(context, xa);
|
146
|
+
}
|
147
|
+
if (n != null && n.getNodeType() == Node.TEXT_NODE) {
|
148
|
+
xa.setContent(a.getNodeValue() + n.getNodeValue());
|
149
|
+
parent.removeChild(n);
|
150
|
+
xn.assimilateXmlNode(context, xa);
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* This is the allocator for XmlNode class. It should only be
|
156
|
+
* called from Ruby code.
|
157
|
+
*/
|
158
|
+
public XmlNode(Ruby ruby, RubyClass cls) {
|
159
|
+
this(ruby, cls, null);
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* This is a constructor to create an XmlNode from an already
|
164
|
+
* existing node. It may be called by Java code.
|
165
|
+
*/
|
166
|
+
public XmlNode(Ruby ruby, RubyClass cls, Node node) {
|
167
|
+
super(ruby, cls);
|
168
|
+
this.node = node;
|
169
|
+
|
170
|
+
if (node != null) {
|
171
|
+
resetCache();
|
172
|
+
|
173
|
+
if (node.getNodeType() != Node.DOCUMENT_NODE) {
|
174
|
+
doc = document(ruby.getCurrentContext());
|
175
|
+
|
176
|
+
if (doc != null) {
|
177
|
+
RuntimeHelpers.invoke(ruby.getCurrentContext(), doc, "decorate", this);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
182
|
+
|
183
|
+
private void resetCache() {
|
184
|
+
node.setUserData(NokogiriHelpers.CACHED_NODE, this, null);
|
185
|
+
}
|
186
|
+
|
187
|
+
/**
|
188
|
+
* Allocate a new object, perform initialization, call that
|
189
|
+
* object's initialize method, and call any block passing the
|
190
|
+
* object as the only argument. If <code>cls</code> is
|
191
|
+
* Nokogiri::XML::Node, creates a new Nokogiri::XML::Element
|
192
|
+
* instead.
|
193
|
+
*
|
194
|
+
* This static method seems to be inherited, strangely enough.
|
195
|
+
* E.g. creating a new XmlAttr from Ruby code calls this method if
|
196
|
+
* XmlAttr does not define its own 'new' method.
|
197
|
+
*
|
198
|
+
* Since there is some Java bookkeeping that always needs to
|
199
|
+
* happen, we don't define the 'initialize' method in Java because
|
200
|
+
* we'd have to count on subclasses calling 'super'.
|
201
|
+
*
|
202
|
+
* The main consequence of this is that every subclass needs to
|
203
|
+
* define its own 'new' method.
|
204
|
+
*
|
205
|
+
* As a convenience, this method does the following:
|
206
|
+
*
|
207
|
+
* <ul>
|
208
|
+
*
|
209
|
+
* <li>allocates a new object using the allocator assigned to
|
210
|
+
* <code>cls</code></li>
|
211
|
+
*
|
212
|
+
* <li>calls the Java method init(); subclasses can override this,
|
213
|
+
* otherwise they should implement a specific 'new' method</li>
|
214
|
+
*
|
215
|
+
* <li>invokes the Ruby initializer</li>
|
216
|
+
*
|
217
|
+
* <li>if a block is given, calls the block with the new node as
|
218
|
+
* the argument</li>
|
219
|
+
*
|
220
|
+
* </ul>
|
221
|
+
*
|
222
|
+
* -pmahoney
|
223
|
+
*/
|
224
|
+
@JRubyMethod(name = "new", meta = true, rest = true)
|
225
|
+
public static IRubyObject rbNew(ThreadContext context, IRubyObject cls,
|
226
|
+
IRubyObject[] args, Block block) {
|
227
|
+
Ruby ruby = context.getRuntime();
|
228
|
+
RubyClass klazz = (RubyClass) cls;
|
229
|
+
|
230
|
+
if (cls.equals(getNokogiriClass(ruby, "Nokogiri::XML::Node"))) {
|
231
|
+
klazz = getNokogiriClass(ruby, "Nokogiri::XML::Element");
|
232
|
+
}
|
233
|
+
|
234
|
+
XmlNode node = (XmlNode) klazz.allocate();
|
235
|
+
node.init(context, args);
|
236
|
+
node.callInit(args, block);
|
237
|
+
if (node.node == null) System.out.println("NODE IS NULL");
|
238
|
+
if (block.isGiven()) block.call(context, node);
|
239
|
+
return node;
|
240
|
+
}
|
241
|
+
|
242
|
+
/**
|
243
|
+
* Initialize the object from Ruby arguments. Should be
|
244
|
+
* overridden by subclasses. Should check for a minimum number of
|
245
|
+
* args but not for an exact number. Any extra args will then be
|
246
|
+
* passed to 'initialize'. The way 'new' and this 'init' function
|
247
|
+
* interact means that subclasses cannot arbitrarily change the
|
248
|
+
* require aruments by defining an 'initialize' method. This is
|
249
|
+
* how the C libxml wrapper works also.
|
250
|
+
*
|
251
|
+
* As written it performs initialization for a new Element with
|
252
|
+
* the given <code>name</code> within the document
|
253
|
+
* <code>doc</code>. So XmlElement need not override this. This
|
254
|
+
* implementation cannot be moved to XmlElement however, because
|
255
|
+
* subclassing XmlNode must result in something that behaves much
|
256
|
+
* like XmlElement.
|
257
|
+
*/
|
258
|
+
protected void init(ThreadContext context, IRubyObject[] args) {
|
259
|
+
if (args.length < 2)
|
260
|
+
throw context.getRuntime().newArgumentError(args.length, 2);
|
261
|
+
|
262
|
+
IRubyObject name = args[0];
|
263
|
+
IRubyObject doc = args[1];
|
264
|
+
|
265
|
+
Document document = asXmlNode(context, doc).getOwnerDocument();
|
266
|
+
if (document == null) {
|
267
|
+
throw getRuntime().newArgumentError("node must have owner document");
|
268
|
+
}
|
269
|
+
XmlDocument xmlDoc =
|
270
|
+
(XmlDocument) getCachedNodeOrCreate(getRuntime(), document);
|
271
|
+
|
272
|
+
Element element =
|
273
|
+
document.createElementNS(null, rubyStringToString(name));
|
274
|
+
setNode(element);
|
275
|
+
setDocument(xmlDoc);
|
276
|
+
RuntimeHelpers.invoke(context, xmlDoc, "decorate", this);
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* Set the underlying node of this node to the underlying node of
|
281
|
+
* <code>otherNode</code>.
|
282
|
+
*
|
283
|
+
* FIXME: also update the cached node?
|
284
|
+
*/
|
285
|
+
protected void assimilateXmlNode(ThreadContext context, IRubyObject otherNode) {
|
286
|
+
XmlNode toAssimilate = asXmlNode(context, otherNode);
|
287
|
+
|
288
|
+
this.node = toAssimilate.node;
|
289
|
+
content = null; // clear cache
|
290
|
+
}
|
291
|
+
|
292
|
+
/**
|
293
|
+
* See org.w3.dom.Node#normalize.
|
294
|
+
*/
|
295
|
+
public void normalize() {
|
296
|
+
node.normalize();
|
297
|
+
}
|
298
|
+
|
299
|
+
public Node getNode() {
|
300
|
+
return node;
|
301
|
+
}
|
302
|
+
|
303
|
+
public static Node getNodeFromXmlNode(ThreadContext context, IRubyObject xmlNode) {
|
304
|
+
return asXmlNode(context, xmlNode).node;
|
305
|
+
}
|
306
|
+
|
307
|
+
protected String indentString(IRubyObject indentStringObject, String xml) {
|
308
|
+
String[] lines = xml.split("\n");
|
309
|
+
|
310
|
+
if(lines.length <= 1) return xml;
|
311
|
+
|
312
|
+
String[] resultLines = new String[lines.length];
|
313
|
+
|
314
|
+
String curLine;
|
315
|
+
boolean closingTag = false;
|
316
|
+
String indentString = (String)indentStringObject.toJava(String.class);
|
317
|
+
int lengthInd = indentString.length();
|
318
|
+
StringBuffer curInd = new StringBuffer();
|
319
|
+
|
320
|
+
resultLines[0] = lines[0];
|
321
|
+
|
322
|
+
for(int i = 1; i < lines.length; i++) {
|
323
|
+
|
324
|
+
curLine = lines[i].trim();
|
325
|
+
|
326
|
+
if(curLine.length() == 0) continue;
|
327
|
+
|
328
|
+
if(curLine.startsWith("</")) {
|
329
|
+
closingTag = true;
|
330
|
+
curInd.setLength(max(0,curInd.length() - lengthInd));
|
331
|
+
}
|
332
|
+
|
333
|
+
resultLines[i] = curInd.toString() + curLine;
|
334
|
+
|
335
|
+
if(!curLine.endsWith("/>") && !closingTag) {
|
336
|
+
curInd.append(indentString);
|
337
|
+
}
|
338
|
+
|
339
|
+
closingTag = false;
|
340
|
+
}
|
341
|
+
|
342
|
+
StringBuffer result = new StringBuffer();
|
343
|
+
for(int i = 0; i < resultLines.length; i++) {
|
344
|
+
result.append(resultLines[i]);
|
345
|
+
result.append("\n");
|
346
|
+
}
|
347
|
+
|
348
|
+
return result.toString();
|
349
|
+
}
|
350
|
+
|
351
|
+
public boolean isComment() { return false; }
|
352
|
+
|
353
|
+
public boolean isElement() { return false; }
|
354
|
+
|
355
|
+
public boolean isProcessingInstruction() { return false; }
|
356
|
+
|
357
|
+
/**
|
358
|
+
* Return the string value of the attribute <code>key</code> or
|
359
|
+
* nil.
|
360
|
+
*
|
361
|
+
* Only applies where the underlying Node is an Element node, but
|
362
|
+
* implemented here in XmlNode because not all nodes with
|
363
|
+
* underlying Element nodes subclass XmlElement, such as the DTD
|
364
|
+
* declarations like XmlElementDecl.
|
365
|
+
*/
|
366
|
+
protected IRubyObject getAttribute(ThreadContext context, String key) {
|
367
|
+
return getAttribute(context.getRuntime(), key);
|
368
|
+
}
|
369
|
+
|
370
|
+
protected IRubyObject getAttribute(Ruby runtime, String key) {
|
371
|
+
String value = getAttribute(key);
|
372
|
+
return nonEmptyStringOrNil(runtime, value);
|
373
|
+
}
|
374
|
+
|
375
|
+
protected String getAttribute(String key) {
|
376
|
+
if (node.getNodeType() != Node.ELEMENT_NODE) return null;
|
377
|
+
|
378
|
+
String value = ((Element)node).getAttribute(key);
|
379
|
+
return value.length() == 0 ? null : value;
|
380
|
+
}
|
381
|
+
|
382
|
+
|
383
|
+
public void post_add_child(ThreadContext context, XmlNode current, XmlNode child) {
|
384
|
+
}
|
385
|
+
|
386
|
+
public void relink_namespace(ThreadContext context) {
|
387
|
+
//this should delegate to subclasses' implementation
|
388
|
+
}
|
389
|
+
|
390
|
+
public void saveContent(ThreadContext context, SaveContext ctx) {
|
391
|
+
}
|
392
|
+
|
393
|
+
public void setName(IRubyObject name) {
|
394
|
+
this.name = name;
|
395
|
+
}
|
396
|
+
|
397
|
+
public void setDocument(IRubyObject doc) {
|
398
|
+
this.doc = doc;
|
399
|
+
}
|
400
|
+
|
401
|
+
protected void setNode(Node node) {
|
402
|
+
this.node = node;
|
403
|
+
resetCache();
|
404
|
+
}
|
405
|
+
|
406
|
+
public void updateNodeNamespaceIfNecessary(ThreadContext context, XmlNamespace ns) {
|
407
|
+
String oldPrefix = this.node.getPrefix();
|
408
|
+
String uri = (String)ns.href(context).toJava(String.class);
|
409
|
+
|
410
|
+
/*
|
411
|
+
* Update if both prefixes are null or equal
|
412
|
+
*/
|
413
|
+
boolean update = (oldPrefix == null && ns.prefix(context).isNil()) ||
|
414
|
+
(oldPrefix != null && !ns.prefix(context).isNil()
|
415
|
+
&& oldPrefix.equals((String)ns.prefix(context).toJava(String.class)));
|
416
|
+
|
417
|
+
if(update) {
|
418
|
+
this.node.getOwnerDocument().renameNode(this.node, uri, this.node.getNodeName());
|
419
|
+
}
|
420
|
+
}
|
421
|
+
|
422
|
+
protected IRubyObject getNodeName(ThreadContext context) {
|
423
|
+
if (name != null) return name;
|
424
|
+
String str = null;
|
425
|
+
|
426
|
+
if (this.name == null && node != null) {
|
427
|
+
str = node.getNodeName();
|
428
|
+
str = NokogiriHelpers.getLocalPart(str);
|
429
|
+
}
|
430
|
+
if (str == null) str = "";
|
431
|
+
name = JavaUtil.convertJavaToUsableRubyObject(context.getRuntime(), str);
|
432
|
+
return name;
|
433
|
+
}
|
434
|
+
|
435
|
+
protected void saveNodeListContent(ThreadContext context, XmlNodeSet list, SaveContext ctx) {
|
436
|
+
saveNodeListContent(context, (RubyArray) list.to_a(context), ctx);
|
437
|
+
}
|
438
|
+
|
439
|
+
protected void saveNodeListContent(ThreadContext context, RubyArray array, SaveContext ctx) {
|
440
|
+
int length = array.getLength();
|
441
|
+
|
442
|
+
boolean formatIndentation = ctx.format() && ctx.indentString()!=null;
|
443
|
+
|
444
|
+
for(int i = 0; i < length; i++) {
|
445
|
+
Object item = array.get(i);
|
446
|
+
if (item instanceof XmlNode) {
|
447
|
+
XmlNode cur = (XmlNode) item;
|
448
|
+
|
449
|
+
// if(formatIndentation &&
|
450
|
+
// (cur.isElement() || cur.isComment() || cur.isProcessingInstruction())) {
|
451
|
+
// ctx.append(ctx.getCurrentIndentString());
|
452
|
+
// }
|
453
|
+
|
454
|
+
cur.saveContent(context, ctx);
|
455
|
+
} else if (item instanceof XmlNamespace) {
|
456
|
+
XmlNamespace cur = (XmlNamespace)item;
|
457
|
+
cur.saveContent(context, ctx);
|
458
|
+
}
|
459
|
+
|
460
|
+
// if(ctx.format()) ctx.append("\n");
|
461
|
+
}
|
462
|
+
}
|
463
|
+
|
464
|
+
/**
|
465
|
+
* Add a namespace definition to this node. To the underlying
|
466
|
+
* node, add an attribute of the form
|
467
|
+
* <code>xmlns:prefix="uri"</code>.
|
468
|
+
*/
|
469
|
+
@JRubyMethod(name = {"add_namespace_definition", "add_namespace"})
|
470
|
+
public IRubyObject add_namespace_definition(ThreadContext context,
|
471
|
+
IRubyObject prefix,
|
472
|
+
IRubyObject href) {
|
473
|
+
String prefixString = prefix.isNil() ? "" : rubyStringToString(prefix);
|
474
|
+
String hrefString = rubyStringToString(href);
|
475
|
+
XmlDocument xmlDocument = (XmlDocument) doc;
|
476
|
+
Node namespaceOwner;
|
477
|
+
if (node.getNodeType() == Node.ELEMENT_NODE) namespaceOwner = node;
|
478
|
+
else if (node.getNodeType() == Node.ATTRIBUTE_NODE) namespaceOwner = ((Attr)node).getOwnerElement();
|
479
|
+
else namespaceOwner = node.getParentNode();
|
480
|
+
XmlNamespace ns = xmlDocument.getNamespaceCache().put(context.getRuntime(), prefixString, hrefString, namespaceOwner, xmlDocument);
|
481
|
+
if (node != namespaceOwner) {
|
482
|
+
node.getOwnerDocument().renameNode(node, hrefString, prefixString + node.getLocalName());
|
483
|
+
}
|
484
|
+
|
485
|
+
return ns;
|
486
|
+
}
|
487
|
+
|
488
|
+
@JRubyMethod(name = {"attribute", "attr"})
|
489
|
+
public IRubyObject attribute(ThreadContext context, IRubyObject name){
|
490
|
+
NamedNodeMap attrs = this.node.getAttributes();
|
491
|
+
Node attr = attrs.getNamedItem((String)name.toJava(String.class));
|
492
|
+
if(attr == null) {
|
493
|
+
return context.getRuntime().newString(ERR_INSECURE_SET_INST_VAR);
|
494
|
+
}
|
495
|
+
return getCachedNodeOrCreate(context.getRuntime(), attr);
|
496
|
+
}
|
497
|
+
|
498
|
+
@JRubyMethod
|
499
|
+
public IRubyObject attribute_nodes(ThreadContext context) {
|
500
|
+
NamedNodeMap nodeMap = this.node.getAttributes();
|
501
|
+
|
502
|
+
Ruby ruby = context.getRuntime();
|
503
|
+
if(nodeMap == null){
|
504
|
+
return ruby.newEmptyArray();
|
505
|
+
}
|
506
|
+
|
507
|
+
RubyArray attr = ruby.newArray();
|
508
|
+
|
509
|
+
for(int i = 0; i < nodeMap.getLength(); i++) {
|
510
|
+
if (!NokogiriHelpers.isNamespace(nodeMap.item(i))) {
|
511
|
+
attr.append(getCachedNodeOrCreate(context.getRuntime(), nodeMap.item(i)));
|
512
|
+
}
|
513
|
+
}
|
514
|
+
|
515
|
+
return attr;
|
516
|
+
}
|
517
|
+
|
518
|
+
@JRubyMethod
|
519
|
+
public IRubyObject attribute_with_ns(ThreadContext context, IRubyObject name, IRubyObject namespace) {
|
520
|
+
String namej = (String)name.toJava(String.class);
|
521
|
+
String nsj = (namespace.isNil()) ? null : (String)namespace.toJava(String.class);
|
522
|
+
|
523
|
+
Node el = this.node.getAttributes().getNamedItemNS(nsj, namej);
|
524
|
+
|
525
|
+
if(el == null) {
|
526
|
+
return context.getRuntime().getNil();
|
527
|
+
}
|
528
|
+
return NokogiriHelpers.getCachedNodeOrCreate(context.getRuntime(), el);
|
529
|
+
}
|
530
|
+
|
531
|
+
@JRubyMethod(name = "blank?")
|
532
|
+
public IRubyObject blank_p(ThreadContext context) {
|
533
|
+
String data = node.getTextContent();
|
534
|
+
if ("".equals(data.trim())) return context.getRuntime().getTrue();
|
535
|
+
return context.getRuntime().getFalse();
|
536
|
+
}
|
537
|
+
|
538
|
+
@JRubyMethod
|
539
|
+
public IRubyObject child(ThreadContext context) {
|
540
|
+
return getCachedNodeOrCreate(context.getRuntime(), node.getFirstChild());
|
541
|
+
}
|
542
|
+
|
543
|
+
@JRubyMethod
|
544
|
+
public IRubyObject children(ThreadContext context) {
|
545
|
+
XmlNodeSet result = new XmlNodeSet(context.getRuntime(), node.getChildNodes());
|
546
|
+
return result;
|
547
|
+
}
|
548
|
+
|
549
|
+
@JRubyMethod
|
550
|
+
public IRubyObject first_element_child(ThreadContext context) {
|
551
|
+
List<Node> elementNodes = new ArrayList<Node>();
|
552
|
+
addElements(node, elementNodes, true);
|
553
|
+
if (elementNodes.size() == 0) return context.getRuntime().getNil();
|
554
|
+
return getCachedNodeOrCreate(context.getRuntime(), elementNodes.get(0));
|
555
|
+
}
|
556
|
+
|
557
|
+
@JRubyMethod
|
558
|
+
public IRubyObject last_element_child(ThreadContext context) {
|
559
|
+
List<Node> elementNodes = new ArrayList<Node>();
|
560
|
+
addElements(node, elementNodes, false);
|
561
|
+
if (elementNodes.size() == 0) return context.getRuntime().getNil();
|
562
|
+
return getCachedNodeOrCreate(context.getRuntime(), elementNodes.get(elementNodes.size()-1));
|
563
|
+
}
|
564
|
+
|
565
|
+
@JRubyMethod(name = {"element_children", "elements"})
|
566
|
+
public IRubyObject element_children(ThreadContext context) {
|
567
|
+
List<Node> elementNodes = new ArrayList<Node>();
|
568
|
+
addElements(node, elementNodes, false);
|
569
|
+
if (elementNodes.size() == 0) return XmlNodeSet.newEmptyNodeSet(context);
|
570
|
+
RubyArray array = NokogiriHelpers.nodeArrayToRubyArray(context.getRuntime(), elementNodes.toArray(new Node[0]));
|
571
|
+
XmlNodeSet result = new XmlNodeSet(context.getRuntime(), array);
|
572
|
+
return result;
|
573
|
+
}
|
574
|
+
|
575
|
+
private void addElements(Node n, List<Node> nodes, boolean isFirstOnly) {
|
576
|
+
NodeList children = n.getChildNodes();
|
577
|
+
if (children.getLength() == 0) return;
|
578
|
+
for (int i=0; i< children.getLength(); i++) {
|
579
|
+
Node child = children.item(i);
|
580
|
+
if (child.getNodeType() == Node.ELEMENT_NODE) {
|
581
|
+
nodes.add(child);
|
582
|
+
if (isFirstOnly) return;
|
583
|
+
}
|
584
|
+
addElements(child, nodes, isFirstOnly);
|
585
|
+
}
|
586
|
+
}
|
587
|
+
|
588
|
+
/**
|
589
|
+
* call-seq:
|
590
|
+
* compare(other)
|
591
|
+
*
|
592
|
+
* Compare this Node to +other+ with respect to their Document
|
593
|
+
*/
|
594
|
+
@JRubyMethod(visibility=Visibility.PRIVATE)
|
595
|
+
public IRubyObject compare(ThreadContext context, IRubyObject other) {
|
596
|
+
if (!(other instanceof XmlNode)) {
|
597
|
+
return context.getRuntime().newFixnum(-2);
|
598
|
+
}
|
599
|
+
|
600
|
+
Node otherNode = asXmlNode(context, other).node;
|
601
|
+
|
602
|
+
// Do not touch this if, if it's not for a good reason.
|
603
|
+
if (node.getNodeType() == Node.DOCUMENT_NODE ||
|
604
|
+
otherNode.getNodeType() == Node.DOCUMENT_NODE) {
|
605
|
+
return context.getRuntime().newFixnum(-1);
|
606
|
+
}
|
607
|
+
|
608
|
+
try{
|
609
|
+
int res = node.compareDocumentPosition(otherNode);
|
610
|
+
if ((res & FIRST_PRECEDES_SECOND) == FIRST_PRECEDES_SECOND) {
|
611
|
+
return context.getRuntime().newFixnum(-1);
|
612
|
+
} else if ((res & SECOND_PRECEDES_FIRST) == SECOND_PRECEDES_FIRST) {
|
613
|
+
return context.getRuntime().newFixnum(1);
|
614
|
+
} else if (res == IDENTICAL_ELEMENTS) {
|
615
|
+
return context.getRuntime().newFixnum(0);
|
616
|
+
}
|
617
|
+
|
618
|
+
return context.getRuntime().newFixnum(-2);
|
619
|
+
} catch (Exception ex) {
|
620
|
+
return context.getRuntime().newFixnum(-2);
|
621
|
+
}
|
622
|
+
}
|
623
|
+
|
624
|
+
/**
|
625
|
+
* TODO: this is a stub implementation. It's not clear what
|
626
|
+
* 'in_context' is supposed to do. Also should take
|
627
|
+
* <code>options</code> into account.
|
628
|
+
*/
|
629
|
+
@JRubyMethod(visibility=Visibility.PRIVATE)
|
630
|
+
public IRubyObject in_context(ThreadContext context,
|
631
|
+
IRubyObject str,
|
632
|
+
IRubyObject options) {
|
633
|
+
RubyModule klass;
|
634
|
+
XmlDomParserContext ctx;
|
635
|
+
InputStream istream;
|
636
|
+
XmlDocument document;
|
637
|
+
|
638
|
+
IRubyObject d = document(context);
|
639
|
+
if (d != null && d instanceof XmlDocument) {
|
640
|
+
document = (XmlDocument)d;
|
641
|
+
} else {
|
642
|
+
return context.getRuntime().getNil();
|
643
|
+
}
|
644
|
+
|
645
|
+
if (document instanceof HtmlDocument) {
|
646
|
+
klass = getNokogiriClass(context.getRuntime(), "Nokogiri::HTML::Document");
|
647
|
+
ctx = new HtmlDomParserContext(context.getRuntime(), options);
|
648
|
+
((HtmlDomParserContext)ctx).enableDocumentFragment();
|
649
|
+
istream = new ByteArrayInputStream(((String)str.toJava(String.class)).getBytes());
|
650
|
+
} else if (document instanceof XmlDocument) {
|
651
|
+
klass = getNokogiriClass(context.getRuntime(), "Nokogiri::XML::Document");
|
652
|
+
ctx = new XmlDomParserContext(context.getRuntime(), options);
|
653
|
+
String input = addTemporaryRootTagIfNecessary(context, (String)str.toJava(String.class));
|
654
|
+
istream = new ByteArrayInputStream(input.getBytes());
|
655
|
+
} else {
|
656
|
+
return context.getRuntime().getNil();
|
657
|
+
}
|
658
|
+
|
659
|
+
ctx.setInputSource(istream);
|
660
|
+
XmlDocument doc = ctx.parse(context, klass, getRuntime().getNil());
|
661
|
+
|
662
|
+
if (isErrorIncreated(document, doc)) {
|
663
|
+
saveErrorsOfCreatedDocument(document, doc);
|
664
|
+
return new XmlNodeSet(getRuntime(), RubyArray.newArray(context.getRuntime()));
|
665
|
+
}
|
666
|
+
NodeList childNodes = removeTemporaryRootIfNecessary(doc.node.getChildNodes());
|
667
|
+
XmlNodeSet nodes = new XmlNodeSet(getRuntime(), childNodes);
|
668
|
+
return nodes;
|
669
|
+
}
|
670
|
+
|
671
|
+
private int getErrorNumbers(XmlDocument document) {
|
672
|
+
IRubyObject obj = document.getInstanceVariable("@errors");
|
673
|
+
if (obj != null && obj instanceof RubyArray) {
|
674
|
+
return ((RubyArray)obj).getLength();
|
675
|
+
}
|
676
|
+
return 0;
|
677
|
+
}
|
678
|
+
|
679
|
+
private boolean isErrorIncreated(XmlDocument base, XmlDocument created) {
|
680
|
+
int baseDocumentErrors = getErrorNumbers(base);
|
681
|
+
int createdDocumentErrors = getErrorNumbers(created);
|
682
|
+
return createdDocumentErrors > baseDocumentErrors;
|
683
|
+
}
|
684
|
+
|
685
|
+
private void saveErrorsOfCreatedDocument(XmlDocument base, XmlDocument created) {
|
686
|
+
RubyArray newErrors = (RubyArray)created.getInstanceVariable("@errors");
|
687
|
+
RubyArray existingErrors = null;
|
688
|
+
IRubyObject obj = base.getInstanceVariable("@errors");
|
689
|
+
if (obj != null && obj instanceof RubyArray) {
|
690
|
+
existingErrors = (RubyArray)obj;
|
691
|
+
} else {
|
692
|
+
existingErrors = RubyArray.newArray(base.getRuntime());
|
693
|
+
}
|
694
|
+
for (int i=0; i<newErrors.getLength(); i++) {
|
695
|
+
existingErrors.add(newErrors.get(i));
|
696
|
+
}
|
697
|
+
base.setInstanceVariable("@errors", existingErrors);
|
698
|
+
}
|
699
|
+
|
700
|
+
private String addTemporaryRootTagIfNecessary(ThreadContext context, String tags) {
|
701
|
+
Matcher matcher = XmlDocumentFragment.wellformed_pattern.matcher(tags);
|
702
|
+
while(matcher.find()) {
|
703
|
+
if (matcher.start() == 0 && matcher.end() == tags.length()) return tags;
|
704
|
+
break;
|
705
|
+
}
|
706
|
+
tags = "<"+ NokogiriNamespaceContext.NOKOGIRI_TEMPORARY_ROOT_TAG + ">" + tags + "</" + NokogiriNamespaceContext.NOKOGIRI_TEMPORARY_ROOT_TAG + ">";
|
707
|
+
return tags;
|
708
|
+
}
|
709
|
+
|
710
|
+
private NodeList removeTemporaryRootIfNecessary(NodeList nodes) {
|
711
|
+
if (nodes != null && nodes.getLength() > 0) {
|
712
|
+
Node n = nodes.item(0);
|
713
|
+
if (n.getNodeType() == Node.ELEMENT_NODE && n.getNodeName().equals(NokogiriNamespaceContext.NOKOGIRI_TEMPORARY_ROOT_TAG)) {
|
714
|
+
return n.getChildNodes();
|
715
|
+
}
|
716
|
+
}
|
717
|
+
return nodes;
|
718
|
+
}
|
719
|
+
|
720
|
+
@JRubyMethod(name = {"content", "text", "inner_text"})
|
721
|
+
public IRubyObject content(ThreadContext context) {
|
722
|
+
if (content != null && content.isNil()) return content;
|
723
|
+
String textContent;
|
724
|
+
if (content != null) textContent = (String)content.toJava(String.class);
|
725
|
+
else if (this instanceof XmlDocument) {
|
726
|
+
textContent = this.node.getChildNodes().item(0).getTextContent();
|
727
|
+
} else {
|
728
|
+
textContent = this.node.getTextContent();
|
729
|
+
}
|
730
|
+
String decodedText = null;
|
731
|
+
if (textContent != null) decodedText = NokogiriHelpers.decodeJavaString(textContent);
|
732
|
+
return stringOrNil(context.getRuntime(), decodedText);
|
733
|
+
}
|
734
|
+
|
735
|
+
@JRubyMethod
|
736
|
+
public IRubyObject document(ThreadContext context) {
|
737
|
+
if(doc == null) {
|
738
|
+
doc = getCachedNodeOrCreate(context.getRuntime(), node.getOwnerDocument());
|
739
|
+
}
|
740
|
+
return doc;
|
741
|
+
}
|
742
|
+
|
743
|
+
@JRubyMethod
|
744
|
+
public IRubyObject dup(ThreadContext context) {
|
745
|
+
return this.dup_implementation(context, true);
|
746
|
+
}
|
747
|
+
|
748
|
+
@JRubyMethod
|
749
|
+
public IRubyObject dup(ThreadContext context, IRubyObject depth) {
|
750
|
+
boolean deep = (Integer)depth.toJava(Integer.class) != 0;
|
751
|
+
|
752
|
+
return this.dup_implementation(context, deep);
|
753
|
+
}
|
754
|
+
|
755
|
+
protected IRubyObject dup_implementation(ThreadContext context, boolean deep) {
|
756
|
+
XmlNode clone;
|
757
|
+
try {
|
758
|
+
clone = (XmlNode) clone();
|
759
|
+
} catch (CloneNotSupportedException e) {
|
760
|
+
throw context.getRuntime().newRuntimeError(e.toString());
|
761
|
+
}
|
762
|
+
if (node == null) throw getRuntime().newRuntimeError("FFFFFFFFFUUUUUUU");
|
763
|
+
Node newNode = node.cloneNode(deep);
|
764
|
+
clone.node = newNode;
|
765
|
+
return clone;
|
766
|
+
}
|
767
|
+
|
768
|
+
public static IRubyObject encode_special_chars(ThreadContext context,
|
769
|
+
IRubyObject string) {
|
770
|
+
String s = (String)string.toJava(String.class);
|
771
|
+
String enc = NokogiriHelpers.encodeJavaString(s);
|
772
|
+
return context.getRuntime().newString(enc);
|
773
|
+
}
|
774
|
+
|
775
|
+
/**
|
776
|
+
* Instance method version of the above static method.
|
777
|
+
*/
|
778
|
+
@JRubyMethod(name="encode_special_chars")
|
779
|
+
public IRubyObject i_encode_special_chars(ThreadContext context,
|
780
|
+
IRubyObject string) {
|
781
|
+
return encode_special_chars(context, string);
|
782
|
+
}
|
783
|
+
|
784
|
+
/**
|
785
|
+
* Get the attribute at the given key, <code>key</code>.
|
786
|
+
* Assumes that this node has attributes (i.e. that key? returned
|
787
|
+
* true). Overridden in XmlElement.
|
788
|
+
*/
|
789
|
+
@JRubyMethod(visibility = Visibility.PRIVATE)
|
790
|
+
public IRubyObject get(ThreadContext context, IRubyObject key) {
|
791
|
+
return context.getRuntime().getNil();
|
792
|
+
}
|
793
|
+
|
794
|
+
/**
|
795
|
+
* Returns the owner document, checking if this node is the
|
796
|
+
* document, or returns null if there is no owner.
|
797
|
+
*/
|
798
|
+
protected Document getOwnerDocument() {
|
799
|
+
if (node.getNodeType() == Node.DOCUMENT_NODE) {
|
800
|
+
return (Document) node;
|
801
|
+
} else {
|
802
|
+
return node.getOwnerDocument();
|
803
|
+
}
|
804
|
+
}
|
805
|
+
|
806
|
+
@JRubyMethod
|
807
|
+
public IRubyObject internal_subset(ThreadContext context) {
|
808
|
+
Document document = getOwnerDocument();
|
809
|
+
|
810
|
+
if(document == null) {
|
811
|
+
return context.getRuntime().getNil();
|
812
|
+
}
|
813
|
+
|
814
|
+
XmlDocument xdoc =
|
815
|
+
(XmlDocument) getCachedNodeOrCreate(context.getRuntime(), document);
|
816
|
+
IRubyObject xdtd = xdoc.getInternalSubset(context);
|
817
|
+
return xdtd;
|
818
|
+
}
|
819
|
+
|
820
|
+
@JRubyMethod
|
821
|
+
public IRubyObject create_internal_subset(ThreadContext context,
|
822
|
+
IRubyObject name,
|
823
|
+
IRubyObject external_id,
|
824
|
+
IRubyObject system_id) {
|
825
|
+
IRubyObject subset = internal_subset(context);
|
826
|
+
if (!subset.isNil()) {
|
827
|
+
throw context.getRuntime()
|
828
|
+
.newRuntimeError("Document already has internal subset");
|
829
|
+
}
|
830
|
+
|
831
|
+
Document document = getOwnerDocument();
|
832
|
+
if(document == null) {
|
833
|
+
return context.getRuntime().getNil();
|
834
|
+
}
|
835
|
+
|
836
|
+
XmlDocument xdoc =
|
837
|
+
(XmlDocument) getCachedNodeOrCreate(context.getRuntime(), document);
|
838
|
+
IRubyObject xdtd = xdoc.createInternalSubset(context, name,
|
839
|
+
external_id, system_id);
|
840
|
+
return xdtd;
|
841
|
+
}
|
842
|
+
|
843
|
+
@JRubyMethod
|
844
|
+
public IRubyObject external_subset(ThreadContext context) {
|
845
|
+
Document document = getOwnerDocument();
|
846
|
+
|
847
|
+
if(document == null) {
|
848
|
+
return context.getRuntime().getNil();
|
849
|
+
}
|
850
|
+
|
851
|
+
XmlDocument xdoc =
|
852
|
+
(XmlDocument) getCachedNodeOrCreate(context.getRuntime(), document);
|
853
|
+
IRubyObject xdtd = xdoc.getExternalSubset(context);
|
854
|
+
return xdtd;
|
855
|
+
}
|
856
|
+
|
857
|
+
@JRubyMethod
|
858
|
+
public IRubyObject create_external_subset(ThreadContext context,
|
859
|
+
IRubyObject name,
|
860
|
+
IRubyObject external_id,
|
861
|
+
IRubyObject system_id) {
|
862
|
+
IRubyObject subset = external_subset(context);
|
863
|
+
if (!subset.isNil()) {
|
864
|
+
throw context.getRuntime()
|
865
|
+
.newRuntimeError("Document already has external subset");
|
866
|
+
}
|
867
|
+
|
868
|
+
Document document = getOwnerDocument();
|
869
|
+
if(document == null) {
|
870
|
+
return context.getRuntime().getNil();
|
871
|
+
}
|
872
|
+
XmlDocument xdoc = (XmlDocument) getCachedNodeOrCreate(context.getRuntime(), document);
|
873
|
+
IRubyObject xdtd = xdoc.createExternalSubset(context, name, external_id, system_id);
|
874
|
+
return xdtd;
|
875
|
+
}
|
876
|
+
|
877
|
+
/**
|
878
|
+
* Test if this node has an attribute named <code>rbkey</code>.
|
879
|
+
* Overridden in XmlElement.
|
880
|
+
*/
|
881
|
+
@JRubyMethod(name = {"key?", "has_attribute?"})
|
882
|
+
public IRubyObject key_p(ThreadContext context, IRubyObject rbkey) {
|
883
|
+
return context.getRuntime().getNil();
|
884
|
+
}
|
885
|
+
|
886
|
+
@JRubyMethod
|
887
|
+
public IRubyObject namespace(ThreadContext context){
|
888
|
+
XmlDocument xmlDocument = (XmlDocument) doc;
|
889
|
+
NokogiriNamespaceCache nsCache = xmlDocument.getNamespaceCache();
|
890
|
+
String prefix = node.getPrefix();
|
891
|
+
XmlNamespace namespace = nsCache.get(prefix == null ? "" : prefix, node.getNamespaceURI());
|
892
|
+
if (namespace == null || ((XmlNamespace) namespace).isEmpty()) {
|
893
|
+
return context.getRuntime().getNil();
|
894
|
+
}
|
895
|
+
|
896
|
+
return namespace;
|
897
|
+
}
|
898
|
+
|
899
|
+
/**
|
900
|
+
* Return an array of XmlNamespace nodes based on the attributes
|
901
|
+
* of this node.
|
902
|
+
*/
|
903
|
+
@JRubyMethod
|
904
|
+
public IRubyObject namespace_definitions(ThreadContext context) {
|
905
|
+
// don't use namespace_definitions cache anymore since
|
906
|
+
// namespaces might be deleted. Reflecting the result of
|
907
|
+
// namesapce removals is complicated, so the cache might not be
|
908
|
+
// updated.
|
909
|
+
Ruby ruby = context.getRuntime();
|
910
|
+
RubyArray namespace_definitions = ruby.newArray();
|
911
|
+
if (doc == null) return namespace_definitions;
|
912
|
+
List<XmlNamespace> namespaces = ((XmlDocument)doc).getNamespaceCache().get(node);
|
913
|
+
for (XmlNamespace namespace : namespaces) {
|
914
|
+
((RubyArray)namespace_definitions).append(namespace);
|
915
|
+
}
|
916
|
+
|
917
|
+
return (RubyArray) namespace_definitions;
|
918
|
+
}
|
919
|
+
|
920
|
+
/**
|
921
|
+
* Return an array of XmlNamespace nodes defined on this node and
|
922
|
+
* on any ancestor node.
|
923
|
+
*/
|
924
|
+
@JRubyMethod
|
925
|
+
public IRubyObject namespace_scopes(ThreadContext context) {
|
926
|
+
RubyArray parentNamespaces;
|
927
|
+
RubyArray namespaces = (RubyArray) namespace_definitions(context);
|
928
|
+
|
929
|
+
IRubyObject parent = parent(context);
|
930
|
+
if (!parent.isNil()) {
|
931
|
+
parentNamespaces = (RubyArray)
|
932
|
+
((XmlNode) parent).namespace_scopes(context);
|
933
|
+
} else {
|
934
|
+
parentNamespaces = getRuntime().newEmptyArray();
|
935
|
+
}
|
936
|
+
|
937
|
+
return parentNamespaces.op_plus(namespaces);
|
938
|
+
}
|
939
|
+
|
940
|
+
@JRubyMethod(name="namespaced_key?")
|
941
|
+
public IRubyObject namespaced_key_p(ThreadContext context, IRubyObject elementLName, IRubyObject namespaceUri) {
|
942
|
+
return this.attribute_with_ns(context, elementLName, namespaceUri).isNil() ?
|
943
|
+
context.getRuntime().getFalse() : context.getRuntime().getTrue();
|
944
|
+
}
|
945
|
+
|
946
|
+
protected void setContent(IRubyObject content) {
|
947
|
+
this.content = content;
|
948
|
+
this.node.setTextContent((String)content.toJava(String.class));
|
949
|
+
}
|
950
|
+
|
951
|
+
private void setContent(String content) {
|
952
|
+
node.setTextContent(content);
|
953
|
+
this.content = null; // clear cache
|
954
|
+
}
|
955
|
+
|
956
|
+
@JRubyMethod(name = "native_content=", visibility = Visibility.PRIVATE)
|
957
|
+
public IRubyObject native_content_set(ThreadContext context, IRubyObject content) {
|
958
|
+
setContent(content);
|
959
|
+
return content;
|
960
|
+
}
|
961
|
+
|
962
|
+
/**
|
963
|
+
* @param args {IRubyObject io,
|
964
|
+
* IRubyObject encoding,
|
965
|
+
* IRubyObject indentString,
|
966
|
+
* IRubyObject options}
|
967
|
+
*/
|
968
|
+
@JRubyMethod(required=4, visibility=Visibility.PRIVATE)
|
969
|
+
public IRubyObject native_write_to(ThreadContext context,
|
970
|
+
IRubyObject[] args) {
|
971
|
+
|
972
|
+
IRubyObject io = args[0];
|
973
|
+
IRubyObject encoding = args[1];
|
974
|
+
IRubyObject indentString = args[2];
|
975
|
+
IRubyObject options = args[3];
|
976
|
+
|
977
|
+
String encString = encoding.isNil() ? null : (String)encoding.toJava(String.class);
|
978
|
+
|
979
|
+
SaveContext ctx = new SaveContext(context, (Integer)options.toJava(Integer.class),
|
980
|
+
(String)indentString.toJava(String.class),
|
981
|
+
encString);
|
982
|
+
|
983
|
+
saveContent(context, ctx);
|
984
|
+
|
985
|
+
RuntimeHelpers.invoke(context, io, "write", ctx.toRubyString(context.getRuntime()));
|
986
|
+
|
987
|
+
return io;
|
988
|
+
}
|
989
|
+
|
990
|
+
@JRubyMethod(name = {"next_sibling", "next"})
|
991
|
+
public IRubyObject next_sibling(ThreadContext context) {
|
992
|
+
return getCachedNodeOrCreate(context.getRuntime(), node.getNextSibling());
|
993
|
+
}
|
994
|
+
|
995
|
+
@JRubyMethod(name = {"previous_sibling", "previous"})
|
996
|
+
public IRubyObject previous_sibling(ThreadContext context) {
|
997
|
+
return getCachedNodeOrCreate(context.getRuntime(), node.getPreviousSibling());
|
998
|
+
}
|
999
|
+
|
1000
|
+
@JRubyMethod(meta = true, rest = true)
|
1001
|
+
public static IRubyObject new_from_str(ThreadContext context,
|
1002
|
+
IRubyObject cls,
|
1003
|
+
IRubyObject[] args) {
|
1004
|
+
XmlDocument doc = (XmlDocument) XmlDocument.read_memory(context, args);
|
1005
|
+
return doc.root(context);
|
1006
|
+
}
|
1007
|
+
|
1008
|
+
@JRubyMethod(name = {"node_name", "name"})
|
1009
|
+
public IRubyObject node_name(ThreadContext context) {
|
1010
|
+
return getNodeName(context);
|
1011
|
+
}
|
1012
|
+
|
1013
|
+
@JRubyMethod(name = {"node_name=", "name="})
|
1014
|
+
public IRubyObject node_name_set(ThreadContext context, IRubyObject nodeName) {
|
1015
|
+
String newName = (String)nodeName.toJava(String.class);
|
1016
|
+
getOwnerDocument().renameNode(node, null, newName);
|
1017
|
+
setName(nodeName);
|
1018
|
+
return this;
|
1019
|
+
}
|
1020
|
+
|
1021
|
+
@JRubyMethod(name = {"[]=", "set_attribute"})
|
1022
|
+
public IRubyObject op_aset(ThreadContext context, IRubyObject index, IRubyObject val) {
|
1023
|
+
return val;
|
1024
|
+
}
|
1025
|
+
|
1026
|
+
@JRubyMethod
|
1027
|
+
public IRubyObject parent(ThreadContext context) {
|
1028
|
+
/*
|
1029
|
+
* Check if this node is the root node of the document.
|
1030
|
+
* If so, parent is the document.
|
1031
|
+
*/
|
1032
|
+
if (node.getOwnerDocument() != null &&
|
1033
|
+
node.getOwnerDocument().getDocumentElement() == node) {
|
1034
|
+
return document(context);
|
1035
|
+
} else {
|
1036
|
+
return getCachedNodeOrCreate(context.getRuntime(), node.getParentNode());
|
1037
|
+
}
|
1038
|
+
}
|
1039
|
+
|
1040
|
+
@JRubyMethod
|
1041
|
+
public IRubyObject path(ThreadContext context) {
|
1042
|
+
return RubyString.newString(context.getRuntime(), NokogiriHelpers.getNodeCompletePath(this.node));
|
1043
|
+
}
|
1044
|
+
|
1045
|
+
@JRubyMethod
|
1046
|
+
public IRubyObject pointer_id(ThreadContext context) {
|
1047
|
+
return RubyFixnum.newFixnum(context.getRuntime(), this.node.hashCode());
|
1048
|
+
}
|
1049
|
+
|
1050
|
+
@JRubyMethod(name = {"remove_attribute", "delete"})
|
1051
|
+
public IRubyObject remove_attribute(ThreadContext context, IRubyObject name) {
|
1052
|
+
return this;
|
1053
|
+
}
|
1054
|
+
|
1055
|
+
@JRubyMethod(visibility=Visibility.PRIVATE)
|
1056
|
+
public IRubyObject set_namespace(ThreadContext context, IRubyObject namespace) {
|
1057
|
+
if (namespace.isNil()) {
|
1058
|
+
if (doc != null) {
|
1059
|
+
Node n = node;
|
1060
|
+
String prefix = n.getPrefix();
|
1061
|
+
String href = n.getNamespaceURI();
|
1062
|
+
((XmlDocument)doc).getNamespaceCache().remove(prefix == null ? "" : prefix, href);
|
1063
|
+
n.getOwnerDocument().renameNode(n, null, n.getNodeName());
|
1064
|
+
}
|
1065
|
+
} else {
|
1066
|
+
XmlNamespace ns = (XmlNamespace) namespace;
|
1067
|
+
String prefix = (String)ns.prefix(context).toJava(String.class);
|
1068
|
+
String href = (String)ns.href(context).toJava(String.class);
|
1069
|
+
|
1070
|
+
// Assigning node = ...renameNode() or not seems to make no
|
1071
|
+
// difference. Why not? -pmahoney
|
1072
|
+
node = node.getOwnerDocument().renameNode(node, href, NokogiriHelpers.newQName(prefix, node));
|
1073
|
+
}
|
1074
|
+
|
1075
|
+
return this;
|
1076
|
+
}
|
1077
|
+
|
1078
|
+
@JRubyMethod(name = {"unlink", "remove"})
|
1079
|
+
public IRubyObject unlink(ThreadContext context) {
|
1080
|
+
if(node.getParentNode() == null) {
|
1081
|
+
throw context.getRuntime().newRuntimeError("TYPE: " + node.getNodeType()+ " PARENT NULL");
|
1082
|
+
} else {
|
1083
|
+
node.getParentNode().removeChild(node);
|
1084
|
+
}
|
1085
|
+
|
1086
|
+
return this;
|
1087
|
+
}
|
1088
|
+
|
1089
|
+
/**
|
1090
|
+
* The C-library simply returns libxml2 magic numbers. Here we
|
1091
|
+
* convert Java Xml nodes to the appropriate constant defined in
|
1092
|
+
* xml/node.rb.
|
1093
|
+
*/
|
1094
|
+
@JRubyMethod(name = {"node_type", "type"})
|
1095
|
+
public IRubyObject node_type(ThreadContext context) {
|
1096
|
+
String type;
|
1097
|
+
switch (node.getNodeType()) {
|
1098
|
+
case Node.ELEMENT_NODE:
|
1099
|
+
if (this instanceof XmlElementDecl)
|
1100
|
+
type = "ELEMENT_DECL";
|
1101
|
+
else if (this instanceof XmlAttributeDecl)
|
1102
|
+
type = "ATTRIBUTE_DECL";
|
1103
|
+
else if (this instanceof XmlEntityDecl)
|
1104
|
+
type = "ENTITY_DECL";
|
1105
|
+
else
|
1106
|
+
type = "ELEMENT_NODE";
|
1107
|
+
break;
|
1108
|
+
case Node.ATTRIBUTE_NODE: type = "ATTRIBUTE_NODE"; break;
|
1109
|
+
case Node.TEXT_NODE: type = "TEXT_NODE"; break;
|
1110
|
+
case Node.CDATA_SECTION_NODE: type = "CDATA_SECTION_NODE"; break;
|
1111
|
+
case Node.ENTITY_REFERENCE_NODE: type = "ENTITY_REF_NODE"; break;
|
1112
|
+
case Node.ENTITY_NODE: type = "ENTITY_NODE"; break;
|
1113
|
+
case Node.PROCESSING_INSTRUCTION_NODE: type = "PI_NODE"; break;
|
1114
|
+
case Node.COMMENT_NODE: type = "COMMENT_NODE"; break;
|
1115
|
+
case Node.DOCUMENT_NODE:
|
1116
|
+
if (this instanceof HtmlDocument)
|
1117
|
+
type = "HTML_DOCUMENT_NODE";
|
1118
|
+
else
|
1119
|
+
type = "DOCUMENT_NODE";
|
1120
|
+
break;
|
1121
|
+
case Node.DOCUMENT_TYPE_NODE: type = "DOCUMENT_TYPE_NODE"; break;
|
1122
|
+
case Node.DOCUMENT_FRAGMENT_NODE: type = "DOCUMENT_FRAG_NODE"; break;
|
1123
|
+
case Node.NOTATION_NODE: type = "NOTATION_NODE"; break;
|
1124
|
+
default:
|
1125
|
+
return context.getRuntime().newFixnum(0);
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
return getNokogiriClass(context.getRuntime(), "Nokogiri::XML::Node").getConstant(type);
|
1129
|
+
}
|
1130
|
+
|
1131
|
+
@JRubyMethod
|
1132
|
+
public IRubyObject line(ThreadContext context) {
|
1133
|
+
Node root = getOwnerDocument();
|
1134
|
+
int[] counter = new int[1];
|
1135
|
+
count(root, counter);
|
1136
|
+
return RubyFixnum.newFixnum(context.getRuntime(), counter[0]+1);
|
1137
|
+
}
|
1138
|
+
|
1139
|
+
private boolean count(Node node, int[] counter) {
|
1140
|
+
if (node == this.node) {
|
1141
|
+
return true;
|
1142
|
+
}
|
1143
|
+
NodeList list = node.getChildNodes();
|
1144
|
+
for (int i=0; i<list.getLength(); i++) {
|
1145
|
+
Node n = list.item(i);
|
1146
|
+
if (n instanceof Text
|
1147
|
+
&& ((Text)n).getData().contains("\n")) {
|
1148
|
+
counter[0] += 1;
|
1149
|
+
}
|
1150
|
+
if (count(n, counter)) return true;
|
1151
|
+
}
|
1152
|
+
return false;
|
1153
|
+
}
|
1154
|
+
|
1155
|
+
@JRubyMethod
|
1156
|
+
public IRubyObject next_element(ThreadContext context) {
|
1157
|
+
Node nextNode = node.getNextSibling();
|
1158
|
+
Ruby ruby = context.getRuntime();
|
1159
|
+
if (nextNode == null) return ruby.getNil();
|
1160
|
+
if (nextNode instanceof Element) {
|
1161
|
+
return getCachedNodeOrCreate(context.getRuntime(), nextNode);
|
1162
|
+
}
|
1163
|
+
Node deeper = nextNode.getNextSibling();
|
1164
|
+
if (deeper == null) return ruby.getNil();
|
1165
|
+
return getCachedNodeOrCreate(context.getRuntime(), deeper);
|
1166
|
+
}
|
1167
|
+
|
1168
|
+
@JRubyMethod
|
1169
|
+
public IRubyObject previous_element(ThreadContext context) {
|
1170
|
+
Node prevNode = node.getPreviousSibling();
|
1171
|
+
Ruby ruby = context.getRuntime();
|
1172
|
+
if (prevNode == null) return ruby.getNil();
|
1173
|
+
if (prevNode instanceof Element) {
|
1174
|
+
return getCachedNodeOrCreate(context.getRuntime(), prevNode);
|
1175
|
+
}
|
1176
|
+
Node shallower = prevNode.getPreviousSibling();
|
1177
|
+
if (shallower == null) return ruby.getNil();
|
1178
|
+
return getCachedNodeOrCreate(context.getRuntime(), shallower);
|
1179
|
+
}
|
1180
|
+
|
1181
|
+
protected enum AdoptScheme {
|
1182
|
+
CHILD, PREV_SIBLING, NEXT_SIBLING, REPLACEMENT;
|
1183
|
+
}
|
1184
|
+
|
1185
|
+
/**
|
1186
|
+
* Adopt XmlNode <code>other</code> into the document of
|
1187
|
+
* <code>this</code> using the specified scheme.
|
1188
|
+
*/
|
1189
|
+
protected IRubyObject adoptAs(ThreadContext context, AdoptScheme scheme,
|
1190
|
+
IRubyObject other_) {
|
1191
|
+
XmlNode other = asXmlNode(context, other_);
|
1192
|
+
// this.doc might be null since this node can be empty node.
|
1193
|
+
if (this.doc != null) {
|
1194
|
+
other.doc = this.doc;
|
1195
|
+
}
|
1196
|
+
IRubyObject nodeOrTags = other;
|
1197
|
+
Node thisNode = node;
|
1198
|
+
Node otherNode = other.node;
|
1199
|
+
|
1200
|
+
try {
|
1201
|
+
Document doc = thisNode.getOwnerDocument();
|
1202
|
+
if (doc != null && doc != otherNode.getOwnerDocument()) {
|
1203
|
+
Node ret = doc.adoptNode(otherNode);
|
1204
|
+
if (ret == null) {
|
1205
|
+
throw context.getRuntime().newRuntimeError("Failed to take ownership of node");
|
1206
|
+
}
|
1207
|
+
}
|
1208
|
+
|
1209
|
+
Node parent = thisNode.getParentNode();
|
1210
|
+
|
1211
|
+
switch (scheme) {
|
1212
|
+
case CHILD:
|
1213
|
+
Node[] children = adoptAsChild(context, thisNode, otherNode);
|
1214
|
+
if (children.length == 1 && otherNode == children[0]) {
|
1215
|
+
break;
|
1216
|
+
} else {
|
1217
|
+
nodeOrTags = nodeArrayToRubyArray(context.getRuntime(), children);
|
1218
|
+
}
|
1219
|
+
break;
|
1220
|
+
case PREV_SIBLING:
|
1221
|
+
adoptAsPrevSibling(context, parent, thisNode, otherNode);
|
1222
|
+
break;
|
1223
|
+
case NEXT_SIBLING:
|
1224
|
+
adoptAsNextSibling(context, parent, thisNode, otherNode);
|
1225
|
+
break;
|
1226
|
+
case REPLACEMENT:
|
1227
|
+
adoptAsReplacement(context, parent, thisNode, otherNode);
|
1228
|
+
break;
|
1229
|
+
}
|
1230
|
+
} catch (Exception e) {
|
1231
|
+
throw context.getRuntime().newRuntimeError(e.toString());
|
1232
|
+
}
|
1233
|
+
|
1234
|
+
if (otherNode.getNodeType() == Node.TEXT_NODE) {
|
1235
|
+
coalesceTextNodes(context, other);
|
1236
|
+
}
|
1237
|
+
|
1238
|
+
relink_namespace(context);
|
1239
|
+
// post_add_child(context, this, other);
|
1240
|
+
|
1241
|
+
return nodeOrTags;
|
1242
|
+
}
|
1243
|
+
|
1244
|
+
protected Node[] adoptAsChild(ThreadContext context, Node parent,
|
1245
|
+
Node otherNode) {
|
1246
|
+
if (hasTemporaryRoot(parent, otherNode)) {
|
1247
|
+
return adoptRealChildrenAsChild(context, parent, otherNode);
|
1248
|
+
}
|
1249
|
+
|
1250
|
+
/*
|
1251
|
+
* This is a bit of a hack. C-Nokogiri allows adding a bare
|
1252
|
+
* text node as the root element. Java (and XML spec?) does
|
1253
|
+
* not. So we wrap the text node in an element.
|
1254
|
+
*/
|
1255
|
+
if (parent.getNodeType() == Node.DOCUMENT_NODE &&
|
1256
|
+
otherNode.getNodeType() == Node.TEXT_NODE) {
|
1257
|
+
Element e = ((Document)parent).createElement("text");
|
1258
|
+
e.appendChild(otherNode);
|
1259
|
+
otherNode = e;
|
1260
|
+
}
|
1261
|
+
addNamespaceURIIfNeeded(otherNode);
|
1262
|
+
parent.appendChild(otherNode);
|
1263
|
+
Node[] nodes = new Node[1];
|
1264
|
+
nodes[0] = otherNode;
|
1265
|
+
return nodes;
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
private boolean hasTemporaryRoot(Node parent, Node child) {
|
1269
|
+
if (parent.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
|
1270
|
+
&& child.getNodeName().equals(NokogiriNamespaceContext.NOKOGIRI_TEMPORARY_ROOT_TAG)) {
|
1271
|
+
return true;
|
1272
|
+
}
|
1273
|
+
return false;
|
1274
|
+
}
|
1275
|
+
|
1276
|
+
private Node[] adoptRealChildrenAsChild(ThreadContext context, Node parent, Node otherNode) {
|
1277
|
+
NodeList children = otherNode.getChildNodes();
|
1278
|
+
int length = children.getLength();
|
1279
|
+
List<Node> nodes = new ArrayList<Node>();
|
1280
|
+
for (int i=0; i < length; i++) {
|
1281
|
+
nodes.add(children.item(i).cloneNode(true));
|
1282
|
+
}
|
1283
|
+
//cut out leading and trailing whitespaces
|
1284
|
+
if (nodes.get(0).getNodeType() == Node.TEXT_NODE && nodes.get(0).getTextContent().trim().length() == 0) {
|
1285
|
+
nodes.remove(0);
|
1286
|
+
}
|
1287
|
+
int lastIndex = nodes.size() - 1;
|
1288
|
+
if (nodes.get(lastIndex).getNodeType() == Node.TEXT_NODE && nodes.get(lastIndex).getTextContent().trim().length() == 0) {
|
1289
|
+
nodes.remove(lastIndex);
|
1290
|
+
}
|
1291
|
+
|
1292
|
+
Node[] nodeArray = new Node[nodes.size()];
|
1293
|
+
for (int i=0; i < nodeArray.length; i++) {
|
1294
|
+
Node child = nodes.get(i);
|
1295
|
+
addNamespaceURIIfNeeded(child);
|
1296
|
+
parent.appendChild(child);
|
1297
|
+
nodeArray[i] = child;
|
1298
|
+
}
|
1299
|
+
return nodeArray;
|
1300
|
+
}
|
1301
|
+
|
1302
|
+
private void addNamespaceURIIfNeeded(Node child) {
|
1303
|
+
if (this instanceof XmlDocumentFragment && ((XmlDocumentFragment)this).getFragmentContext() != null) {
|
1304
|
+
XmlElement fragmentContext = ((XmlDocumentFragment)this).getFragmentContext();
|
1305
|
+
String namespace_uri = fragmentContext.node.getNamespaceURI();
|
1306
|
+
if (namespace_uri != null && namespace_uri.length() > 0) {
|
1307
|
+
node.getOwnerDocument().renameNode(child, namespace_uri, child.getNodeName());
|
1308
|
+
}
|
1309
|
+
}
|
1310
|
+
}
|
1311
|
+
|
1312
|
+
protected void adoptAsPrevSibling(ThreadContext context,
|
1313
|
+
Node parent,
|
1314
|
+
Node thisNode, Node otherNode) {
|
1315
|
+
if (parent == null) {
|
1316
|
+
/* I'm not sure what do do here... A node with no
|
1317
|
+
* parent can't exactly have a 'sibling', so we make
|
1318
|
+
* otherNode parentless also. */
|
1319
|
+
if (otherNode.getParentNode() != null)
|
1320
|
+
otherNode.getParentNode().removeChild(otherNode);
|
1321
|
+
|
1322
|
+
return;
|
1323
|
+
}
|
1324
|
+
|
1325
|
+
parent.insertBefore(otherNode, thisNode);
|
1326
|
+
}
|
1327
|
+
|
1328
|
+
protected void adoptAsNextSibling(ThreadContext context,
|
1329
|
+
Node parent,
|
1330
|
+
Node thisNode, Node otherNode) {
|
1331
|
+
if (parent == null) {
|
1332
|
+
/* I'm not sure what do do here... A node with no
|
1333
|
+
* parent can't exactly have a 'sibling', so we make
|
1334
|
+
* otherNode parentless also. */
|
1335
|
+
if (otherNode.getParentNode() != null)
|
1336
|
+
otherNode.getParentNode().removeChild(otherNode);
|
1337
|
+
|
1338
|
+
return;
|
1339
|
+
}
|
1340
|
+
|
1341
|
+
Node nextSib = thisNode.getNextSibling();
|
1342
|
+
if (nextSib != null) {
|
1343
|
+
parent.insertBefore(otherNode, nextSib);
|
1344
|
+
} else {
|
1345
|
+
parent.appendChild(otherNode);
|
1346
|
+
}
|
1347
|
+
}
|
1348
|
+
|
1349
|
+
protected void adoptAsReplacement(ThreadContext context,
|
1350
|
+
Node parentNode,
|
1351
|
+
Node thisNode, Node otherNode) {
|
1352
|
+
if (parentNode == null) {
|
1353
|
+
/* nothing to replace? */
|
1354
|
+
return;
|
1355
|
+
}
|
1356
|
+
|
1357
|
+
try {
|
1358
|
+
parentNode.replaceChild(otherNode, thisNode);
|
1359
|
+
if (otherNode.getNodeType() != Node.TEXT_NODE) {
|
1360
|
+
otherNode.getOwnerDocument().renameNode(otherNode, thisNode.getNamespaceURI(), otherNode.getNodeName());
|
1361
|
+
}
|
1362
|
+
} catch (Exception e) {
|
1363
|
+
String prefix = "could not replace child: ";
|
1364
|
+
throw context.getRuntime().newRuntimeError(prefix + e.toString());
|
1365
|
+
}
|
1366
|
+
}
|
1367
|
+
|
1368
|
+
/**
|
1369
|
+
* Add <code>other</code> as a child of <code>this</code>.
|
1370
|
+
*/
|
1371
|
+
@JRubyMethod(visibility=Visibility.PRIVATE)
|
1372
|
+
public IRubyObject add_child_node(ThreadContext context, IRubyObject other) {
|
1373
|
+
return adoptAs(context, AdoptScheme.CHILD, other);
|
1374
|
+
}
|
1375
|
+
|
1376
|
+
/**
|
1377
|
+
* Replace <code>this</code> with <code>other</code>.
|
1378
|
+
*/
|
1379
|
+
@JRubyMethod(visibility=Visibility.PRIVATE)
|
1380
|
+
public IRubyObject replace_node(ThreadContext context, IRubyObject other) {
|
1381
|
+
return adoptAs(context, AdoptScheme.REPLACEMENT, other);
|
1382
|
+
}
|
1383
|
+
|
1384
|
+
/**
|
1385
|
+
* Add <code>other</code> as a sibling before <code>this</code>.
|
1386
|
+
*/
|
1387
|
+
@JRubyMethod(visibility=Visibility.PRIVATE)
|
1388
|
+
public IRubyObject add_previous_sibling_node(ThreadContext context, IRubyObject other) {
|
1389
|
+
return adoptAs(context, AdoptScheme.PREV_SIBLING, other);
|
1390
|
+
}
|
1391
|
+
|
1392
|
+
/**
|
1393
|
+
* Add <code>other</code> as a sibling after <code>this</code>.
|
1394
|
+
*/
|
1395
|
+
@JRubyMethod(visibility=Visibility.PRIVATE)
|
1396
|
+
public IRubyObject add_next_sibling_node(ThreadContext context, IRubyObject other) {
|
1397
|
+
return adoptAs(context, AdoptScheme.NEXT_SIBLING, other);
|
1398
|
+
}
|
1399
|
+
}
|