moxml 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +15 -0
  3. data/.github/workflows/release.yml +23 -0
  4. data/.gitignore +3 -0
  5. data/.rubocop.yml +2 -0
  6. data/.rubocop_todo.yml +65 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile +10 -3
  9. data/README.adoc +401 -594
  10. data/lib/moxml/adapter/base.rb +102 -0
  11. data/lib/moxml/adapter/customized_oga/xml_declaration.rb +18 -0
  12. data/lib/moxml/adapter/customized_oga/xml_generator.rb +104 -0
  13. data/lib/moxml/adapter/nokogiri.rb +319 -0
  14. data/lib/moxml/adapter/oga.rb +318 -0
  15. data/lib/moxml/adapter/ox.rb +325 -0
  16. data/lib/moxml/adapter.rb +26 -170
  17. data/lib/moxml/attribute.rb +47 -14
  18. data/lib/moxml/builder.rb +64 -0
  19. data/lib/moxml/cdata.rb +4 -26
  20. data/lib/moxml/comment.rb +6 -22
  21. data/lib/moxml/config.rb +39 -15
  22. data/lib/moxml/context.rb +29 -0
  23. data/lib/moxml/declaration.rb +16 -26
  24. data/lib/moxml/doctype.rb +9 -0
  25. data/lib/moxml/document.rb +51 -63
  26. data/lib/moxml/document_builder.rb +87 -0
  27. data/lib/moxml/element.rb +63 -97
  28. data/lib/moxml/error.rb +20 -0
  29. data/lib/moxml/namespace.rb +12 -37
  30. data/lib/moxml/node.rb +78 -58
  31. data/lib/moxml/node_set.rb +19 -222
  32. data/lib/moxml/processing_instruction.rb +6 -25
  33. data/lib/moxml/text.rb +4 -26
  34. data/lib/moxml/version.rb +1 -1
  35. data/lib/moxml/xml_utils/encoder.rb +55 -0
  36. data/lib/moxml/xml_utils.rb +80 -0
  37. data/lib/moxml.rb +33 -33
  38. data/moxml.gemspec +1 -1
  39. data/spec/moxml/adapter/nokogiri_spec.rb +14 -0
  40. data/spec/moxml/adapter/oga_spec.rb +14 -0
  41. data/spec/moxml/adapter/ox_spec.rb +49 -0
  42. data/spec/moxml/all_with_adapters_spec.rb +46 -0
  43. data/spec/moxml/config_spec.rb +55 -0
  44. data/spec/moxml/error_spec.rb +71 -0
  45. data/spec/moxml/examples/adapter_spec.rb +27 -0
  46. data/spec/moxml_spec.rb +50 -0
  47. data/spec/spec_helper.rb +32 -0
  48. data/spec/support/shared_examples/attribute.rb +165 -0
  49. data/spec/support/shared_examples/builder.rb +25 -0
  50. data/spec/support/shared_examples/cdata.rb +70 -0
  51. data/spec/support/shared_examples/comment.rb +65 -0
  52. data/spec/support/shared_examples/context.rb +35 -0
  53. data/spec/support/shared_examples/declaration.rb +93 -0
  54. data/spec/support/shared_examples/doctype.rb +25 -0
  55. data/spec/support/shared_examples/document.rb +110 -0
  56. data/spec/support/shared_examples/document_builder.rb +43 -0
  57. data/spec/support/shared_examples/edge_cases.rb +185 -0
  58. data/spec/support/shared_examples/element.rb +130 -0
  59. data/spec/support/shared_examples/examples/attribute.rb +42 -0
  60. data/spec/support/shared_examples/examples/basic_usage.rb +67 -0
  61. data/spec/support/shared_examples/examples/memory.rb +54 -0
  62. data/spec/support/shared_examples/examples/namespace.rb +65 -0
  63. data/spec/support/shared_examples/examples/readme_examples.rb +100 -0
  64. data/spec/support/shared_examples/examples/thread_safety.rb +43 -0
  65. data/spec/support/shared_examples/examples/xpath.rb +39 -0
  66. data/spec/support/shared_examples/integration.rb +135 -0
  67. data/spec/support/shared_examples/namespace.rb +96 -0
  68. data/spec/support/shared_examples/node.rb +110 -0
  69. data/spec/support/shared_examples/node_set.rb +90 -0
  70. data/spec/support/shared_examples/processing_instruction.rb +88 -0
  71. data/spec/support/shared_examples/text.rb +66 -0
  72. data/spec/support/shared_examples/xml_adapter.rb +191 -0
  73. data/spec/support/xml_matchers.rb +27 -0
  74. metadata +55 -6
  75. data/.github/workflows/main.yml +0 -27
  76. data/lib/moxml/error_handler.rb +0 -77
  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,130 @@
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="&lt; &gt; &amp; &quot; \'')
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
+
110
+ describe "complex element structures" do
111
+ it "creates nested paragraphs without namespaces" do
112
+ outer_p = doc.create_element("p")
113
+ inner_p = doc.create_element("p")
114
+ inner_p.add_child("Some text inside paragraph")
115
+ outer_p.add_child(inner_p)
116
+
117
+ expect(outer_p.to_xml).to include("<p>Some text inside paragraph</p>")
118
+ end
119
+
120
+ it "does not return text of child elements in root text" do
121
+ outer_p = doc.create_element("p")
122
+ inner_p = doc.create_element("p")
123
+ inner_p.add_child("Some text inside paragraph")
124
+ outer_p.add_child(inner_p)
125
+ expect(outer_p.inner_text).to eq("")
126
+ expect(inner_p.text).to include("Some text inside paragraph")
127
+ end
128
+ end
129
+ end
130
+ 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="&lt; &gt; &amp; &quot; \''
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