moxml 0.1.3 → 0.1.6

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop_todo.yml +48 -20
  4. data/Gemfile +3 -0
  5. data/LICENSE.md +33 -0
  6. data/README.adoc +95 -23
  7. data/lib/moxml/adapter/base.rb +20 -2
  8. data/lib/moxml/adapter/customized_ox/attribute.rb +29 -0
  9. data/lib/moxml/adapter/customized_ox/namespace.rb +34 -0
  10. data/lib/moxml/adapter/customized_ox/text.rb +12 -0
  11. data/lib/moxml/adapter/customized_rexml/formatter.rb +195 -0
  12. data/lib/moxml/adapter/nokogiri.rb +4 -2
  13. data/lib/moxml/adapter/oga.rb +25 -9
  14. data/lib/moxml/adapter/ox.rb +238 -92
  15. data/lib/moxml/adapter/rexml.rb +462 -0
  16. data/lib/moxml/adapter.rb +1 -1
  17. data/lib/moxml/attribute.rb +2 -2
  18. data/lib/moxml/cdata.rb +0 -4
  19. data/lib/moxml/comment.rb +0 -4
  20. data/lib/moxml/config.rb +1 -1
  21. data/lib/moxml/context.rb +2 -2
  22. data/lib/moxml/doctype.rb +1 -5
  23. data/lib/moxml/document.rb +1 -1
  24. data/lib/moxml/document_builder.rb +14 -18
  25. data/lib/moxml/element.rb +4 -3
  26. data/lib/moxml/namespace.rb +5 -1
  27. data/lib/moxml/node.rb +17 -2
  28. data/lib/moxml/node_set.rb +8 -1
  29. data/lib/moxml/processing_instruction.rb +0 -4
  30. data/lib/moxml/text.rb +0 -4
  31. data/lib/moxml/version.rb +1 -1
  32. data/lib/ox/node.rb +9 -0
  33. data/spec/fixtures/small.xml +1 -0
  34. data/spec/moxml/adapter/rexml_spec.rb +14 -0
  35. data/spec/moxml/all_with_adapters_spec.rb +2 -3
  36. data/spec/support/shared_examples/builder.rb +19 -2
  37. data/spec/support/shared_examples/cdata.rb +7 -5
  38. data/spec/support/shared_examples/declaration.rb +17 -4
  39. data/spec/support/shared_examples/doctype.rb +2 -1
  40. data/spec/support/shared_examples/document.rb +10 -0
  41. data/spec/support/shared_examples/edge_cases.rb +9 -3
  42. data/spec/support/shared_examples/element.rb +5 -1
  43. data/spec/support/shared_examples/examples/benchmark_spec.rb +51 -0
  44. data/spec/support/shared_examples/examples/memory.rb +30 -17
  45. data/spec/support/shared_examples/examples/readme_examples.rb +5 -0
  46. data/spec/support/shared_examples/examples/thread_safety.rb +2 -0
  47. data/spec/support/shared_examples/examples/xpath.rb +34 -3
  48. data/spec/support/shared_examples/integration.rb +6 -2
  49. data/spec/support/shared_examples/namespace.rb +16 -0
  50. data/spec/support/shared_examples/node.rb +4 -0
  51. data/spec/support/shared_examples/node_set.rb +20 -0
  52. data/spec/support/shared_examples/processing_instruction.rb +1 -1
  53. data/spec/support/shared_examples/text.rb +2 -1
  54. data/spec/support/shared_examples/xml_adapter.rb +169 -7
  55. metadata +13 -3
@@ -25,31 +25,47 @@ RSpec.shared_examples "Moxml::NodeSet" do
25
25
 
26
26
  describe "enumeration" do
27
27
  it "iterates over nodes" do
28
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
29
+
28
30
  texts = []
29
31
  nodes.each { |node| texts << node.text }
30
32
  expect(texts).to eq(%w[First Second Third])
31
33
  end
32
34
 
33
35
  it "maps nodes" do
36
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
37
+
34
38
  texts = nodes.map(&:text)
35
39
  expect(texts).to eq(%w[First Second Third])
36
40
  end
37
41
 
38
42
  it "selects nodes" do
43
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
44
+
39
45
  selected = nodes.select { |node| node.text.include?("i") }
40
46
  expect(selected.size).to eq(2)
41
47
  expect(selected.map(&:text)).to eq(%w[First Third])
42
48
  end
49
+
50
+ it "compares nodes" do
51
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
52
+
53
+ expect(doc.xpath("//child")).to eq(doc.root.children)
54
+ end
43
55
  end
44
56
 
45
57
  describe "access methods" do
46
58
  it "accesses by index" do
59
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
60
+
47
61
  expect(nodes[0].text).to eq("First")
48
62
  expect(nodes[1].text).to eq("Second")
49
63
  expect(nodes[-1].text).to eq("Third")
50
64
  end
51
65
 
52
66
  it "accesses by range" do
67
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
68
+
53
69
  subset = nodes[0..1]
54
70
  expect(subset).to be_a(described_class)
55
71
  expect(subset.size).to eq(2)
@@ -57,6 +73,8 @@ RSpec.shared_examples "Moxml::NodeSet" do
57
73
  end
58
74
 
59
75
  it "provides first and last" do
76
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
77
+
60
78
  expect(nodes.first.text).to eq("First")
61
79
  expect(nodes.last.text).to eq("Third")
62
80
  end
@@ -77,6 +95,8 @@ RSpec.shared_examples "Moxml::NodeSet" do
77
95
 
78
96
  describe "concatenation" do
79
97
  it "combines node sets" do
98
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
99
+
80
100
  other_doc = context.parse("<root><item>Fourth</item></root>")
81
101
  other_nodes = other_doc.xpath("//item")
82
102
  combined = nodes + other_nodes
@@ -43,7 +43,7 @@ RSpec.shared_examples "Moxml::ProcessingInstruction" do
43
43
 
44
44
  describe "serialization" do
45
45
  it "formats processing instruction" do
46
- expect(pi.to_xml).to eq('<?xml-stylesheet href="style.xsl" type="text/xsl"?>')
46
+ expect(pi.to_xml.strip).to end_with('<?xml-stylesheet href="style.xsl" type="text/xsl"?>')
47
47
  end
48
48
 
49
49
  it "handles special characters" do
@@ -33,7 +33,8 @@ RSpec.shared_examples "Moxml::Text" do
33
33
  describe "special characters" do
34
34
  it "encodes basic XML entities" do
35
35
  text.content = "< > & \" '"
36
- expect(text.to_xml).to eq("&lt; &gt; &amp; \" '")
36
+ doc.add_child(text)
37
+ expect(doc.to_xml.strip).to end_with("&lt; &gt; &amp; \" '")
37
38
  end
38
39
 
39
40
  it "preserves whitespace" do
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This test works directly with native nodes which looks quite strange
4
+ # A better way is to run it through Moxml wrappers
3
5
  RSpec.shared_examples "xml adapter" do
4
6
  let(:xml) do
5
7
  <<~XML
@@ -80,7 +82,7 @@ RSpec.shared_examples "xml adapter" do
80
82
  expect(children.length).to eq(3)
81
83
  end
82
84
 
83
- it "gets siblings", skip: "Oga includes text nodes into siblings" do
85
+ it "gets siblings" do
84
86
  children = described_class.children(root)
85
87
  first = children[0]
86
88
  second = children[1]
@@ -97,13 +99,16 @@ RSpec.shared_examples "xml adapter" do
97
99
 
98
100
  it "adds text child" do
99
101
  described_class.add_child(root, "text")
100
- expect(described_class.children(root).last.text).to eq("text")
102
+ # a workaround for Rexml until we convert tests to work with Moxml wrappers
103
+ last_child = described_class.children(root).last
104
+ last_text = last_child.respond_to?(:text) ? last_child.text : last_child.to_s
105
+ expect(last_text).to eq("text")
101
106
  end
102
107
  end
103
108
 
104
109
  describe "attributes" do
105
- let(:doc) { described_class.parse(xml) }
106
- let(:element) { described_class.children(described_class.root(doc.native)).first }
110
+ let(:doc) { described_class.parse(xml).native }
111
+ let(:element) { described_class.children(described_class.root(doc)).first }
107
112
 
108
113
  it "gets attributes" do
109
114
  attrs = described_class.attributes(element)
@@ -123,7 +128,9 @@ RSpec.shared_examples "xml adapter" do
123
128
 
124
129
  it "handles special characters in attributes" do
125
130
  described_class.set_attribute(element, "special", '< > & " \'')
126
- value = described_class.get_attribute(element, "special")&.to_xml
131
+ # a workaround for Rexml until we convert tests to work with Moxml wrappers
132
+ attr = described_class.get_attribute(element, "special")
133
+ value = attr.respond_to?(:to_xml) ? attr&.to_xml : attr.to_s
127
134
  expect(value).to match(/&lt; &gt; &amp; (&quot;|") ('|&apos;)/)
128
135
  end
129
136
  end
@@ -156,7 +163,10 @@ RSpec.shared_examples "xml adapter" do
156
163
  expect(result).to include("</root>")
157
164
  end
158
165
 
159
- it "respects indentation settings", skip: "Indent cannot be negative, and zero indent doesn't remove newlines" do
166
+ it "respects indentation settings" do
167
+ pending("Oga does not support indentation settings") if described_class.name.include?("Oga")
168
+ pending("Postponed for Rexml till better times") if described_class.name.include?("Rexml")
169
+
160
170
  unindented = described_class.serialize(doc, indent: 0)
161
171
  indented = described_class.serialize(doc, indent: 2)
162
172
 
@@ -176,7 +186,7 @@ RSpec.shared_examples "xml adapter" do
176
186
  end
177
187
 
178
188
  describe "xpath" do
179
- let(:doc) { described_class.parse(xml) }
189
+ let(:doc) { described_class.parse(xml).native }
180
190
 
181
191
  it "finds nodes by xpath" do
182
192
  nodes = described_class.xpath(doc, "//xmlns:child")
@@ -188,4 +198,156 @@ RSpec.shared_examples "xml adapter" do
188
198
  expect(described_class.get_attribute_value(node, "id")).to eq("1")
189
199
  end
190
200
  end
201
+
202
+ describe "namespace handling" do
203
+ context "case 1" do
204
+ let(:xml) do
205
+ <<~XML
206
+ <?xml version="1.0" encoding="UTF-8"?>
207
+ <svg xmlns="http://www.w3.org/2000/svg"
208
+ xmlns:xlink="http://www.w3.org/1999/xlink"
209
+ width="100" height="100">
210
+ <defs>
211
+ <circle id="myCircle" cx="50" cy="50" r="40"/>
212
+ </defs>
213
+ <use xlink:href="#myCircle" fill="red"/>
214
+ <text x="50" y="50" text-anchor="middle">SVG</text>
215
+ </svg>
216
+ XML
217
+ end
218
+
219
+ it "preserves and correctly handles multiple namespaces" do
220
+ pending("Rexml does not respect ZPath namespaces") if described_class.name.include?("Rexml")
221
+ # Parse original XML
222
+ doc = described_class.parse(xml).native
223
+
224
+ # Test namespace preservation in serialization
225
+ result = described_class.serialize(doc)
226
+ expect(result).to include('xmlns="http://www.w3.org/2000/svg"')
227
+ expect(result).to include('xmlns:xlink="http://www.w3.org/1999/xlink"')
228
+
229
+ # Test xpath with namespaces
230
+ namespaces = {
231
+ "svg" => "http://www.w3.org/2000/svg",
232
+ "xlink" => "http://www.w3.org/1999/xlink"
233
+ }
234
+
235
+ # Find use element and verify xlink:href attribute
236
+ use_elem = described_class.at_xpath(doc, "//svg:use", namespaces)
237
+ expect(use_elem).not_to be_nil
238
+ expect(described_class.get_attribute_value(use_elem, "xlink:href")).to eq("#myCircle")
239
+
240
+ # Verify circle element exists in defs
241
+ circle = described_class.at_xpath(doc, "//svg:defs/svg:circle", namespaces)
242
+ expect(circle).not_to be_nil
243
+ expect(described_class.get_attribute_value(circle, "id")).to eq("myCircle")
244
+
245
+ # Test default SVG namespace
246
+ text = described_class.at_xpath(doc, "//svg:text", namespaces)
247
+ expect(described_class.text_content(text)).to eq("SVG")
248
+ end
249
+ end
250
+
251
+ context "case 2" do
252
+ let(:xml) do
253
+ <<~XML
254
+ <?xml version="1.0" encoding="UTF-8"?>
255
+ <rss version="2.0"#{" "}
256
+ xmlns:atom="http://www.w3.org/2005/Atom"
257
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
258
+ xmlns:content="http://purl.org/rss/1.0/modules/content/">
259
+ <channel>
260
+ <title>Example RSS Feed</title>
261
+ <atom:link href="http://example.com/feed" rel="self"/>
262
+ <item>
263
+ <title>Example Post</title>
264
+ <dc:creator>John Doe</dc:creator>
265
+ <content:encoded><![CDATA[<p>Post content</p>]]></content:encoded>
266
+ </item>
267
+ </channel>
268
+ </rss>
269
+ XML
270
+ end
271
+
272
+ it "preserves and correctly handles multiple namespaces" do
273
+ # Parse original XML
274
+ doc = described_class.parse(xml).native
275
+
276
+ # Test namespace preservation in serialization
277
+ result = described_class.serialize(doc)
278
+ expect(result).to include('xmlns:atom="http://www.w3.org/2005/Atom"')
279
+ expect(result).to include('xmlns:dc="http://purl.org/dc/elements/1.1/"')
280
+ expect(result).to include('xmlns:content="http://purl.org/rss/1.0/modules/content/"')
281
+
282
+ # Test xpath with namespaces
283
+ namespaces = {
284
+ "atom" => "http://www.w3.org/2005/Atom",
285
+ "dc" => "http://purl.org/dc/elements/1.1/",
286
+ "content" => "http://purl.org/rss/1.0/modules/content/"
287
+ }
288
+
289
+ # Find creator using namespaced xpath
290
+ creator = described_class.at_xpath(doc, "//dc:creator", namespaces)
291
+ expect(described_class.text_content(creator)).to eq("John Doe")
292
+
293
+ # Verify atom:link exists
294
+ link = described_class.at_xpath(doc, "//atom:link", namespaces)
295
+ expect(link).not_to be_nil
296
+ expect(described_class.get_attribute_value(link, "href")).to eq("http://example.com/feed")
297
+
298
+ # Verify CDATA in content:encoded
299
+ content = described_class.at_xpath(doc, "//content:encoded", namespaces)
300
+ expect(described_class.text_content(content)).to eq("<p>Post content</p>")
301
+ end
302
+ end
303
+
304
+ context "case 3" do
305
+ let(:xml) do
306
+ <<~XML
307
+ <?xml version="1.0" encoding="UTF-8"?>
308
+ <soap:Envelope#{" "}
309
+ xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
310
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
311
+ xmlns:ns="urn:example:namespace">
312
+ <soap:Header>
313
+ <ns:SessionId>12345</ns:SessionId>
314
+ </soap:Header>
315
+ <soap:Body>
316
+ <ns:GetUserRequest>
317
+ <ns:UserId xsi:type="xsi:string">user123</ns:UserId>
318
+ </ns:GetUserRequest>
319
+ </soap:Body>
320
+ </soap:Envelope>
321
+ XML
322
+ end
323
+
324
+ it "preserves and correctly handles multiple namespaces" do
325
+ # Parse original XML
326
+ doc = described_class.parse(xml).native
327
+
328
+ # Test namespace preservation in serialization
329
+ result = described_class.serialize(doc)
330
+ expect(result).to include('xmlns:soap="http://www.w3.org/2003/05/soap-envelope"')
331
+ expect(result).to include('xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
332
+ expect(result).to include('xmlns:ns="urn:example:namespace"')
333
+
334
+ # Test xpath with namespaces
335
+ namespaces = {
336
+ "soap" => "http://www.w3.org/2003/05/soap-envelope",
337
+ "ns" => "urn:example:namespace"
338
+ }
339
+
340
+ # Find user ID using namespaced xpath
341
+ user_id = described_class.at_xpath(doc, "//ns:UserId", namespaces)
342
+ expect(described_class.text_content(user_id)).to eq("user123")
343
+
344
+ # Verify soap:Body exists
345
+ body = described_class.at_xpath(doc, "//soap:Body", namespaces)
346
+ expect(body).not_to be_nil
347
+
348
+ # Verify attribute with namespace
349
+ expect(described_class.get_attribute_value(user_id, "xsi:type")).to eq("xsi:string")
350
+ end
351
+ end
352
+ end
191
353
  end
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.3
4
+ version: 0.1.6
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-02-26 00:00:00.000000000 Z
11
+ date: 2025-07-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'Moxml is a unified XML manipulation library that provides a common API.
14
14
 
@@ -27,6 +27,7 @@ files:
27
27
  - ".rubocop_todo.yml"
28
28
  - ".ruby-version"
29
29
  - Gemfile
30
+ - LICENSE.md
30
31
  - README.adoc
31
32
  - Rakefile
32
33
  - bin/console
@@ -36,9 +37,14 @@ files:
36
37
  - lib/moxml/adapter/base.rb
37
38
  - lib/moxml/adapter/customized_oga/xml_declaration.rb
38
39
  - lib/moxml/adapter/customized_oga/xml_generator.rb
40
+ - lib/moxml/adapter/customized_ox/attribute.rb
41
+ - lib/moxml/adapter/customized_ox/namespace.rb
42
+ - lib/moxml/adapter/customized_ox/text.rb
43
+ - lib/moxml/adapter/customized_rexml/formatter.rb
39
44
  - lib/moxml/adapter/nokogiri.rb
40
45
  - lib/moxml/adapter/oga.rb
41
46
  - lib/moxml/adapter/ox.rb
47
+ - lib/moxml/adapter/rexml.rb
42
48
  - lib/moxml/attribute.rb
43
49
  - lib/moxml/builder.rb
44
50
  - lib/moxml/cdata.rb
@@ -59,11 +65,14 @@ files:
59
65
  - lib/moxml/version.rb
60
66
  - lib/moxml/xml_utils.rb
61
67
  - lib/moxml/xml_utils/encoder.rb
68
+ - lib/ox/node.rb
62
69
  - moxml.gemspec
63
70
  - sig/moxml.rbs
71
+ - spec/fixtures/small.xml
64
72
  - spec/moxml/adapter/nokogiri_spec.rb
65
73
  - spec/moxml/adapter/oga_spec.rb
66
74
  - spec/moxml/adapter/ox_spec.rb
75
+ - spec/moxml/adapter/rexml_spec.rb
67
76
  - spec/moxml/all_with_adapters_spec.rb
68
77
  - spec/moxml/config_spec.rb
69
78
  - spec/moxml/error_spec.rb
@@ -83,6 +92,7 @@ files:
83
92
  - spec/support/shared_examples/element.rb
84
93
  - spec/support/shared_examples/examples/attribute.rb
85
94
  - spec/support/shared_examples/examples/basic_usage.rb
95
+ - spec/support/shared_examples/examples/benchmark_spec.rb
86
96
  - spec/support/shared_examples/examples/memory.rb
87
97
  - spec/support/shared_examples/examples/namespace.rb
88
98
  - spec/support/shared_examples/examples/readme_examples.rb
@@ -116,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
126
  - !ruby/object:Gem::Version
117
127
  version: '0'
118
128
  requirements: []
119
- rubygems_version: 3.3.27
129
+ rubygems_version: 3.5.22
120
130
  signing_key:
121
131
  specification_version: 4
122
132
  summary: Unified XML manipulation library