moxml 0.1.0 → 0.1.1
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 +400 -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 +314 -0
- data/lib/moxml/adapter/oga.rb +309 -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 +61 -99
- 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 +110 -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,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Moxml Edge Cases" do
|
4
|
+
let(:context) { Moxml.new }
|
5
|
+
|
6
|
+
describe "special characters handling" do
|
7
|
+
it "handles all kinds of whitespace", skip: "carriege returns are troublesome" do
|
8
|
+
# Nokogiri can't handle carriege returns properly
|
9
|
+
# https://github.com/sparklemotion/nokogiri/issues/1356
|
10
|
+
xml = "<root>\u0020\u0009 \u000D\u000A \u000D</root>"
|
11
|
+
doc = context.parse(xml)
|
12
|
+
expect(doc.root.text).to eq(" \t \r\n \r")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "handles unicode characters" do
|
16
|
+
text = "Hello 世界 🌍"
|
17
|
+
doc = context.create_document
|
18
|
+
element = doc.create_element("test")
|
19
|
+
element.text = text
|
20
|
+
expect(element.text).to eq(text)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "handles zero-width characters" do
|
24
|
+
text = "test\u200B\u200Ctest"
|
25
|
+
doc = context.create_document
|
26
|
+
element = doc.create_element("test")
|
27
|
+
element.text = text
|
28
|
+
expect(element.text).to eq(text)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "malformed content handling" do
|
33
|
+
it "handles CDATA with nested markers" do
|
34
|
+
cdata_text = "]]>]]>]]>"
|
35
|
+
doc = context.create_document
|
36
|
+
cdata = doc.create_cdata(cdata_text)
|
37
|
+
expect(cdata.to_xml).to include(
|
38
|
+
"]]]]><![CDATA[>]]]]><![CDATA[>]]]]><![CDATA[>"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "handles invalid processing instruction content" do
|
43
|
+
content = "?> invalid"
|
44
|
+
doc = context.create_document
|
45
|
+
pi = doc.create_processing_instruction("test", content)
|
46
|
+
expect(pi.to_xml).not_to include("?>?>")
|
47
|
+
end
|
48
|
+
|
49
|
+
it "rejects comments with double hyphens" do
|
50
|
+
doc = context.create_document
|
51
|
+
expect do
|
52
|
+
doc.create_comment("-- test -- comment --")
|
53
|
+
end.to raise_error(Moxml::ValidationError, "XML comment cannot start or end with a hyphen")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "rejects comments starting with hyphen" do
|
57
|
+
doc = context.create_document
|
58
|
+
expect do
|
59
|
+
doc.create_comment("-starting with hyphen")
|
60
|
+
end.to raise_error(Moxml::ValidationError, "XML comment cannot start or end with a hyphen")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "rejects comments ending with hyphen" do
|
64
|
+
doc = context.create_document
|
65
|
+
expect do
|
66
|
+
doc.create_comment("ending with hyphen-")
|
67
|
+
end.to raise_error(Moxml::ValidationError, "XML comment cannot start or end with a hyphen")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "accepts valid comments" do
|
71
|
+
doc = context.create_document
|
72
|
+
comment = doc.create_comment("valid - comment")
|
73
|
+
expect(comment.content).to eq("valid - comment")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "namespace edge cases" do
|
78
|
+
it "handles default namespace changes" do
|
79
|
+
xml = <<~XML
|
80
|
+
<root xmlns="http://default1.org">
|
81
|
+
<child xmlns="http://default2.org">
|
82
|
+
<grandchild xmlns=""/>
|
83
|
+
</child>
|
84
|
+
</root>
|
85
|
+
XML
|
86
|
+
|
87
|
+
doc = context.parse(xml)
|
88
|
+
grandchild = doc.at_xpath("//xmlns:grandchild", "xmlns" => "")
|
89
|
+
expect(grandchild.namespace.uri).to eq("")
|
90
|
+
end
|
91
|
+
|
92
|
+
it "handles recursive namespace definitions" do
|
93
|
+
xml = <<~XML
|
94
|
+
<root xmlns:a="http://a.org">
|
95
|
+
<a:child xmlns:a="http://b.org">
|
96
|
+
<a:grandchild/>
|
97
|
+
</a:child>
|
98
|
+
</root>
|
99
|
+
XML
|
100
|
+
|
101
|
+
doc = context.parse(xml)
|
102
|
+
grandchild = doc.at_xpath("//a:grandchild", "a" => "http://b.org")
|
103
|
+
expect(grandchild.namespace.uri).to eq("http://b.org")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "attribute edge cases" do
|
108
|
+
it "handles attributes with same local name but different namespaces" do
|
109
|
+
xml = <<~XML
|
110
|
+
<root xmlns:a="http://a.org" xmlns:b="http://b.org">
|
111
|
+
<element a:id="1" b:id="2"/>
|
112
|
+
</root>
|
113
|
+
XML
|
114
|
+
|
115
|
+
doc = context.parse(xml)
|
116
|
+
element = doc.at_xpath("//element")
|
117
|
+
expect(element["a:id"]).to eq("1")
|
118
|
+
expect(element["b:id"]).to eq("2")
|
119
|
+
end
|
120
|
+
|
121
|
+
it "handles special attribute values" do
|
122
|
+
doc = context.create_document
|
123
|
+
element = doc.create_element("test")
|
124
|
+
|
125
|
+
special_values = {
|
126
|
+
"empty" => "",
|
127
|
+
"space" => " ",
|
128
|
+
"tabs_newlines" => "\t\n",
|
129
|
+
"unicode" => "⚡",
|
130
|
+
"entities" => "<&>'\""
|
131
|
+
}
|
132
|
+
|
133
|
+
special_values.each do |name, value|
|
134
|
+
element[name] = value
|
135
|
+
expect(element[name]).to eq(value)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "document structure edge cases" do
|
141
|
+
it "handles deeply nested elements" do
|
142
|
+
doc = context.create_document
|
143
|
+
current = doc.create_element("root")
|
144
|
+
doc.add_child(current)
|
145
|
+
|
146
|
+
1000.times do |i|
|
147
|
+
nested = doc.create_element("nested#{i}")
|
148
|
+
current.add_child(nested)
|
149
|
+
current = nested
|
150
|
+
end
|
151
|
+
|
152
|
+
expect(doc.to_xml).to include("<nested999>")
|
153
|
+
end
|
154
|
+
|
155
|
+
it "handles large number of siblings" do
|
156
|
+
doc = context.create_document
|
157
|
+
root = doc.create_element("root")
|
158
|
+
doc.add_child(root)
|
159
|
+
|
160
|
+
1000.times do |i|
|
161
|
+
child = doc.create_element("child")
|
162
|
+
child.text = i.to_s
|
163
|
+
root.add_child(child)
|
164
|
+
end
|
165
|
+
|
166
|
+
expect(root.children.size).to eq(1000)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "handles mixed content with all node types" do
|
170
|
+
doc = context.create_document
|
171
|
+
root = doc.create_element("root")
|
172
|
+
doc.add_child(root)
|
173
|
+
|
174
|
+
root.add_child("text1")
|
175
|
+
root.add_child(doc.create_comment("comment"))
|
176
|
+
root.add_child("text2")
|
177
|
+
root.add_child(doc.create_cdata("<tag>"))
|
178
|
+
root.add_child(doc.create_element("child"))
|
179
|
+
root.add_child("text3")
|
180
|
+
root.add_child(doc.create_processing_instruction("pi", "data"))
|
181
|
+
|
182
|
+
expect(root.children.size).to eq(7)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Moxml::Element" do
|
4
|
+
describe Moxml::Element do
|
5
|
+
let(:context) { Moxml.new }
|
6
|
+
let(:doc) { context.create_document }
|
7
|
+
let(:element) { doc.create_element("test") }
|
8
|
+
|
9
|
+
describe "name handling" do
|
10
|
+
it "gets name" do
|
11
|
+
expect(element.name).to eq("test")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "sets name" do
|
15
|
+
element.name = "new_name"
|
16
|
+
expect(element.name).to eq("new_name")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "attributes" do
|
21
|
+
before { element["id"] = "123" }
|
22
|
+
|
23
|
+
it "sets attribute" do
|
24
|
+
expect(element["id"]).to eq("123")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "gets attribute" do
|
28
|
+
expect(element.attribute("id")).to be_a(Moxml::Attribute)
|
29
|
+
expect(element.attribute("id").value).to eq("123")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "gets all attributes" do
|
33
|
+
element["class"] = "test"
|
34
|
+
expect(element.attributes.size).to eq(2)
|
35
|
+
expect(element.attributes.map(&:name)).to contain_exactly("id", "class")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "removes attribute" do
|
39
|
+
element.remove_attribute("id")
|
40
|
+
expect(element["id"]).to be_nil
|
41
|
+
end
|
42
|
+
|
43
|
+
it "handles special characters" do
|
44
|
+
element["special"] = '< > & " \''
|
45
|
+
expect(element.to_xml).to include('special="< > & " \'')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "namespaces" do
|
50
|
+
it "adds namespace" do
|
51
|
+
element.add_namespace("x", "http://example.org")
|
52
|
+
expect(element.namespaces.size).to eq(1)
|
53
|
+
expect(element.namespaces.first.prefix).to eq("x")
|
54
|
+
expect(element.namespaces.first.uri).to eq("http://example.org")
|
55
|
+
end
|
56
|
+
|
57
|
+
it "sets namespace" do
|
58
|
+
ns = element.add_namespace("x", "http://example.org").namespace
|
59
|
+
element.namespace = ns
|
60
|
+
expect(element.namespace).to eq(ns)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "adds default namespace" do
|
64
|
+
element.add_namespace(nil, "http://example.org")
|
65
|
+
expect(element.namespace.prefix).to be_nil
|
66
|
+
expect(element.namespace.uri).to eq("http://example.org")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "content manipulation" do
|
71
|
+
it "sets text content" do
|
72
|
+
element.text = "content"
|
73
|
+
expect(element.text).to eq("content")
|
74
|
+
end
|
75
|
+
|
76
|
+
it "appends text" do
|
77
|
+
element.text = "first"
|
78
|
+
element.add_child("second")
|
79
|
+
expect(element.text).to eq("firstsecond")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sets inner HTML" do
|
83
|
+
element.inner_html = "<child>text</child>"
|
84
|
+
expect(element.children.size).to eq(1)
|
85
|
+
expect(element.children.first.name).to eq("child")
|
86
|
+
expect(element.children.first.text).to eq("text")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "node manipulation" do
|
91
|
+
it "adds child element" do
|
92
|
+
child = doc.create_element("child")
|
93
|
+
element.add_child(child)
|
94
|
+
expect(element.children.first).to eq(child)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "adds child text" do
|
98
|
+
element.add_child("text")
|
99
|
+
expect(element.text).to eq("text")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "adds mixed content" do
|
103
|
+
element.add_child("text")
|
104
|
+
element.add_child(doc.create_element("child"))
|
105
|
+
element.add_child("more")
|
106
|
+
expect(element.to_xml).to include("text<child></child>more")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Attribute Examples" do
|
4
|
+
let(:context) { Moxml.new }
|
5
|
+
let(:doc) { context.create_document }
|
6
|
+
|
7
|
+
describe "Attribute manipulation" do
|
8
|
+
it "handles basic attributes" do
|
9
|
+
element = doc.create_element("test")
|
10
|
+
element["id"] = "123"
|
11
|
+
element["class"] = "main"
|
12
|
+
|
13
|
+
expect(element.to_xml).to include('id="123"')
|
14
|
+
expect(element.to_xml).to include('class="main"')
|
15
|
+
end
|
16
|
+
|
17
|
+
it "handles namespaced attributes" do
|
18
|
+
element = doc.create_element("test")
|
19
|
+
element.add_namespace("xs", "http://www.w3.org/2001/XMLSchema")
|
20
|
+
element["xs:type"] = "string"
|
21
|
+
|
22
|
+
expect(element.to_xml).to include('xs:type="string"')
|
23
|
+
end
|
24
|
+
|
25
|
+
it "handles special characters in attributes" do
|
26
|
+
element = doc.create_element("test")
|
27
|
+
element["special"] = '< > & " \''
|
28
|
+
|
29
|
+
expect(element.to_xml).to include(
|
30
|
+
'special="< > & " \''
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "removes attributes" do
|
35
|
+
element = doc.create_element("test")
|
36
|
+
element["temp"] = "value"
|
37
|
+
element.remove_attribute("temp")
|
38
|
+
|
39
|
+
expect(element.to_xml).not_to include("temp")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Basic Usage Examples" do
|
4
|
+
let(:context) { Moxml.new }
|
5
|
+
|
6
|
+
describe "Document creation" do
|
7
|
+
it "creates basic document" do
|
8
|
+
doc = context.create_document
|
9
|
+
root = doc.create_element("book")
|
10
|
+
doc.add_child(root)
|
11
|
+
|
12
|
+
expect(doc.to_xml).to include("<book></book>")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "creates document with declaration" do
|
16
|
+
doc = context.create_document
|
17
|
+
doc.add_child(doc.create_declaration("1.0", "UTF-8"))
|
18
|
+
root = doc.create_element("book")
|
19
|
+
doc.add_child(root)
|
20
|
+
|
21
|
+
expect(doc.to_xml).to include(
|
22
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
23
|
+
"<book></book>"
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "creates document with processing instructions" do
|
28
|
+
doc = context.create_document
|
29
|
+
pi = doc.create_processing_instruction("xml-stylesheet",
|
30
|
+
'type="text/xsl" href="style.xsl"')
|
31
|
+
doc.add_child(pi)
|
32
|
+
doc.add_child(doc.create_element("root"))
|
33
|
+
|
34
|
+
expect(doc.to_xml).to include(
|
35
|
+
'<?xml-stylesheet type="text/xsl" href="style.xsl"?>',
|
36
|
+
"<root></root>"
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "Node creation" do
|
42
|
+
let(:doc) { context.create_document }
|
43
|
+
|
44
|
+
it "creates different node types" do
|
45
|
+
element = doc.create_element("test")
|
46
|
+
text = doc.create_text("content")
|
47
|
+
cdata = doc.create_cdata("<xml>data</xml>")
|
48
|
+
comment = doc.create_comment("note")
|
49
|
+
pi = doc.create_processing_instruction("target", "data")
|
50
|
+
|
51
|
+
root = doc.create_element("root")
|
52
|
+
doc.add_child(root)
|
53
|
+
root.add_child(element)
|
54
|
+
root.add_child(text)
|
55
|
+
root.add_child(cdata)
|
56
|
+
root.add_child(comment)
|
57
|
+
root.add_child(pi)
|
58
|
+
|
59
|
+
xml = doc.to_xml
|
60
|
+
expect(xml).to include("<test></test>")
|
61
|
+
expect(xml).to include("content")
|
62
|
+
expect(xml).to include("<![CDATA[<xml>data</xml>]]>")
|
63
|
+
expect(xml).to include("<!--note-->")
|
64
|
+
expect(xml).to include("<?target data?>")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "get_process_mem"
|
4
|
+
require "tempfile"
|
5
|
+
|
6
|
+
RSpec.shared_examples "Memory Usage Examples" do
|
7
|
+
let(:context) { Moxml.new }
|
8
|
+
|
9
|
+
describe "Memory efficient processing" do
|
10
|
+
it "processes large documents efficiently", skip: "Oga fails this test" do
|
11
|
+
# Create large document
|
12
|
+
doc = context.create_document
|
13
|
+
root = doc.create_element("root")
|
14
|
+
doc.add_child(root)
|
15
|
+
|
16
|
+
1000.times do |i|
|
17
|
+
node = doc.create_element("large-node")
|
18
|
+
node.add_child(doc.create_text("Content #{i}"))
|
19
|
+
root.add_child(node)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Process and remove nodes
|
23
|
+
memory_before = GetProcessMem.new.bytes
|
24
|
+
doc.xpath("//large-node").each(&:remove)
|
25
|
+
GC.start
|
26
|
+
memory_after = GetProcessMem.new.bytes
|
27
|
+
|
28
|
+
expect(memory_after).to be <= memory_before
|
29
|
+
expect(doc.xpath("//large-node")).to be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it "handles streaming processing" do
|
33
|
+
# Create temp file
|
34
|
+
file = Tempfile.new(["test", ".xml"])
|
35
|
+
begin
|
36
|
+
file.write("<root><item>data</item></root>")
|
37
|
+
file.close
|
38
|
+
|
39
|
+
# Process file
|
40
|
+
doc = nil
|
41
|
+
File.open(file.path) do |f|
|
42
|
+
doc = context.parse(f)
|
43
|
+
expect(doc.at_xpath("//item").text).to eq("data")
|
44
|
+
doc = nil
|
45
|
+
end
|
46
|
+
GC.start
|
47
|
+
|
48
|
+
expect(doc).to be_nil
|
49
|
+
ensure
|
50
|
+
file.unlink
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Namespace Examples" do
|
4
|
+
let(:context) { Moxml.new }
|
5
|
+
|
6
|
+
describe "Namespace handling" do
|
7
|
+
it "handles default namespace" do
|
8
|
+
doc = context.create_document
|
9
|
+
root = doc.create_element("root")
|
10
|
+
root.add_namespace(nil, "http://example.org")
|
11
|
+
doc.add_child(root)
|
12
|
+
|
13
|
+
expect(doc.to_xml).to include('xmlns="http://example.org"')
|
14
|
+
end
|
15
|
+
|
16
|
+
it "handles prefixed namespaces" do
|
17
|
+
doc = context.create_document
|
18
|
+
root = doc.create_element("root")
|
19
|
+
root.add_namespace("dc", "http://purl.org/dc/elements/1.1/")
|
20
|
+
doc.add_child(root)
|
21
|
+
|
22
|
+
title = doc.create_element("dc:title")
|
23
|
+
title.add_child(doc.create_text("Test"))
|
24
|
+
root.add_child(title)
|
25
|
+
|
26
|
+
expect(doc.to_xml).to include(
|
27
|
+
'xmlns:dc="http://purl.org/dc/elements/1.1/"',
|
28
|
+
"<dc:title>Test</dc:title>"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "handles namespace inheritance" do
|
33
|
+
doc = context.create_document
|
34
|
+
root = doc.create_element("root")
|
35
|
+
root.add_namespace("ns", "http://example.org")
|
36
|
+
doc.add_child(root)
|
37
|
+
|
38
|
+
child = doc.create_element("ns:child")
|
39
|
+
root.add_child(child)
|
40
|
+
grandchild = doc.create_element("ns:grandchild")
|
41
|
+
child.add_child(grandchild)
|
42
|
+
|
43
|
+
expect(doc.to_xml).to include(
|
44
|
+
"<ns:child>",
|
45
|
+
"<ns:grandchild>"
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "handles namespace overriding" do
|
50
|
+
doc = context.create_document
|
51
|
+
root = doc.create_element("root")
|
52
|
+
root.add_namespace("ns", "http://example.org/1")
|
53
|
+
doc.add_child(root)
|
54
|
+
|
55
|
+
child = doc.create_element("child")
|
56
|
+
child.add_namespace("ns", "http://example.org/2")
|
57
|
+
root.add_child(child)
|
58
|
+
|
59
|
+
expect(doc.to_xml).to include(
|
60
|
+
'xmlns:ns="http://example.org/1"',
|
61
|
+
'xmlns:ns="http://example.org/2"'
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "README Examples" do
|
4
|
+
describe "Quick Start example" do
|
5
|
+
it "builds document as shown in README" do
|
6
|
+
context = Moxml.new
|
7
|
+
doc = context.create_document
|
8
|
+
|
9
|
+
root = doc.create_element("book")
|
10
|
+
doc.add_child(root)
|
11
|
+
|
12
|
+
root.add_namespace("dc", "http://purl.org/dc/elements/1.1/")
|
13
|
+
title = doc.create_element("dc:title")
|
14
|
+
title.add_child(doc.create_text("XML Processing with Ruby"))
|
15
|
+
root.add_child(title)
|
16
|
+
|
17
|
+
expect(doc.to_xml).to include(
|
18
|
+
'<book xmlns:dc="http://purl.org/dc/elements/1.1/">',
|
19
|
+
"<dc:title>XML Processing with Ruby</dc:title>",
|
20
|
+
"</book>"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "Complex document example" do
|
26
|
+
it "builds document with all features" do
|
27
|
+
doc = Moxml.new.create_document
|
28
|
+
|
29
|
+
# Add declaration
|
30
|
+
doc.add_child(doc.create_declaration("1.0", "UTF-8"))
|
31
|
+
|
32
|
+
# Create root with namespace
|
33
|
+
root = doc.create_element("library")
|
34
|
+
root.add_namespace(nil, "http://example.org/library")
|
35
|
+
root.add_namespace("dc", "http://purl.org/dc/elements/1.1/")
|
36
|
+
doc.add_child(root)
|
37
|
+
|
38
|
+
# Add books
|
39
|
+
%w[Ruby XML].each do |title|
|
40
|
+
book = doc.create_element("book")
|
41
|
+
|
42
|
+
# Add metadata
|
43
|
+
dc_title = doc.create_element("dc:title")
|
44
|
+
dc_title.add_child(doc.create_text(title))
|
45
|
+
book.add_child(dc_title)
|
46
|
+
|
47
|
+
# Add description
|
48
|
+
desc = doc.create_element("description")
|
49
|
+
desc.add_child(doc.create_cdata("About #{title}..."))
|
50
|
+
book.add_child(desc)
|
51
|
+
|
52
|
+
root.add_child(book)
|
53
|
+
end
|
54
|
+
|
55
|
+
xml = doc.to_xml
|
56
|
+
expect(xml).to include('<?xml version="1.0" encoding="UTF-8"?>')
|
57
|
+
expect(xml).to include('<library xmlns="http://example.org/library" xmlns:dc="http://purl.org/dc/elements/1.1/">')
|
58
|
+
expect(xml).to include("<dc:title>Ruby</dc:title>")
|
59
|
+
expect(xml).to include("<![CDATA[About Ruby...]]>")
|
60
|
+
expect(xml).to include("<dc:title>XML</dc:title>")
|
61
|
+
expect(xml).to include("<![CDATA[About XML...]]>")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "Error handling example" do
|
66
|
+
it "handles errors as shown in README" do
|
67
|
+
context = Moxml.new
|
68
|
+
|
69
|
+
expect do
|
70
|
+
context.parse("<invalid>")
|
71
|
+
end.to raise_error(Moxml::ParseError)
|
72
|
+
|
73
|
+
doc = context.parse("<root/>")
|
74
|
+
expect do
|
75
|
+
doc.xpath("///")
|
76
|
+
end.to raise_error(Moxml::XPathError)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "Thread safety example" do
|
81
|
+
it "processes XML in thread-safe manner" do
|
82
|
+
processor = Class.new do
|
83
|
+
def initialize
|
84
|
+
@mutex = Mutex.new
|
85
|
+
@context = Moxml.new
|
86
|
+
end
|
87
|
+
|
88
|
+
def process(xml)
|
89
|
+
@mutex.synchronize do
|
90
|
+
doc = @context.parse(xml)
|
91
|
+
doc.to_xml
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end.new
|
95
|
+
|
96
|
+
result = processor.process("<root/>")
|
97
|
+
expect(result).to include("<root></root>")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples "Thread Safety Examples" do
|
4
|
+
describe "Thread-safe processing" do
|
5
|
+
let(:processor_class) do
|
6
|
+
Class.new do
|
7
|
+
def initialize
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@context = Moxml.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(xml)
|
13
|
+
@mutex.synchronize do
|
14
|
+
doc = @context.parse(xml)
|
15
|
+
yield doc if block_given?
|
16
|
+
doc.to_xml
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "handles concurrent processing" do
|
23
|
+
processor = processor_class.new
|
24
|
+
threads = []
|
25
|
+
results = Queue.new
|
26
|
+
|
27
|
+
10.times do |i|
|
28
|
+
threads << Thread.new do
|
29
|
+
xml = "<root><id>#{i}</id></root>"
|
30
|
+
result = processor.process(xml) do |doc|
|
31
|
+
# Simulate some work
|
32
|
+
sleep(rand * 0.1)
|
33
|
+
doc.at_xpath("//id").text
|
34
|
+
end
|
35
|
+
results << result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
threads.each(&:join)
|
40
|
+
expect(results.size).to eq(10)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|