moxml 0.1.0 → 0.1.1

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 +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