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,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::Attribute" do
4
+ describe Moxml::Attribute do
5
+ let(:context) { Moxml.new }
6
+ let(:doc) { context.create_document }
7
+ let(:element) { doc.create_element("test") }
8
+
9
+ describe "creation and access" do
10
+ before do
11
+ element["test"] = "value"
12
+ end
13
+
14
+ let(:attribute) { element.attributes.first }
15
+
16
+ it "creates attribute" do
17
+ expect(attribute).to be_a(described_class)
18
+ expect(attribute).to be_attribute
19
+ end
20
+
21
+ it "gets name" do
22
+ expect(attribute.name).to eq("test")
23
+ end
24
+
25
+ it "gets value" do
26
+ expect(attribute.value).to eq("value")
27
+ end
28
+
29
+ it "sets name" do
30
+ attribute.name = "new_name"
31
+ expect(attribute.name).to eq("new_name")
32
+ expect(element["new_name"]).to eq("value")
33
+ expect(element["test"]).to be_nil
34
+ end
35
+
36
+ it "sets value" do
37
+ attribute.value = "new_value"
38
+ expect(attribute.value).to eq("new_value")
39
+ expect(element["test"]).to eq("new_value")
40
+ end
41
+ end
42
+
43
+ describe "namespaces" do
44
+ before do
45
+ element.add_namespace("ns", "http://example.org")
46
+ end
47
+
48
+ it "handles namespaced attributes" do
49
+ element["ns:attr"] = "value"
50
+ attribute = element.attributes.first
51
+
52
+ expect(attribute.namespace).not_to be_nil
53
+ expect(attribute.namespace.prefix).to eq("ns")
54
+ expect(attribute.namespace.uri).to eq("http://example.org")
55
+ end
56
+
57
+ it "changes attribute namespace" do
58
+ element["attr"] = "value"
59
+ attribute = element.attributes.first
60
+
61
+ element.add_namespace("other", "http://other.org")
62
+ new_ns = element.namespaces.last
63
+ attribute.namespace = new_ns
64
+
65
+ expect(attribute.namespace.prefix).to eq("other")
66
+ expect(attribute.to_s).to eq('other:attr="value"')
67
+ end
68
+ end
69
+
70
+ describe "manipulation" do
71
+ before do
72
+ element["test"] = "value"
73
+ end
74
+
75
+ let(:attribute) do
76
+ element.attributes.first
77
+ end
78
+
79
+ it "removes attribute" do
80
+ attribute.remove
81
+ expect(element["test"]).to be_nil
82
+ expect(element.attributes).to be_empty
83
+ end
84
+
85
+ it "handles special characters in values" do
86
+ attribute.value = "< & > ' \""
87
+ expect(attribute.to_s).to eq('test="< & > \' ""')
88
+ end
89
+ end
90
+
91
+ describe "#to_s" do
92
+ it "formats regular attribute" do
93
+ element["test"] = "value"
94
+ expect(element.attributes.first.to_s).to eq('test="value"')
95
+ end
96
+
97
+ it "formats namespaced attribute" do
98
+ element.add_namespace("ns", "http://example.org")
99
+ element["ns:test"] = "value"
100
+ expect(element.attributes.first.to_s).to eq('ns:test="value"')
101
+ end
102
+ end
103
+
104
+ describe "equality" do
105
+ it "compares attributes" do
106
+ element["test"] = "value"
107
+ attr1 = element.attributes.first
108
+
109
+ element2 = doc.create_element("other")
110
+ element2["test"] = "value"
111
+ attr2 = element2.attributes.first
112
+
113
+ element3 = doc.create_element("other")
114
+ element3["test"] = "different"
115
+ attr3 = element3.attributes.first
116
+
117
+ expect(attr1).to eq(attr2)
118
+ expect(attr1).not_to eq(attr3)
119
+ end
120
+
121
+ it "compares attributes with namespaces" do
122
+ element.add_namespace("ns", "http://example.org")
123
+ element["ns:test"] = "value"
124
+ attr1 = element.attributes.first
125
+
126
+ element2 = doc.create_element("other")
127
+ element2.add_namespace("ns", "http://example.org")
128
+ element2["ns:test"] = "value"
129
+ attr2 = element2.attributes.first
130
+
131
+ expect(attr1).to eq(attr2)
132
+ end
133
+ end
134
+
135
+ describe "edge cases" do
136
+ it "handles empty values" do
137
+ element["empty"] = ""
138
+ attribute = element.attributes.first
139
+ expect(attribute.value).to eq("")
140
+ expect(attribute.to_s).to eq('empty=""')
141
+ end
142
+
143
+ it "handles nil values" do
144
+ element["nil"] = nil
145
+ attribute = element.attributes.first
146
+ expect(attribute.value).to eq("")
147
+ expect(attribute.to_s).to eq('nil=""')
148
+ end
149
+
150
+ it "handles numeric values" do
151
+ element["num"] = 123
152
+ attribute = element.attributes.first
153
+ expect(attribute.value).to eq("123")
154
+ expect(attribute.to_s).to eq('num="123"')
155
+ end
156
+
157
+ it "handles boolean values" do
158
+ element["bool"] = true
159
+ attribute = element.attributes.first
160
+ expect(attribute.value).to eq("true")
161
+ expect(attribute.to_s).to eq('bool="true"')
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::Builder" do
4
+ let(:context) { Moxml.new }
5
+ let(:builder) { Moxml::Builder.new(context) }
6
+
7
+ describe "#document" do
8
+ it "creates a well-formed document" do
9
+ doc = builder.build do
10
+ declaration version: "1.0", encoding: "UTF-8"
11
+ element "root" do
12
+ element "child", id: "1" do
13
+ text "content"
14
+ end
15
+ end
16
+ end
17
+
18
+ xml = doc.to_xml
19
+ expect(xml).to include('<?xml version="1.0" encoding="UTF-8"?>')
20
+ expect(xml).to include("<root>")
21
+ expect(xml).to include('<child id="1">content</child>')
22
+ expect(xml).to include("</root>")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::Cdata" do
4
+ let(:context) { Moxml.new }
5
+ let(:doc) { context.create_document }
6
+ let(:cdata) { doc.create_cdata("<content>") }
7
+
8
+ it "identifies as CDATA node" do
9
+ expect(cdata).to be_cdata
10
+ end
11
+
12
+ describe "content manipulation" do
13
+ it "gets content" do
14
+ expect(cdata.content).to eq("<content>")
15
+ end
16
+
17
+ it "sets content" do
18
+ cdata.content = "new <content>"
19
+ expect(cdata.content).to eq("new <content>")
20
+ end
21
+
22
+ it "handles nil content" do
23
+ cdata.content = nil
24
+ expect(cdata.content).to eq("")
25
+ end
26
+ end
27
+
28
+ describe "serialization" do
29
+ it "wraps content in CDATA section" do
30
+ expect(cdata.to_xml).to eq("<![CDATA[<content>]]>")
31
+ end
32
+
33
+ it "escapes CDATA end marker" do
34
+ cdata.content = "content]]>more"
35
+ expect(cdata.to_xml).to eq("<![CDATA[content]]]]><![CDATA[>more]]>")
36
+ end
37
+
38
+ it "handles special characters" do
39
+ cdata.content = "< > & \" '"
40
+ expect(cdata.to_xml).to include("< > & \" '")
41
+ end
42
+
43
+ it "preserves whitespace" do
44
+ cdata.content = " spaced content\n\t"
45
+ expect(cdata.content).to eq(" spaced content\n\t")
46
+ end
47
+ end
48
+
49
+ describe "node operations" do
50
+ let(:element) { doc.create_element("test") }
51
+
52
+ it "adds to element" do
53
+ element.add_child(cdata)
54
+ expect(element.to_xml).to include("<![CDATA[<content>]]>")
55
+ end
56
+
57
+ it "removes from element" do
58
+ element.add_child(cdata)
59
+ cdata.remove
60
+ expect(element.children).to be_empty
61
+ end
62
+
63
+ it "replaces with another node" do
64
+ element.add_child(cdata)
65
+ text = doc.create_text("replacement")
66
+ cdata.replace(text)
67
+ expect(element.text).to eq("replacement")
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::Comment" do
4
+ let(:context) { Moxml.new }
5
+ let(:doc) { context.create_document }
6
+ let(:comment) { doc.create_comment("test comment") }
7
+
8
+ it "identifies as comment node" do
9
+ expect(comment).to be_comment
10
+ end
11
+
12
+ describe "content manipulation" do
13
+ it "gets content" do
14
+ expect(comment.content).to eq("test comment")
15
+ end
16
+
17
+ it "sets content" do
18
+ comment.content = "new comment"
19
+ expect(comment.content).to eq("new comment")
20
+ end
21
+
22
+ it "handles nil content" do
23
+ comment.content = nil
24
+ expect(comment.content).to eq("")
25
+ end
26
+ end
27
+
28
+ describe "serialization" do
29
+ it "wraps content in comment markers" do
30
+ expect(comment.to_xml).to eq("<!--test comment-->")
31
+ end
32
+
33
+ it "raises an error on double hyphens" do
34
+ expect { comment.content = "test -- comment" }
35
+ .to raise_error(Moxml::ValidationError, "XML comment cannot contain double hyphens (--)")
36
+ end
37
+
38
+ it "handles special characters" do
39
+ comment.content = "< > & \" '"
40
+ expect(comment.to_xml).to eq("<!--< > & \" '-->")
41
+ end
42
+ end
43
+
44
+ describe "node operations" do
45
+ let(:element) { doc.create_element("test") }
46
+
47
+ it "adds to element" do
48
+ element.add_child(comment)
49
+ expect(element.to_xml).to include("<!--test comment-->")
50
+ end
51
+
52
+ it "removes from element" do
53
+ element.add_child(comment)
54
+ comment.remove
55
+ expect(element.children).to be_empty
56
+ end
57
+
58
+ it "replaces with another node" do
59
+ element.add_child(comment)
60
+ text = doc.create_text("replacement")
61
+ comment.replace(text)
62
+ expect(element.text).to eq("replacement")
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::Context" do
4
+ let(:context) { Moxml::Context.new }
5
+
6
+ describe "#parse" do
7
+ it "returns a Moxml::Document" do
8
+ doc = context.parse("<root/>")
9
+ expect(doc).to be_a(Moxml::Document)
10
+ end
11
+
12
+ it "builds complete document model" do
13
+ doc = context.parse("<root><child>text</child></root>")
14
+ expect(doc.root).to be_a(Moxml::Element)
15
+ expect(doc.root.children.first).to be_a(Moxml::Element)
16
+ expect(doc.root.children.first.children.first).to be_a(Moxml::Text)
17
+ end
18
+
19
+ it "maintains document structure", skip: "Nokogiri doesn't consider the declaration as a child node" do
20
+ xml = <<~XML
21
+ <?xml version="1.0"?>
22
+ <!-- comment -->
23
+ <root>
24
+ <![CDATA[data]]>
25
+ <?pi target?>
26
+ </root>
27
+ XML
28
+ doc = context.parse(xml)
29
+
30
+ expect(doc.children[1]).to be_a(Moxml::Comment)
31
+ expect(doc.at_xpath("//root").children[0]).to be_a(Moxml::Cdata)
32
+ expect(doc.at_xpath("//root").children[1]).to be_a(Moxml::ProcessingInstruction)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::Declaration" do
4
+ let(:context) { Moxml.new }
5
+ let(:doc) { context.create_document }
6
+ let(:declaration) { doc.create_declaration("1.0", "UTF-8", "yes") }
7
+
8
+ it "identifies as declaration node" do
9
+ expect(declaration).to be_declaration
10
+ end
11
+
12
+ describe "version handling" do
13
+ it "gets version" do
14
+ expect(declaration.version).to eq("1.0")
15
+ end
16
+
17
+ it "sets version" do
18
+ declaration.version = "1.1"
19
+ expect(declaration.version).to eq("1.1")
20
+ end
21
+
22
+ it "validates version" do
23
+ expect { declaration.version = "2.0" }
24
+ .to raise_error(Moxml::ValidationError, "Invalid XML version: 2.0")
25
+ end
26
+ end
27
+
28
+ describe "encoding handling" do
29
+ it "gets encoding" do
30
+ expect(declaration.encoding).to eq("UTF-8")
31
+ end
32
+
33
+ it "sets encoding" do
34
+ declaration.encoding = "ISO-8859-1"
35
+ expect(declaration.encoding).to eq("ISO-8859-1")
36
+ end
37
+
38
+ it "normalizes encoding" do
39
+ declaration.encoding = "utf-8"
40
+ expect(declaration.encoding).to eq("utf-8")
41
+ end
42
+ end
43
+
44
+ describe "standalone handling" do
45
+ it "gets standalone" do
46
+ expect(declaration.standalone).to eq("yes")
47
+ end
48
+
49
+ it "sets standalone" do
50
+ declaration.standalone = "no"
51
+ expect(declaration.standalone).to eq("no")
52
+ end
53
+
54
+ it "validates standalone value" do
55
+ expect { declaration.standalone = "maybe" }
56
+ .to raise_error(Moxml::ValidationError, "Invalid standalone value: maybe")
57
+ end
58
+
59
+ it "allows nil standalone" do
60
+ declaration.standalone = nil
61
+ expect(declaration.standalone).to be_nil
62
+ end
63
+ end
64
+
65
+ describe "serialization" do
66
+ it "formats complete declaration" do
67
+ expect(declaration.to_xml).to eq('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
68
+ end
69
+
70
+ it "formats minimal declaration with empty encoding" do
71
+ decl = doc.create_declaration("1.0", nil)
72
+ expect(decl.to_xml).to eq('<?xml version="1.0"?>')
73
+ end
74
+
75
+ it "formats declaration with encoding only" do
76
+ decl = doc.create_declaration("1.0", "UTF-8")
77
+ expect(decl.to_xml).to eq('<?xml version="1.0" encoding="UTF-8"?>')
78
+ end
79
+ end
80
+
81
+ describe "node operations" do
82
+ it "adds to document" do
83
+ doc.add_child(declaration)
84
+ expect(doc.to_xml).to start_with("<?xml")
85
+ end
86
+
87
+ it "removes from document", skip: "The document contains a default declaration" do
88
+ doc.add_child(declaration)
89
+ declaration.remove
90
+ expect(doc.to_xml).not_to include("<?xml")
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::Doctype" do
4
+ let(:context) { Moxml.new }
5
+ let(:doc) { context.create_document }
6
+ let(:doctype) do
7
+ doc.create_doctype(
8
+ "html",
9
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
10
+ "http://www.w3.org/TR/html4/loose.dtd"
11
+ )
12
+ end
13
+
14
+ it "identifies as doctype node" do
15
+ expect(doctype).to be_doctype
16
+ end
17
+
18
+ describe "serialization" do
19
+ it "wraps content in doctype markers" do
20
+ expect(doctype.to_xml).to eq(
21
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::Document" do
4
+ let(:context) { Moxml.new }
5
+ let(:xml) do
6
+ <<~XML
7
+ <?xml version="1.0" encoding="UTF-8"?>
8
+ <!DOCTYPE html>
9
+ <root xmlns="http://example.org" xmlns:x="http://example.org/x">
10
+ <child id="1">Text</child>
11
+ <child id="2"/>
12
+ <x:special>
13
+ <![CDATA[Some <special> text]]>
14
+ <!-- A comment -->
15
+ <?pi target?>
16
+ </x:special>
17
+ </root>
18
+ XML
19
+ end
20
+
21
+ describe "#parse" do
22
+ let(:doc) { context.parse(xml) }
23
+
24
+ it "parses XML string" do
25
+ expect(doc).to be_a(Moxml::Document)
26
+ expect(doc.root.name).to eq("root")
27
+ end
28
+
29
+ it "preserves XML declaration" do
30
+ expect(doc.to_xml).to include('<?xml version="1.0" encoding="UTF-8"?>')
31
+ end
32
+
33
+ it "handles namespaces" do
34
+ expect(doc.root.namespaces.size).to eq(2)
35
+ end
36
+
37
+ it "preserves DOCTYPE" do
38
+ expect(doc.to_xml).to include("<!DOCTYPE html>")
39
+ end
40
+
41
+ it "raises error for invalid XML when strict" do
42
+ context.config.strict_parsing = true
43
+ expect { context.parse("<invalid>") }.to raise_error(Moxml::ParseError)
44
+ end
45
+ end
46
+
47
+ describe "node creation" do
48
+ let(:doc) { context.create_document }
49
+
50
+ it "creates element" do
51
+ element = doc.create_element("test")
52
+ expect(element).to be_a(Moxml::Element)
53
+ expect(element.name).to eq("test")
54
+ end
55
+
56
+ it "creates text" do
57
+ text = doc.create_text("content")
58
+ expect(text).to be_a(Moxml::Text)
59
+ expect(text.content).to eq("content")
60
+ end
61
+
62
+ it "creates CDATA" do
63
+ cdata = doc.create_cdata("<content>")
64
+ expect(cdata).to be_a(Moxml::Cdata)
65
+ expect(cdata.content).to eq("<content>")
66
+ end
67
+
68
+ it "creates comment" do
69
+ comment = doc.create_comment("comment")
70
+ expect(comment).to be_a(Moxml::Comment)
71
+ expect(comment.content).to eq("comment")
72
+ end
73
+
74
+ it "creates processing instruction" do
75
+ pi = doc.create_processing_instruction("target", "content")
76
+ expect(pi).to be_a(Moxml::ProcessingInstruction)
77
+ expect(pi.target).to eq("target")
78
+ expect(pi.content).to eq("content")
79
+ end
80
+
81
+ it "creates declaration" do
82
+ decl = doc.create_declaration("1.0", "UTF-8", "yes")
83
+ expect(decl).to be_a(Moxml::Declaration)
84
+ expect(decl.version).to eq("1.0")
85
+ expect(decl.encoding).to eq("UTF-8")
86
+ expect(decl.standalone).to eq("yes")
87
+ end
88
+ end
89
+
90
+ describe "document structure" do
91
+ let(:doc) { context.create_document }
92
+
93
+ it "builds complete document" do
94
+ doc.add_child(doc.create_declaration("1.0", "UTF-8"))
95
+ root = doc.create_element("root")
96
+ doc.add_child(root)
97
+
98
+ child = doc.create_element("child")
99
+ child.add_child(doc.create_text("text"))
100
+ root.add_child(child)
101
+
102
+ expect(doc.to_xml).to include(
103
+ '<?xml version="1.0" encoding="UTF-8"?>',
104
+ "<root>",
105
+ "<child>text</child>",
106
+ "</root>"
107
+ )
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "Moxml::DocumentBuilder" do
4
+ let(:context) { Moxml.new }
5
+ let(:builder) { Moxml::DocumentBuilder.new(context) }
6
+
7
+ describe "#build" do
8
+ it "builds a document model from native document" do
9
+ xml = "<root><child>text</child></root>"
10
+ doc = context.config.adapter.parse(xml)
11
+
12
+ expect(doc).to be_a(Moxml::Document)
13
+ expect(doc.root).to be_a(Moxml::Element)
14
+ expect(doc.root.name).to eq("root")
15
+ expect(doc.root.children.first).to be_a(Moxml::Element)
16
+ expect(doc.root.children.first.text).to eq("text")
17
+ end
18
+
19
+ it "handles complex documents" do
20
+ xml = <<~XML
21
+ <?xml version="1.0" encoding="UTF-8"?>
22
+ <root xmlns="http://example.org">
23
+ <!-- comment -->
24
+ <child id="1">
25
+ <![CDATA[cdata content]]>
26
+ </child>
27
+ <?pi target data?>
28
+ </root>
29
+ XML
30
+
31
+ doc = context.config.adapter.parse(xml)
32
+
33
+ expect(doc.root.namespaces.count).to eq(1)
34
+ expect(doc.root.namespaces.first.uri).to eq("http://example.org")
35
+ expect(doc.root.children[0]).to be_a(Moxml::Comment)
36
+ expect(doc.root.children[1]).to be_a(Moxml::Element)
37
+ expect(doc.root.children[1].name).to eq("child")
38
+ expect(doc.root.children[1]["id"]).to eq("1")
39
+ expect(doc.root.children[1].children.first).to be_a(Moxml::Cdata)
40
+ expect(doc.root.children[2]).to be_a(Moxml::ProcessingInstruction)
41
+ end
42
+ end
43
+ end