moxml 0.1.8 → 0.1.10

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +22 -39
  3. data/README.adoc +51 -20
  4. data/docs/_config.yml +3 -3
  5. data/docs/_guides/index.adoc +15 -7
  6. data/docs/_guides/modifying-xml.adoc +0 -1
  7. data/docs/_guides/node-api-consistency.adoc +572 -0
  8. data/docs/_guides/parsing-xml.adoc +0 -1
  9. data/docs/_guides/xml-declaration.adoc +450 -0
  10. data/docs/_pages/adapter-compatibility.adoc +1 -1
  11. data/docs/_pages/adapters/headed-ox.adoc +9 -9
  12. data/docs/_pages/adapters/index.adoc +0 -1
  13. data/docs/_pages/adapters/libxml.adoc +1 -2
  14. data/docs/_pages/adapters/nokogiri.adoc +1 -2
  15. data/docs/_pages/adapters/oga.adoc +1 -2
  16. data/docs/_pages/adapters/ox.adoc +2 -1
  17. data/docs/_pages/adapters/rexml.adoc +2 -3
  18. data/docs/_pages/best-practices.adoc +0 -1
  19. data/docs/_pages/compatibility.adoc +0 -1
  20. data/docs/_pages/configuration.adoc +0 -1
  21. data/docs/_pages/error-handling.adoc +0 -1
  22. data/docs/_pages/headed-ox-limitations.adoc +16 -0
  23. data/docs/_pages/installation.adoc +0 -1
  24. data/docs/_pages/node-api-reference.adoc +93 -4
  25. data/docs/_pages/performance.adoc +0 -1
  26. data/docs/_pages/quick-start.adoc +0 -1
  27. data/docs/_pages/thread-safety.adoc +0 -1
  28. data/docs/_references/document-api.adoc +0 -1
  29. data/docs/_tutorials/basic-usage.adoc +0 -1
  30. data/docs/_tutorials/builder-pattern.adoc +0 -1
  31. data/docs/_tutorials/namespace-handling.adoc +0 -1
  32. data/docs/_tutorials/xpath-queries.adoc +0 -1
  33. data/lib/moxml/adapter/customized_rexml/formatter.rb +2 -2
  34. data/lib/moxml/adapter/libxml.rb +34 -4
  35. data/lib/moxml/adapter/nokogiri.rb +50 -2
  36. data/lib/moxml/adapter/oga.rb +80 -3
  37. data/lib/moxml/adapter/ox.rb +70 -7
  38. data/lib/moxml/adapter/rexml.rb +45 -10
  39. data/lib/moxml/attribute.rb +6 -0
  40. data/lib/moxml/context.rb +18 -1
  41. data/lib/moxml/declaration.rb +9 -0
  42. data/lib/moxml/doctype.rb +33 -0
  43. data/lib/moxml/document.rb +14 -0
  44. data/lib/moxml/document_builder.rb +7 -0
  45. data/lib/moxml/element.rb +6 -0
  46. data/lib/moxml/error.rb +5 -5
  47. data/lib/moxml/node.rb +73 -1
  48. data/lib/moxml/processing_instruction.rb +6 -0
  49. data/lib/moxml/version.rb +1 -1
  50. data/lib/moxml/xpath/compiler.rb +2 -0
  51. data/lib/moxml/xpath/errors.rb +1 -1
  52. data/spec/integration/shared_examples/node_wrappers/declaration_behavior.rb +0 -3
  53. data/spec/moxml/declaration_preservation_spec.rb +217 -0
  54. data/spec/moxml/doctype_spec.rb +19 -3
  55. data/spec/performance/memory_usage_spec.rb +3 -2
  56. metadata +5 -3
  57. data/.ruby-version +0 -1
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "XML Declaration Preservation" do
6
+ # Test with all available adapters
7
+ ADAPTERS = %i[nokogiri oga rexml ox libxml headed_ox].freeze
8
+
9
+ ADAPTERS.each do |adapter_name|
10
+ context "with #{adapter_name} adapter" do
11
+ let(:context) { Moxml.new(adapter_name) }
12
+
13
+ describe "automatic preservation" do
14
+ context "when input has no XML declaration" do
15
+ let(:xml_without_decl) { '<svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>' }
16
+
17
+ it "does not add XML declaration to output" do
18
+ doc = context.parse(xml_without_decl)
19
+ output = doc.to_xml
20
+
21
+ expect(output).not_to include("<?xml")
22
+ expect(output).to include("<svg")
23
+ end
24
+
25
+ it "sets has_xml_declaration to false" do
26
+ doc = context.parse(xml_without_decl)
27
+ expect(doc.has_xml_declaration).to be false
28
+ end
29
+ end
30
+
31
+ context "when input has XML declaration" do
32
+ let(:xml_with_decl) do
33
+ '<?xml version="1.0" encoding="UTF-8"?><root><child/></root>'
34
+ end
35
+
36
+ it "preserves XML declaration in output" do
37
+ doc = context.parse(xml_with_decl)
38
+ output = doc.to_xml
39
+
40
+ expect(output).to include("<?xml")
41
+ expect(output).to include('version="1.0"')
42
+ end
43
+
44
+ it "sets has_xml_declaration to true" do
45
+ doc = context.parse(xml_with_decl)
46
+ expect(doc.has_xml_declaration).to be true
47
+ end
48
+ end
49
+
50
+ context "when input has declaration with standalone attribute" do
51
+ let(:xml_with_standalone) do
52
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><root/>'
53
+ end
54
+
55
+ it "preserves the declaration" do
56
+ doc = context.parse(xml_with_standalone)
57
+ output = doc.to_xml
58
+
59
+ expect(output).to include("<?xml")
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "explicit override" do
65
+ let(:xml_without_decl) { "<root><child/></root>" }
66
+ let(:xml_with_decl) { '<?xml version="1.0"?><root><child/></root>' }
67
+
68
+ context "when forcing declaration on document without one" do
69
+ it "adds declaration when declaration: true" do
70
+ doc = context.parse(xml_without_decl)
71
+ output = doc.to_xml(declaration: true)
72
+
73
+ expect(output).to include("<?xml")
74
+ end
75
+ end
76
+
77
+ context "when removing declaration from document with one" do
78
+ it "removes declaration when declaration: false" do
79
+ doc = context.parse(xml_with_decl)
80
+ output = doc.to_xml(declaration: false)
81
+
82
+ expect(output).not_to include("<?xml")
83
+ expect(output).to include("<root")
84
+ end
85
+ end
86
+
87
+ context "when explicitly preserving declaration" do
88
+ it "keeps declaration when declaration: true" do
89
+ doc = context.parse(xml_with_decl)
90
+ output = doc.to_xml(declaration: true)
91
+
92
+ expect(output).to include("<?xml")
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "round-trip fidelity" do
98
+ context "for document without declaration" do
99
+ let(:original) { "<root><item id=\"1\"/></root>" }
100
+
101
+ it "maintains absence of declaration through parse and serialize" do
102
+ doc = context.parse(original)
103
+ output = doc.to_xml
104
+
105
+ expect(output).not_to include("<?xml")
106
+
107
+ # Parse again and verify
108
+ doc2 = context.parse(output)
109
+ expect(doc2.has_xml_declaration).to be false
110
+ end
111
+ end
112
+
113
+ context "for document with declaration" do
114
+ let(:original) do
115
+ '<?xml version="1.0" encoding="UTF-8"?><root><item id="1"/></root>'
116
+ end
117
+
118
+ it "maintains presence of declaration through parse and serialize" do
119
+ doc = context.parse(original)
120
+ output = doc.to_xml
121
+
122
+ expect(output).to include("<?xml")
123
+
124
+ # Parse again and verify
125
+ doc2 = context.parse(output)
126
+ expect(doc2.has_xml_declaration).to be true
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "edge cases" do
132
+ context "with empty document" do
133
+ it "does not add declaration to empty document" do
134
+ doc = context.create_document
135
+
136
+ # Empty documents should not have declaration by default
137
+ expect(doc.has_xml_declaration).to be false
138
+ end
139
+ end
140
+
141
+ context "with built document" do
142
+ it "does not add declaration to programmatically built document" do
143
+ doc = context.create_document
144
+ root = doc.create_element("root")
145
+ doc.root = root
146
+
147
+ output = doc.to_xml
148
+
149
+ expect(output).not_to include("<?xml")
150
+ expect(doc.has_xml_declaration).to be false
151
+ end
152
+
153
+ it "can explicitly add declaration to built document" do
154
+ doc = context.create_document
155
+ root = doc.create_element("root")
156
+ doc.root = root
157
+
158
+ output = doc.to_xml(declaration: true)
159
+
160
+ expect(output).to include("<?xml")
161
+ end
162
+ end
163
+ end
164
+
165
+ describe "non-document nodes" do
166
+ let(:xml) { '<?xml version="1.0"?><root><child>text</child></root>' }
167
+
168
+ it "does not add declaration when serializing element nodes" do
169
+ doc = context.parse(xml)
170
+ root = doc.root
171
+ output = root.to_xml
172
+
173
+ expect(output).not_to include("<?xml")
174
+ expect(output).to include("<root>")
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ describe "integration with svg_conform use case" do
181
+ let(:context) { Moxml.new }
182
+
183
+ context "remediating SVG without declaration" do
184
+ let(:svg_input) { '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect x="10" y="10" width="80" height="80"/></svg>' }
185
+
186
+ it "does not add declaration to remediated output" do
187
+ doc = context.parse(svg_input)
188
+
189
+ # Simulate remediation: add viewport
190
+ root = doc.root
191
+ root["viewBox"] = "0 0 100 100"
192
+
193
+ output = doc.to_xml
194
+
195
+ expect(output).not_to include("<?xml")
196
+ expect(output).to include('viewBox="0 0 100 100"')
197
+ end
198
+ end
199
+
200
+ context "remediating SVG with declaration" do
201
+ let(:svg_input) { '<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>' }
202
+
203
+ it "preserves declaration in remediated output" do
204
+ doc = context.parse(svg_input)
205
+
206
+ # Simulate remediation
207
+ root = doc.root
208
+ root["viewBox"] = "0 0 100 100"
209
+
210
+ output = doc.to_xml
211
+
212
+ expect(output).to include("<?xml")
213
+ expect(output).to include('viewBox="0 0 100 100"')
214
+ end
215
+ end
216
+ end
217
+ end
@@ -8,7 +8,6 @@ RSpec.describe Moxml::Doctype do
8
8
 
9
9
  describe "#name" do
10
10
  it "returns doctype name" do
11
- skip "Doctype accessor methods not yet implemented in all adapters"
12
11
  doctype = doc.create_doctype("root", nil, "test.dtd")
13
12
  expect(doctype.name).to eq("root")
14
13
  end
@@ -16,15 +15,32 @@ RSpec.describe Moxml::Doctype do
16
15
 
17
16
  describe "#system_id" do
18
17
  it "returns system identifier" do
19
- skip "Doctype accessor methods not yet implemented in all adapters"
20
18
  doctype = doc.create_doctype("root", nil, "test.dtd")
21
19
  expect(doctype.system_id).to eq("test.dtd")
22
20
  end
23
21
  end
24
22
 
23
+ describe "#external_id" do
24
+ it "returns external identifier when present" do
25
+ doctype = doc.create_doctype("html", "-//W3C//DTD HTML 4.01//EN", "http://www.w3.org/TR/html4/strict.dtd")
26
+ expect(doctype.external_id).to eq("-//W3C//DTD HTML 4.01//EN")
27
+ end
28
+
29
+ it "returns nil when not present" do
30
+ doctype = doc.create_doctype("root", nil, "test.dtd")
31
+ expect(doctype.external_id).to be_nil
32
+ end
33
+ end
34
+
35
+ describe "#identifier" do
36
+ it "returns the doctype name" do
37
+ doctype = doc.create_doctype("html", nil, nil)
38
+ expect(doctype.identifier).to eq("html")
39
+ end
40
+ end
41
+
25
42
  describe "creation" do
26
43
  it "creates a doctype" do
27
- skip "Doctype accessor methods not yet implemented in all adapters"
28
44
  doctype = doc.create_doctype("html", nil, nil)
29
45
  expect(doctype).to be_a(described_class)
30
46
  expect(doctype.name).to eq("html")
@@ -51,9 +51,10 @@ RSpec.shared_examples "Memory Usage Examples" do
51
51
  end
52
52
 
53
53
  it "handles streaming processing" do
54
- if %i[ox headed_ox].include?(context.config.adapter_name)
55
- pending "Ox/HeadedOx have load_file method but not stream parsing"
54
+ if context.config.adapter_name == :headed_ox
55
+ pending "HeadedOx double-wrapping issue with file-based parsing"
56
56
  end
57
+
57
58
  # Process file
58
59
  doc = nil
59
60
  File.open("spec/fixtures/small.xml") do |f|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-11-24 00:00:00.000000000 Z
11
+ date: 2025-11-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Moxml is a unified XML manipulation library that provides a common API
@@ -29,7 +29,6 @@ files:
29
29
  - ".rspec"
30
30
  - ".rubocop.yml"
31
31
  - ".rubocop_todo.yml"
32
- - ".ruby-version"
33
32
  - Gemfile
34
33
  - LICENSE.md
35
34
  - README.adoc
@@ -44,9 +43,11 @@ files:
44
43
  - docs/_guides/development-testing.adoc
45
44
  - docs/_guides/index.adoc
46
45
  - docs/_guides/modifying-xml.adoc
46
+ - docs/_guides/node-api-consistency.adoc
47
47
  - docs/_guides/parsing-xml.adoc
48
48
  - docs/_guides/sax-parsing.adoc
49
49
  - docs/_guides/working-with-documents.adoc
50
+ - docs/_guides/xml-declaration.adoc
50
51
  - docs/_pages/adapter-compatibility.adoc
51
52
  - docs/_pages/adapters/headed-ox.adoc
52
53
  - docs/_pages/adapters/index.adoc
@@ -203,6 +204,7 @@ files:
203
204
  - spec/moxml/comment_spec.rb
204
205
  - spec/moxml/config_spec.rb
205
206
  - spec/moxml/context_spec.rb
207
+ - spec/moxml/declaration_preservation_spec.rb
206
208
  - spec/moxml/declaration_spec.rb
207
209
  - spec/moxml/doctype_spec.rb
208
210
  - spec/moxml/document_builder_spec.rb
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 3.0.7@moxml