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.
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 +400 -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 +314 -0
  14. data/lib/moxml/adapter/oga.rb +309 -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 +61 -99
  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 +110 -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,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="&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
+ 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="&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