moxml 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +23 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +65 -0
- data/.ruby-version +1 -0
- data/Gemfile +10 -3
- data/README.adoc +401 -594
- data/lib/moxml/adapter/base.rb +102 -0
- data/lib/moxml/adapter/customized_oga/xml_declaration.rb +18 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +104 -0
- data/lib/moxml/adapter/nokogiri.rb +319 -0
- data/lib/moxml/adapter/oga.rb +318 -0
- data/lib/moxml/adapter/ox.rb +325 -0
- data/lib/moxml/adapter.rb +26 -170
- data/lib/moxml/attribute.rb +47 -14
- data/lib/moxml/builder.rb +64 -0
- data/lib/moxml/cdata.rb +4 -26
- data/lib/moxml/comment.rb +6 -22
- data/lib/moxml/config.rb +39 -15
- data/lib/moxml/context.rb +29 -0
- data/lib/moxml/declaration.rb +16 -26
- data/lib/moxml/doctype.rb +9 -0
- data/lib/moxml/document.rb +51 -63
- data/lib/moxml/document_builder.rb +87 -0
- data/lib/moxml/element.rb +63 -97
- data/lib/moxml/error.rb +20 -0
- data/lib/moxml/namespace.rb +12 -37
- data/lib/moxml/node.rb +78 -58
- data/lib/moxml/node_set.rb +19 -222
- data/lib/moxml/processing_instruction.rb +6 -25
- data/lib/moxml/text.rb +4 -26
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils/encoder.rb +55 -0
- data/lib/moxml/xml_utils.rb +80 -0
- data/lib/moxml.rb +33 -33
- data/moxml.gemspec +1 -1
- data/spec/moxml/adapter/nokogiri_spec.rb +14 -0
- data/spec/moxml/adapter/oga_spec.rb +14 -0
- data/spec/moxml/adapter/ox_spec.rb +49 -0
- data/spec/moxml/all_with_adapters_spec.rb +46 -0
- data/spec/moxml/config_spec.rb +55 -0
- data/spec/moxml/error_spec.rb +71 -0
- data/spec/moxml/examples/adapter_spec.rb +27 -0
- data/spec/moxml_spec.rb +50 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/shared_examples/attribute.rb +165 -0
- data/spec/support/shared_examples/builder.rb +25 -0
- data/spec/support/shared_examples/cdata.rb +70 -0
- data/spec/support/shared_examples/comment.rb +65 -0
- data/spec/support/shared_examples/context.rb +35 -0
- data/spec/support/shared_examples/declaration.rb +93 -0
- data/spec/support/shared_examples/doctype.rb +25 -0
- data/spec/support/shared_examples/document.rb +110 -0
- data/spec/support/shared_examples/document_builder.rb +43 -0
- data/spec/support/shared_examples/edge_cases.rb +185 -0
- data/spec/support/shared_examples/element.rb +130 -0
- data/spec/support/shared_examples/examples/attribute.rb +42 -0
- data/spec/support/shared_examples/examples/basic_usage.rb +67 -0
- data/spec/support/shared_examples/examples/memory.rb +54 -0
- data/spec/support/shared_examples/examples/namespace.rb +65 -0
- data/spec/support/shared_examples/examples/readme_examples.rb +100 -0
- data/spec/support/shared_examples/examples/thread_safety.rb +43 -0
- data/spec/support/shared_examples/examples/xpath.rb +39 -0
- data/spec/support/shared_examples/integration.rb +135 -0
- data/spec/support/shared_examples/namespace.rb +96 -0
- data/spec/support/shared_examples/node.rb +110 -0
- data/spec/support/shared_examples/node_set.rb +90 -0
- data/spec/support/shared_examples/processing_instruction.rb +88 -0
- data/spec/support/shared_examples/text.rb +66 -0
- data/spec/support/shared_examples/xml_adapter.rb +191 -0
- data/spec/support/xml_matchers.rb +27 -0
- metadata +55 -6
- data/.github/workflows/main.yml +0 -27
- data/lib/moxml/error_handler.rb +0 -77
- data/lib/moxml/errors.rb +0 -169
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "XPath Examples" do
|
4
|
+
let(:context) { Moxml.new }
|
5
|
+
|
6
|
+
describe "XPath querying" do
|
7
|
+
let(:doc) do
|
8
|
+
context.parse(<<~XML)
|
9
|
+
<root xmlns:dc="http://purl.org/dc/elements/1.1/">
|
10
|
+
<book id="1">
|
11
|
+
<dc:title>First</dc:title>
|
12
|
+
</book>
|
13
|
+
<book id="2">
|
14
|
+
<dc:title>Second</dc:title>
|
15
|
+
</book>
|
16
|
+
</root>
|
17
|
+
XML
|
18
|
+
end
|
19
|
+
|
20
|
+
it "finds nodes by XPath" do
|
21
|
+
books = doc.xpath("//book")
|
22
|
+
expect(books.size).to eq(2)
|
23
|
+
expect(books.map { |b| b["id"] }).to eq(%w[1 2])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "finds nodes with namespaces" do
|
27
|
+
titles = doc.xpath("//dc:title",
|
28
|
+
"dc" => "http://purl.org/dc/elements/1.1/")
|
29
|
+
expect(titles.map(&:text)).to eq(%w[First Second])
|
30
|
+
end
|
31
|
+
|
32
|
+
it "finds nodes by attributes" do
|
33
|
+
book = doc.at_xpath('//book[@id="2"]')
|
34
|
+
expect(book).not_to be_nil
|
35
|
+
expect(book.at_xpath(".//dc:title",
|
36
|
+
"dc" => "http://purl.org/dc/elements/1.1/").text).to eq("Second")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Moxml Integration" do
|
4
|
+
let(:context) { Moxml.new }
|
5
|
+
|
6
|
+
describe "complete document workflow" do
|
7
|
+
let(:doc) { context.create_document }
|
8
|
+
before do
|
9
|
+
# Create document with declaration
|
10
|
+
doc.add_child(doc.create_declaration("1.0", "UTF-8"))
|
11
|
+
|
12
|
+
# Add root with namespaces
|
13
|
+
root = doc.create_element("root")
|
14
|
+
root.add_namespace(nil, "http://example.org")
|
15
|
+
root.add_namespace("xs", "http://www.w3.org/2001/XMLSchema")
|
16
|
+
doc.add_child(root)
|
17
|
+
|
18
|
+
# Add processing instruction
|
19
|
+
style_pi = doc.create_processing_instruction("xml-stylesheet", 'type="text/xsl" href="style.xsl"')
|
20
|
+
root.add_previous_sibling(style_pi)
|
21
|
+
|
22
|
+
# Add mixed content
|
23
|
+
root.add_child(doc.create_comment(" Mixed content example "))
|
24
|
+
|
25
|
+
text_node = doc.create_element("text")
|
26
|
+
text_node.add_child("Some text ")
|
27
|
+
text_node.add_child(doc.create_cdata("<with><markup/>"))
|
28
|
+
text_node.add_child(" and more text")
|
29
|
+
root.add_child(text_node)
|
30
|
+
|
31
|
+
# Add element with attributes
|
32
|
+
item = doc.create_element("item")
|
33
|
+
item["id"] = "123"
|
34
|
+
item["xs:type"] = "custom"
|
35
|
+
root.add_child(item)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "has correct xml elements" do
|
39
|
+
# Verify structure
|
40
|
+
expected_xml_tags = [
|
41
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
42
|
+
'<?xml-stylesheet type="text/xsl" href="style.xsl"?>',
|
43
|
+
'<root xmlns="http://example.org" xmlns:xs="http://www.w3.org/2001/XMLSchema">',
|
44
|
+
"<!-- Mixed content example -->",
|
45
|
+
"<text>Some text <![CDATA[<with><markup/>]]> and more text</text>",
|
46
|
+
'<item id="123" xs:type="custom"></item>',
|
47
|
+
"</root>"
|
48
|
+
]
|
49
|
+
|
50
|
+
expected_xml_tags.each do |expected_xml|
|
51
|
+
# include(*expected_xml_tags) is shorter but worse for debugging
|
52
|
+
expect(doc.to_xml).to include(expected_xml)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "handles xpath queries" do
|
57
|
+
# Test XPath queries
|
58
|
+
#
|
59
|
+
# XPath with a default namespace is a problem
|
60
|
+
# Nokogiri:
|
61
|
+
# https://timnew.me/blog/2012/10/25/pitfall-in-nokogiri-xpath-and-namespace/
|
62
|
+
# Oga:
|
63
|
+
# https://www.rubydoc.info/gems/oga/0.3.3#namespace-support
|
64
|
+
expect(doc.xpath('//xmlns:item[@id="123"]')).not_to be_empty
|
65
|
+
expect(doc.at_xpath("//xmlns:text").text).to include("Some text")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "namespace handling" do
|
70
|
+
it "handles complex namespace scenarios" do
|
71
|
+
xml = <<~XML
|
72
|
+
<root xmlns="http://default.org" xmlns:a="http://a.org" xmlns:b="http://b.org">
|
73
|
+
<child>
|
74
|
+
<a:element b:attr="value">
|
75
|
+
<deeper xmlns="http://other.org"/>
|
76
|
+
</a:element>
|
77
|
+
</child>
|
78
|
+
</root>
|
79
|
+
XML
|
80
|
+
|
81
|
+
doc = context.parse(xml)
|
82
|
+
|
83
|
+
# Test root namespace
|
84
|
+
root = doc.root
|
85
|
+
expect(root.namespace.prefix).to be_nil
|
86
|
+
expect(root.namespace.uri).to eq("http://default.org")
|
87
|
+
|
88
|
+
# Test namespace inheritance
|
89
|
+
child = doc.at_xpath("//xmlns:child")
|
90
|
+
expect(child.namespace.uri).to eq("http://default.org")
|
91
|
+
|
92
|
+
# Test namespace prefix resolution
|
93
|
+
a_element = doc.at_xpath("//a:element", "a" => "http://a.org")
|
94
|
+
expect(a_element).not_to be_nil
|
95
|
+
expect(a_element.namespace.prefix).to eq("a")
|
96
|
+
|
97
|
+
# Test attribute namespace
|
98
|
+
attr = a_element["b:attr"]
|
99
|
+
expect(attr).to eq("value")
|
100
|
+
|
101
|
+
# Test namespace override
|
102
|
+
deeper = a_element.children.first
|
103
|
+
expect(deeper.namespace.uri).to eq("http://other.org")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "document modification" do
|
108
|
+
let(:doc) { context.parse("<root><a>1</a><b>2</b></root>") }
|
109
|
+
|
110
|
+
it "handles complex modifications" do
|
111
|
+
# Move nodes
|
112
|
+
b_node = doc.at_xpath("//b")
|
113
|
+
a_node = doc.at_xpath("//a")
|
114
|
+
b_node.add_previous_sibling(a_node)
|
115
|
+
|
116
|
+
# Add nodes
|
117
|
+
c_node = doc.create_element("c")
|
118
|
+
c_node.add_child("3")
|
119
|
+
b_node.add_next_sibling(c_node)
|
120
|
+
|
121
|
+
# Modify attributes
|
122
|
+
doc.root["id"] = "main"
|
123
|
+
|
124
|
+
# Add mixed content
|
125
|
+
b_node.add_child(doc.create_comment(" comment "))
|
126
|
+
b_node.add_child(doc.create_cdata("<tag>"))
|
127
|
+
|
128
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a b c])
|
129
|
+
expect(doc.to_xml).to include(
|
130
|
+
'<root id="main">',
|
131
|
+
"<b>2<!-- comment --><![CDATA[<tag>]]></b>"
|
132
|
+
)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Moxml::Namespace" do
|
4
|
+
describe Moxml::Namespace do
|
5
|
+
let(:context) { Moxml.new }
|
6
|
+
let(:doc) { context.create_document }
|
7
|
+
let(:element) { doc.create_element("test") }
|
8
|
+
|
9
|
+
describe "creation" do
|
10
|
+
it "creates namespace with prefix" do
|
11
|
+
element.add_namespace("xs", "http://www.w3.org/2001/XMLSchema")
|
12
|
+
ns = element.namespaces.first
|
13
|
+
|
14
|
+
expect(ns).to be_namespace
|
15
|
+
expect(ns.prefix).to eq("xs")
|
16
|
+
expect(ns.uri).to eq("http://www.w3.org/2001/XMLSchema")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "creates default namespace" do
|
20
|
+
element.add_namespace(nil, "http://example.org")
|
21
|
+
ns = element.namespaces.first
|
22
|
+
|
23
|
+
expect(ns.prefix).to be_nil
|
24
|
+
expect(ns.uri).to eq("http://example.org")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "validates URI" do
|
28
|
+
expect do
|
29
|
+
element.add_namespace("xs", "invalid uri")
|
30
|
+
end.to raise_error(Moxml::NamespaceError, "Invalid URI: invalid uri")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "string representation" do
|
35
|
+
it "formats prefixed namespace" do
|
36
|
+
element.add_namespace("xs", "http://www.w3.org/2001/XMLSchema")
|
37
|
+
expect(element.namespaces.first.to_s).to eq('xmlns:xs="http://www.w3.org/2001/XMLSchema"')
|
38
|
+
end
|
39
|
+
|
40
|
+
it "formats default namespace" do
|
41
|
+
element.add_namespace(nil, "http://example.org")
|
42
|
+
expect(element.namespaces.first.to_s).to eq('xmlns="http://example.org"')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "equality" do
|
47
|
+
let(:ns1) { element.add_namespace("xs", "http://www.w3.org/2001/XMLSchema").namespaces.last }
|
48
|
+
let(:ns2) { element.add_namespace("xs", "http://www.w3.org/2001/XMLSchema").namespaces.last }
|
49
|
+
let(:ns3) { element.add_namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance").namespaces.last }
|
50
|
+
|
51
|
+
it "compares namespaces" do
|
52
|
+
expect(ns1).to eq(ns2)
|
53
|
+
expect(ns1).not_to eq(ns3)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "compares with different elements" do
|
57
|
+
other_element = doc.create_element("other")
|
58
|
+
other_ns = other_element.add_namespace("xs", "http://www.w3.org/2001/XMLSchema").namespaces.first
|
59
|
+
expect(ns1).to eq(other_ns)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "inheritance" do
|
64
|
+
it "does not inherit parent namespaces" do
|
65
|
+
# https://stackoverflow.com/a/67347081
|
66
|
+
root = doc.create_element("root")
|
67
|
+
root.namespace = { "xs" => "http://www.w3.org/2001/XMLSchema" }
|
68
|
+
child = doc.create_element("child")
|
69
|
+
root.add_child(child)
|
70
|
+
|
71
|
+
expect(child.namespace).to be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "inherits default parent namespaces" do
|
75
|
+
root = doc.create_element("root")
|
76
|
+
root.namespace = { nil => "http://www.w3.org/2001/XMLSchema" }
|
77
|
+
child = doc.create_element("child")
|
78
|
+
root.add_child(child)
|
79
|
+
|
80
|
+
expect(child.namespace.prefix).to be_nil
|
81
|
+
expect(child.namespace.uri).to eq("http://www.w3.org/2001/XMLSchema")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "overrides parent namespace" do
|
85
|
+
root = doc.create_element("root")
|
86
|
+
root.namespace = { "ns" => "http://example.org/1" }
|
87
|
+
child = doc.create_element("child")
|
88
|
+
child.namespace = { "ns" => "http://example.org/2" }
|
89
|
+
root.add_child(child)
|
90
|
+
|
91
|
+
expect(root.namespace.uri).to eq("http://example.org/1")
|
92
|
+
expect(child.namespace.uri).to eq("http://example.org/2")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Moxml::Node" do
|
4
|
+
describe Moxml::Node do
|
5
|
+
let(:context) { Moxml.new }
|
6
|
+
let(:doc) { context.parse("<root><child>text</child></root>") }
|
7
|
+
let(:root) { doc.root }
|
8
|
+
let(:child) { root.children.first }
|
9
|
+
|
10
|
+
describe "#document" do
|
11
|
+
it "returns containing document" do
|
12
|
+
expect(root.document).to eq(doc)
|
13
|
+
expect(child.document).to eq(doc)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#parent" do
|
18
|
+
it "returns parent node" do
|
19
|
+
expect(child.parent).to eq(root)
|
20
|
+
expect(root.parent).to be_a(Moxml::Document)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#children" do
|
25
|
+
it "returns node set of children" do
|
26
|
+
expect(root.children).to be_a(Moxml::NodeSet)
|
27
|
+
expect(root.children.size).to eq(1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "sibling access" do
|
32
|
+
let(:doc) { context.parse("<root><a/>text<b/></root>") }
|
33
|
+
let(:first) { doc.root.children[0] }
|
34
|
+
let(:middle) { doc.root.children[1] }
|
35
|
+
let(:last) { doc.root.children[2] }
|
36
|
+
|
37
|
+
it "navigates between siblings" do
|
38
|
+
expect(first.next_sibling).to eq(middle)
|
39
|
+
expect(middle.next_sibling).to eq(last)
|
40
|
+
expect(last.previous_sibling).to eq(middle)
|
41
|
+
expect(middle.previous_sibling).to eq(first)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "node manipulation" do
|
46
|
+
it "adds child" do
|
47
|
+
new_child = doc.create_element("new")
|
48
|
+
root.add_child(new_child)
|
49
|
+
expect(root.children.last).to eq(new_child)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "adds previous sibling" do
|
53
|
+
new_node = doc.create_element("new")
|
54
|
+
child.add_previous_sibling(new_node)
|
55
|
+
expect(root.children.first).to eq(new_node)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "adds next sibling" do
|
59
|
+
new_node = doc.create_element("new")
|
60
|
+
child.add_next_sibling(new_node)
|
61
|
+
expect(root.children.last).to eq(new_node)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "removes node" do
|
65
|
+
child.remove
|
66
|
+
expect(root.children).to be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it "replaces node" do
|
70
|
+
new_node = doc.create_element("new")
|
71
|
+
child.replace(new_node)
|
72
|
+
expect(root.children.first).to eq(new_node)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#to_xml" do
|
77
|
+
let(:context) { Moxml.new }
|
78
|
+
let(:doc) { context.parse("<root><child>text</child></root>") }
|
79
|
+
|
80
|
+
it "uses native serialization" do
|
81
|
+
expect(context.config.adapter).to receive(:serialize)
|
82
|
+
.with(doc.native, hash_including(indent: 2))
|
83
|
+
|
84
|
+
doc.to_xml(indent: 2)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "passes through serialization options" do
|
88
|
+
expect(context.config.adapter).to receive(:serialize)
|
89
|
+
.with(doc.native, hash_including(encoding: "UTF-8", indent: 4))
|
90
|
+
|
91
|
+
doc.to_xml(encoding: "UTF-8", indent: 4)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "XPath support" do
|
96
|
+
let(:doc) { context.parse("<root><a><b>1</b></a><a><b>2</b></a></root>") }
|
97
|
+
|
98
|
+
it "finds nodes by xpath" do
|
99
|
+
nodes = doc.xpath("//b")
|
100
|
+
expect(nodes.size).to eq(2)
|
101
|
+
expect(nodes.map(&:text)).to eq(%w[1 2])
|
102
|
+
end
|
103
|
+
|
104
|
+
it "finds first node by xpath" do
|
105
|
+
node = doc.at_xpath("//b")
|
106
|
+
expect(node.text).to eq("1")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Moxml::NodeSet" do
|
4
|
+
describe Moxml::NodeSet do
|
5
|
+
let(:context) { Moxml.new }
|
6
|
+
let(:xml) do
|
7
|
+
<<~XML
|
8
|
+
<root>
|
9
|
+
<child>First</child>
|
10
|
+
<child>Second</child>
|
11
|
+
<child>Third</child>
|
12
|
+
</root>
|
13
|
+
XML
|
14
|
+
end
|
15
|
+
let(:doc) { context.parse(xml) }
|
16
|
+
let(:nodes) { doc.xpath("//child") }
|
17
|
+
|
18
|
+
it "implements Enumerable" do
|
19
|
+
expect(nodes).to be_a(Enumerable)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "is a NodeSet" do
|
23
|
+
expect(nodes).to be_a(described_class)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "enumeration" do
|
27
|
+
it "iterates over nodes" do
|
28
|
+
texts = []
|
29
|
+
nodes.each { |node| texts << node.text }
|
30
|
+
expect(texts).to eq(%w[First Second Third])
|
31
|
+
end
|
32
|
+
|
33
|
+
it "maps nodes" do
|
34
|
+
texts = nodes.map(&:text)
|
35
|
+
expect(texts).to eq(%w[First Second Third])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "selects nodes" do
|
39
|
+
selected = nodes.select { |node| node.text.include?("i") }
|
40
|
+
expect(selected.size).to eq(2)
|
41
|
+
expect(selected.map(&:text)).to eq(%w[First Third])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "access methods" do
|
46
|
+
it "accesses by index" do
|
47
|
+
expect(nodes[0].text).to eq("First")
|
48
|
+
expect(nodes[1].text).to eq("Second")
|
49
|
+
expect(nodes[-1].text).to eq("Third")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "accesses by range" do
|
53
|
+
subset = nodes[0..1]
|
54
|
+
expect(subset).to be_a(described_class)
|
55
|
+
expect(subset.size).to eq(2)
|
56
|
+
expect(subset.map(&:text)).to eq(%w[First Second])
|
57
|
+
end
|
58
|
+
|
59
|
+
it "provides first and last" do
|
60
|
+
expect(nodes.first.text).to eq("First")
|
61
|
+
expect(nodes.last.text).to eq("Third")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "modification methods" do
|
66
|
+
it "removes all nodes" do
|
67
|
+
nodes.remove
|
68
|
+
expect(doc.xpath("//child")).to be_empty
|
69
|
+
end
|
70
|
+
|
71
|
+
it "preserves document structure after removal" do
|
72
|
+
nodes.remove
|
73
|
+
expect(doc.root).not_to be_nil
|
74
|
+
expect(doc.root.name).to eq("root")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "concatenation" do
|
79
|
+
it "combines node sets" do
|
80
|
+
other_doc = context.parse("<root><item>Fourth</item></root>")
|
81
|
+
other_nodes = other_doc.xpath("//item")
|
82
|
+
combined = nodes + other_nodes
|
83
|
+
|
84
|
+
expect(combined).to be_a(described_class)
|
85
|
+
expect(combined.size).to eq(4)
|
86
|
+
expect(combined.map(&:text)).to eq(%w[First Second Third Fourth])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Moxml::ProcessingInstruction" do
|
4
|
+
let(:context) { Moxml.new }
|
5
|
+
let(:doc) { context.create_document }
|
6
|
+
let(:pi) { doc.create_processing_instruction("xml-stylesheet", 'href="style.xsl" type="text/xsl"') }
|
7
|
+
|
8
|
+
it "identifies as processing instruction node" do
|
9
|
+
expect(pi).to be_processing_instruction
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "target manipulation" do
|
13
|
+
it "gets target" do
|
14
|
+
expect(pi.target).to eq("xml-stylesheet")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets target" do
|
18
|
+
pi.target = "new-target"
|
19
|
+
expect(pi.target).to eq("new-target")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "handles nil target" do
|
23
|
+
pi.target = nil
|
24
|
+
expect(pi.target).to eq("")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "content manipulation" do
|
29
|
+
it "gets content" do
|
30
|
+
expect(pi.content).to eq('href="style.xsl" type="text/xsl"')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "sets content" do
|
34
|
+
pi.content = 'href="new.xsl"'
|
35
|
+
expect(pi.content).to eq('href="new.xsl"')
|
36
|
+
end
|
37
|
+
|
38
|
+
it "handles nil content" do
|
39
|
+
pi.content = nil
|
40
|
+
expect(pi.content).to eq("")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "serialization" do
|
45
|
+
it "formats processing instruction" do
|
46
|
+
expect(pi.to_xml).to eq('<?xml-stylesheet href="style.xsl" type="text/xsl"?>')
|
47
|
+
end
|
48
|
+
|
49
|
+
it "handles special characters" do
|
50
|
+
pi.content = '< > & " \''
|
51
|
+
expect(pi.to_xml).to include('< > & " \'')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "node operations" do
|
56
|
+
let(:element) { doc.create_element("test") }
|
57
|
+
|
58
|
+
it "adds to element" do
|
59
|
+
element.add_child(pi)
|
60
|
+
expect(element.to_xml).to include("<?xml-stylesheet")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "removes from element" do
|
64
|
+
element.add_child(pi)
|
65
|
+
pi.remove
|
66
|
+
expect(element.children).to be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it "replaces with another node" do
|
70
|
+
element.add_child(pi)
|
71
|
+
text = doc.create_text("replacement")
|
72
|
+
pi.replace(text)
|
73
|
+
expect(element.text).to eq("replacement")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "common use cases" do
|
78
|
+
it "creates stylesheet instruction" do
|
79
|
+
pi = doc.create_processing_instruction("xml-stylesheet", 'type="text/xsl" href="style.xsl"')
|
80
|
+
expect(pi.to_xml).to eq('<?xml-stylesheet type="text/xsl" href="style.xsl"?>')
|
81
|
+
end
|
82
|
+
|
83
|
+
it "creates PHP instruction" do
|
84
|
+
pi = doc.create_processing_instruction("php", 'echo "Hello";')
|
85
|
+
expect(pi.to_xml).to eq('<?php echo "Hello";?>')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Moxml::Text" do
|
4
|
+
let(:context) { Moxml.new }
|
5
|
+
let(:doc) { context.create_document }
|
6
|
+
let(:text) { doc.create_text("content") }
|
7
|
+
|
8
|
+
it "identifies as text node" do
|
9
|
+
expect(text).to be_text
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "content manipulation" do
|
13
|
+
it "gets content" do
|
14
|
+
expect(text.content).to eq("content")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets content" do
|
18
|
+
text.content = "new content"
|
19
|
+
expect(text.content).to eq("new content")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "handles nil content" do
|
23
|
+
text.content = nil
|
24
|
+
expect(text.content).to eq("")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "converts non-string content" do
|
28
|
+
text.content = 123
|
29
|
+
expect(text.content).to eq("123")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "special characters" do
|
34
|
+
it "encodes basic XML entities" do
|
35
|
+
text.content = "< > & \" '"
|
36
|
+
expect(text.to_xml).to eq("< > & \" '")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "preserves whitespace" do
|
40
|
+
text.content = " spaced content\n\t"
|
41
|
+
expect(text.content).to eq(" spaced content\n\t")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "node operations" do
|
46
|
+
let(:element) { doc.create_element("test") }
|
47
|
+
|
48
|
+
it "adds to element" do
|
49
|
+
element.add_child(text)
|
50
|
+
expect(element.text).to eq("content")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "removes from element" do
|
54
|
+
element.add_child(text)
|
55
|
+
text.remove
|
56
|
+
expect(element.text).to eq("")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "replaces with another node" do
|
60
|
+
element.add_child(text)
|
61
|
+
new_text = doc.create_text("replacement")
|
62
|
+
text.replace(new_text)
|
63
|
+
expect(element.text).to eq("replacement")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|