moxml 0.1.0 → 0.1.1
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 +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
|