moxml 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|