moxml 0.1.4 → 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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop_todo.yml +2 -2
  4. data/Gemfile +2 -0
  5. data/README.adoc +71 -21
  6. data/lib/moxml/adapter/base.rb +8 -2
  7. data/lib/moxml/adapter/customized_ox/attribute.rb +29 -0
  8. data/lib/moxml/adapter/customized_ox/namespace.rb +34 -0
  9. data/lib/moxml/adapter/customized_ox/text.rb +12 -0
  10. data/lib/moxml/adapter/customized_rexml/formatter.rb +3 -3
  11. data/lib/moxml/adapter/nokogiri.rb +3 -1
  12. data/lib/moxml/adapter/oga.rb +4 -2
  13. data/lib/moxml/adapter/ox.rb +238 -92
  14. data/lib/moxml/adapter/rexml.rb +10 -6
  15. data/lib/moxml/adapter.rb +1 -1
  16. data/lib/moxml/cdata.rb +0 -4
  17. data/lib/moxml/comment.rb +0 -4
  18. data/lib/moxml/context.rb +2 -2
  19. data/lib/moxml/doctype.rb +1 -5
  20. data/lib/moxml/document.rb +1 -1
  21. data/lib/moxml/document_builder.rb +1 -1
  22. data/lib/moxml/element.rb +2 -1
  23. data/lib/moxml/namespace.rb +4 -0
  24. data/lib/moxml/node.rb +17 -2
  25. data/lib/moxml/node_set.rb +8 -1
  26. data/lib/moxml/processing_instruction.rb +0 -4
  27. data/lib/moxml/text.rb +0 -4
  28. data/lib/moxml/version.rb +1 -1
  29. data/lib/ox/node.rb +9 -0
  30. data/spec/fixtures/small.xml +1 -0
  31. data/spec/moxml/all_with_adapters_spec.rb +2 -1
  32. data/spec/support/shared_examples/builder.rb +19 -2
  33. data/spec/support/shared_examples/cdata.rb +7 -5
  34. data/spec/support/shared_examples/declaration.rb +16 -4
  35. data/spec/support/shared_examples/doctype.rb +2 -1
  36. data/spec/support/shared_examples/document.rb +10 -0
  37. data/spec/support/shared_examples/edge_cases.rb +6 -0
  38. data/spec/support/shared_examples/element.rb +4 -0
  39. data/spec/support/shared_examples/examples/benchmark_spec.rb +51 -0
  40. data/spec/support/shared_examples/examples/memory.rb +30 -17
  41. data/spec/support/shared_examples/examples/readme_examples.rb +5 -0
  42. data/spec/support/shared_examples/examples/thread_safety.rb +2 -0
  43. data/spec/support/shared_examples/examples/xpath.rb +34 -3
  44. data/spec/support/shared_examples/integration.rb +4 -0
  45. data/spec/support/shared_examples/namespace.rb +16 -0
  46. data/spec/support/shared_examples/node.rb +4 -0
  47. data/spec/support/shared_examples/node_set.rb +20 -0
  48. data/spec/support/shared_examples/processing_instruction.rb +1 -1
  49. data/spec/support/shared_examples/text.rb +2 -1
  50. metadata +8 -2
@@ -42,7 +42,6 @@ module Moxml
42
42
  def size
43
43
  nodes.size
44
44
  end
45
-
46
45
  alias length size
47
46
 
48
47
  def to_a
@@ -53,6 +52,14 @@ module Moxml
53
52
  self.class.new(nodes + other.nodes, context)
54
53
  end
55
54
 
55
+ def ==(other)
56
+ self.class == other.class &&
57
+ length == other.length &&
58
+ nodes.each_with_index.all? do |node, index|
59
+ Node.wrap(node, context) == other[index]
60
+ end
61
+ end
62
+
56
63
  def text
57
64
  map(&:text).join
58
65
  end
@@ -17,9 +17,5 @@ module Moxml
17
17
  def content=(new_content)
18
18
  adapter.set_processing_instruction_content(@native, new_content.to_s)
19
19
  end
20
-
21
- def processing_instruction?
22
- true
23
- end
24
20
  end
25
21
  end
data/lib/moxml/text.rb CHANGED
@@ -9,9 +9,5 @@ module Moxml
9
9
  def content=(text)
10
10
  adapter.set_text_content(@native, normalize_xml_value(text))
11
11
  end
12
-
13
- def text?
14
- true
15
- end
16
12
  end
17
13
  end
data/lib/moxml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
data/lib/ox/node.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ox/node"
4
+
5
+ module Ox
6
+ class Node
7
+ attr_accessor :parent
8
+ end
9
+ end
@@ -0,0 +1 @@
1
+ <root><item>data</item></root>
@@ -25,7 +25,8 @@ RSpec.describe "Test all shared examples" do
25
25
  "README Examples",
26
26
  "XPath Examples",
27
27
  "Memory Usage Examples",
28
- "Thread Safety Examples"
28
+ "Thread Safety Examples",
29
+ "Performance Examples"
29
30
  ]
30
31
 
31
32
  Moxml::Adapter::AVALIABLE_ADAPTERS.each do |adapter_name|
@@ -5,8 +5,8 @@ RSpec.shared_examples "Moxml::Builder" do
5
5
  let(:builder) { Moxml::Builder.new(context) }
6
6
 
7
7
  describe "#document" do
8
- it "creates a well-formed document" do
9
- doc = builder.build do
8
+ let(:doc) do
9
+ builder.build do
10
10
  declaration version: "1.0", encoding: "UTF-8"
11
11
  element "root" do
12
12
  element "child", id: "1" do
@@ -14,12 +14,29 @@ RSpec.shared_examples "Moxml::Builder" do
14
14
  end
15
15
  end
16
16
  end
17
+ end
17
18
 
19
+ it "creates a well-formed document" do
18
20
  xml = doc.to_xml
19
21
  expect(xml).to include('<?xml version="1.0" encoding="UTF-8"?>')
20
22
  expect(xml).to include("<root>")
21
23
  expect(xml).to include('<child id="1">content</child>')
22
24
  expect(xml).to include("</root>")
23
25
  end
26
+
27
+ it "creates the same document through direct manipulation" do
28
+ doc2 = context.create_document
29
+ doc2.add_child(doc2.create_declaration("1.0", "UTF-8"))
30
+ root = doc2.create_element("root")
31
+ child = doc2.create_element("child")
32
+ child["id"] = "1"
33
+ text = doc2.create_text("content")
34
+
35
+ child.add_child(text)
36
+ root.add_child(child)
37
+ doc2.add_child(root)
38
+
39
+ expect(doc.to_xml).to eq(doc2.to_xml)
40
+ end
24
41
  end
25
42
  end
@@ -23,6 +23,11 @@ RSpec.shared_examples "Moxml::Cdata" do
23
23
  cdata.content = nil
24
24
  expect(cdata.content).to eq("")
25
25
  end
26
+
27
+ it "preserves whitespace" do
28
+ cdata.content = " spaced content\n\t"
29
+ expect(cdata.content).to eq(" spaced content\n\t")
30
+ end
26
31
  end
27
32
 
28
33
  describe "serialization" do
@@ -31,6 +36,8 @@ RSpec.shared_examples "Moxml::Cdata" do
31
36
  end
32
37
 
33
38
  it "escapes CDATA end marker" do
39
+ # pending for Ox: https://github.com/ohler55/ox/issues/377
40
+ pending "Ox doesn't escape the end token" if context.config.adapter_name == :ox
34
41
  cdata.content = "content]]>more"
35
42
  expect(cdata.to_xml).to eq("<![CDATA[content]]]]><![CDATA[>more]]>")
36
43
  end
@@ -39,11 +46,6 @@ RSpec.shared_examples "Moxml::Cdata" do
39
46
  cdata.content = "< > & \" '"
40
47
  expect(cdata.to_xml).to include("< > & \" '")
41
48
  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
49
  end
48
50
 
49
51
  describe "node operations" do
@@ -65,17 +65,26 @@ RSpec.shared_examples "Moxml::Declaration" do
65
65
 
66
66
  describe "serialization" do
67
67
  it "formats complete declaration" do
68
- expect(declaration.to_xml).to eq('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
68
+ doc.add_child(declaration)
69
+ expect(doc.to_xml.strip).to end_with('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
69
70
  end
70
71
 
71
72
  it "formats minimal declaration with empty encoding" do
72
73
  decl = doc.create_declaration("1.0", nil)
73
- expect(decl.to_xml).to eq('<?xml version="1.0"?>')
74
+ if context.config.adapter_name == :rexml
75
+ # REXML sets a default encoding when a declaration is added to the document
76
+ expect(decl.to_xml.strip).to eq('<?xml version="1.0"?>')
77
+ else
78
+ # Ox cannot serialize standalone declaration
79
+ doc.add_child(decl)
80
+ expect(doc.to_xml.strip).to end_with('<?xml version="1.0"?>')
81
+ end
74
82
  end
75
83
 
76
84
  it "formats declaration with encoding only" do
77
85
  decl = doc.create_declaration("1.0", "UTF-8")
78
- expect(decl.to_xml).to eq('<?xml version="1.0" encoding="UTF-8"?>')
86
+ doc.add_child(decl)
87
+ expect(doc.to_xml.strip).to end_with('<?xml version="1.0" encoding="UTF-8"?>')
79
88
  end
80
89
  end
81
90
 
@@ -85,7 +94,10 @@ RSpec.shared_examples "Moxml::Declaration" do
85
94
  expect(doc.to_xml).to start_with("<?xml")
86
95
  end
87
96
 
88
- it "removes from document", skip: "The document contains a default declaration" do
97
+ it "removes from document" do
98
+ if Moxml.new.config.adapter.name.match?(/Nokogiri|Rexml|Ox/)
99
+ pending("The document contains a default declaration")
100
+ end
89
101
  doc.add_child(declaration)
90
102
  declaration.remove
91
103
  expect(doc.to_xml).not_to include("<?xml")
@@ -17,7 +17,8 @@ RSpec.shared_examples "Moxml::Doctype" do
17
17
 
18
18
  describe "serialization" do
19
19
  it "wraps content in doctype markers" do
20
- expect(doctype.to_xml).to eq(
20
+ doc.add_child(doctype)
21
+ expect(doc.to_xml.strip).to end_with(
21
22
  '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
22
23
  )
23
24
  end
@@ -106,5 +106,15 @@ RSpec.shared_examples "Moxml::Document" do
106
106
  "</root>"
107
107
  )
108
108
  end
109
+
110
+ it "prevents multiple roots" do
111
+ xml =
112
+ <<~XML
113
+ <?xml version="1.0" encoding="UTF-8"?>
114
+ <root/><another_root/>
115
+ XML
116
+
117
+ expect { context.parse(xml) }.to raise_error(Moxml::Error)
118
+ end
109
119
  end
110
120
  end
@@ -31,6 +31,7 @@ RSpec.shared_examples "Moxml Edge Cases" do
31
31
 
32
32
  describe "malformed content handling" do
33
33
  it "handles CDATA with nested markers" do
34
+ pending "Ox doesn't escape the end token" if context.config.adapter_name == :ox
34
35
  cdata_text = "]]>]]>]]>"
35
36
  doc = context.create_document
36
37
  cdata = doc.create_cdata(cdata_text)
@@ -76,6 +77,7 @@ RSpec.shared_examples "Moxml Edge Cases" do
76
77
 
77
78
  describe "namespace edge cases" do
78
79
  it "handles default namespace changes" do
80
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
79
81
  xml = <<~XML
80
82
  <root xmlns="http://default1.org">
81
83
  <child xmlns="http://default2.org">
@@ -90,6 +92,8 @@ RSpec.shared_examples "Moxml Edge Cases" do
90
92
  end
91
93
 
92
94
  it "handles recursive namespace definitions" do
95
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
96
+
93
97
  xml = <<~XML
94
98
  <root xmlns:a="http://a.org">
95
99
  <a:child xmlns:a="http://b.org">
@@ -106,6 +110,8 @@ RSpec.shared_examples "Moxml Edge Cases" do
106
110
 
107
111
  describe "attribute edge cases" do
108
112
  it "handles attributes with same local name but different namespaces" do
113
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
114
+
109
115
  xml = <<~XML
110
116
  <root xmlns:a="http://a.org" xmlns:b="http://b.org">
111
117
  <element a:id="1" b:id="2"/>
@@ -6,6 +6,10 @@ RSpec.shared_examples "Moxml::Element" do
6
6
  let(:doc) { context.create_document }
7
7
  let(:element) { doc.create_element("test") }
8
8
 
9
+ it "identifies as element node" do
10
+ expect(element).to be_element
11
+ end
12
+
9
13
  describe "name handling" do
10
14
  it "gets name" do
11
15
  expect(element.name).to eq("test")
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "benchmark/ips"
5
+
6
+ RSpec.shared_examples "Performance Examples" do
7
+ let(:context) { Moxml.new }
8
+
9
+ let(:large_xml) do
10
+ xml = "<root>\n"
11
+ 1000.times do |i|
12
+ xml += "<item id='#{i}'><name>Test #{i}</name><value>#{i}</value></item>\n"
13
+ end
14
+ xml += "</root>"
15
+ xml
16
+ end
17
+
18
+ it "measures parsing performance" do
19
+ doc = nil
20
+
21
+ report = Benchmark.ips do |x|
22
+ x.config(time: 5, warmup: 2)
23
+
24
+ x.report("Parser") do
25
+ doc = context.parse(large_xml)
26
+ end
27
+
28
+ x.report("Serializer") do
29
+ _ = doc.to_xml
30
+ end
31
+
32
+ x.compare!
33
+ end
34
+
35
+ # first - parser, second - serializer
36
+ thresholds = {
37
+ nokogiri: [18, 1200],
38
+ oga: [12, 110],
39
+ rexml: [0, 60],
40
+ ox: [2, 2000]
41
+ }
42
+
43
+ report.entries.each_with_index do |entry, index|
44
+ puts "#{entry.label} performance: #{entry.ips.round(2)} ips"
45
+ threshold = thresholds[context.config.adapter_name][index]
46
+ message = "#{entry.label} performance below threshold: " \
47
+ "got #{entry.ips.round(2)} ips, expected >= #{threshold} ips"
48
+ expect(entry.ips).to be >= threshold, message
49
+ end
50
+ end
51
+ end
@@ -29,26 +29,39 @@ RSpec.shared_examples "Memory Usage Examples" do
29
29
  expect(doc.xpath("//large-node")).to be_empty
30
30
  end
31
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
32
+ it "handles deeply nested nodes", skip: "Very flaky tests" do
33
+ doc = context.create_document
34
+ parent = doc
35
+
36
+ 1000.times do |i|
37
+ node = doc.create_element("el_#{i}")
38
+ node.add_child(doc.create_text("Content #{i}"))
39
+ parent.add_child(node)
40
+ parent = node
41
+ end
42
+
43
+ # Process and remove nodes
44
+ memory_before = GetProcessMem.new.bytes
45
+ doc = nil
46
+ GC.start # Oga fails without it
47
+ memory_after = GetProcessMem.new.bytes
48
+
49
+ expect(memory_after).to be <= memory_before
50
+ expect(doc).to be_nil
51
+ end
38
52
 
39
- # Process file
53
+ it "handles streaming processing" do
54
+ pending "Ox has a load_file method, but nothing about a stream" if context.config.adapter_name == :ox
55
+ # Process file
56
+ doc = nil
57
+ File.open("spec/fixtures/small.xml") do |f|
58
+ doc = context.parse(f)
59
+ expect(doc.at_xpath("//item").text).to eq("data")
40
60
  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
61
  end
62
+ GC.start
63
+
64
+ expect(doc).to be_nil
52
65
  end
53
66
  end
54
67
  end
@@ -110,6 +110,7 @@ RSpec.shared_examples "README Examples" do
110
110
  describe "Error handling example" do
111
111
  it "handles errors as shown in README" do
112
112
  context = Moxml.new
113
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
113
114
 
114
115
  expect do
115
116
  context.parse("<invalid>")
@@ -145,6 +146,10 @@ RSpec.shared_examples "README Examples" do
145
146
  def process(xml)
146
147
  @mutex.synchronize do
147
148
  doc = @context.parse(xml)
149
+ # Modify document
150
+ element = doc.create_element("test")
151
+ doc.root.add_child(element)
152
+ doc.root.children.first.remove
148
153
  doc.to_xml
149
154
  end
150
155
  end
@@ -20,6 +20,8 @@ RSpec.shared_examples "Thread Safety Examples" do
20
20
  end
21
21
 
22
22
  it "handles concurrent processing" do
23
+ pending "Ox doesn't have a native XPath" if Moxml.new.config.adapter_name == :ox
24
+
23
25
  processor = processor_class.new
24
26
  threads = []
25
27
  results = Queue.new
@@ -18,22 +18,53 @@ RSpec.shared_examples "XPath Examples" do
18
18
  end
19
19
 
20
20
  it "finds nodes by XPath" do
21
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
22
+
21
23
  books = doc.xpath("//book")
22
24
  expect(books.size).to eq(2)
23
- expect(books.map { |b| b["id"] }).to eq(%w[1 2])
25
+ expect(books.map { _1["id"] }).to eq(%w[1 2])
24
26
  end
25
27
 
26
28
  it "finds nodes with namespaces" do
29
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
30
+
27
31
  titles = doc.xpath("//dc:title",
28
32
  "dc" => "http://purl.org/dc/elements/1.1/")
29
33
  expect(titles.map(&:text)).to eq(%w[First Second])
30
34
  end
31
35
 
32
36
  it "finds nodes by attributes" do
37
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
38
+
33
39
  book = doc.at_xpath('//book[@id="2"]')
34
40
  expect(book).not_to be_nil
35
- expect(book.at_xpath(".//dc:title",
36
- "dc" => "http://purl.org/dc/elements/1.1/").text).to eq("Second")
41
+ title = book.at_xpath(".//dc:title",
42
+ "dc" => "http://purl.org/dc/elements/1.1/")
43
+ expect(title.text).to eq("Second")
44
+ end
45
+
46
+ it "finds nested attributes efficiently" do
47
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
48
+ # More efficient - specific path
49
+ titles1 = doc.xpath("//book/dc:title")
50
+
51
+ # Less efficient - requires full document scan
52
+ titles2 = doc.xpath("//dc:title")
53
+
54
+ # Most efficient - direct child access
55
+ titles3 = doc.root.xpath("./*/dc:title", "dc" => "http://purl.org/dc/elements/1.1/")
56
+
57
+ # Chain queries
58
+ nodes = doc.xpath("//book").map do |book|
59
+ # Each book is a mapped Moxml::Element
60
+ book.at_xpath(".//dc:title", "dc" => "http://purl.org/dc/elements/1.1/").native
61
+ end
62
+ titles4 = ::Moxml::NodeSet.new(nodes, doc.context)
63
+
64
+ expect(titles1.count).to eq(2)
65
+ expect(titles1).to eq(titles2)
66
+ expect(titles1).to eq(titles3)
67
+ expect(titles1).to eq(titles4)
37
68
  end
38
69
  end
39
70
  end
@@ -54,6 +54,7 @@ RSpec.shared_examples "Moxml Integration" do
54
54
  end
55
55
 
56
56
  it "handles xpath queries" do
57
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
57
58
  # Test XPath queries
58
59
  #
59
60
  # XPath with a default namespace is a problem
@@ -68,6 +69,7 @@ RSpec.shared_examples "Moxml Integration" do
68
69
 
69
70
  describe "namespace handling" do
70
71
  it "handles complex namespace scenarios" do
72
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
71
73
  xml = <<~XML
72
74
  <root xmlns="http://default.org" xmlns:a="http://a.org" xmlns:b="http://b.org">
73
75
  <child>
@@ -108,6 +110,8 @@ RSpec.shared_examples "Moxml Integration" do
108
110
  let(:doc) { context.parse("<root><a>1</a><b>2</b></root>") }
109
111
 
110
112
  it "handles complex modifications" do
113
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
114
+
111
115
  # Move nodes
112
116
  b_node = doc.at_xpath("//b")
113
117
  a_node = doc.at_xpath("//a")
@@ -41,6 +41,22 @@ RSpec.shared_examples "Moxml::Namespace" do
41
41
  element.add_namespace(nil, "http://example.org")
42
42
  expect(element.namespaces.first.to_s).to eq('xmlns="http://example.org"')
43
43
  end
44
+
45
+ it "renders the same xml - a readme example" do
46
+ # chainable operations
47
+ element
48
+ .add_namespace("dc", "http://purl.org/dc/elements/1.1/")
49
+ .add_child(doc.create_text("content"))
50
+
51
+ # clear node type checking
52
+ node = doc.create_element("test")
53
+ if node.element?
54
+ node.add_namespace("dc", "http://purl.org/dc/elements/1.1/")
55
+ node.add_child(doc.create_text("content"))
56
+ end
57
+
58
+ expect(element.to_xml).to eq(node.to_xml)
59
+ end
44
60
  end
45
61
 
46
62
  describe "equality" do
@@ -96,12 +96,16 @@ RSpec.shared_examples "Moxml::Node" do
96
96
  let(:doc) { context.parse("<root><a><b>1</b></a><a><b>2</b></a></root>") }
97
97
 
98
98
  it "finds nodes by xpath" do
99
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
100
+
99
101
  nodes = doc.xpath("//b")
100
102
  expect(nodes.size).to eq(2)
101
103
  expect(nodes.map(&:text)).to eq(%w[1 2])
102
104
  end
103
105
 
104
106
  it "finds first node by xpath" do
107
+ pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
108
+
105
109
  node = doc.at_xpath("//b")
106
110
  expect(node.text).to eq("1")
107
111
  end
@@ -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
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.4
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-05-12 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
 
@@ -37,6 +37,9 @@ files:
37
37
  - lib/moxml/adapter/base.rb
38
38
  - lib/moxml/adapter/customized_oga/xml_declaration.rb
39
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
40
43
  - lib/moxml/adapter/customized_rexml/formatter.rb
41
44
  - lib/moxml/adapter/nokogiri.rb
42
45
  - lib/moxml/adapter/oga.rb
@@ -62,8 +65,10 @@ files:
62
65
  - lib/moxml/version.rb
63
66
  - lib/moxml/xml_utils.rb
64
67
  - lib/moxml/xml_utils/encoder.rb
68
+ - lib/ox/node.rb
65
69
  - moxml.gemspec
66
70
  - sig/moxml.rbs
71
+ - spec/fixtures/small.xml
67
72
  - spec/moxml/adapter/nokogiri_spec.rb
68
73
  - spec/moxml/adapter/oga_spec.rb
69
74
  - spec/moxml/adapter/ox_spec.rb
@@ -87,6 +92,7 @@ files:
87
92
  - spec/support/shared_examples/element.rb
88
93
  - spec/support/shared_examples/examples/attribute.rb
89
94
  - spec/support/shared_examples/examples/basic_usage.rb
95
+ - spec/support/shared_examples/examples/benchmark_spec.rb
90
96
  - spec/support/shared_examples/examples/memory.rb
91
97
  - spec/support/shared_examples/examples/namespace.rb
92
98
  - spec/support/shared_examples/examples/readme_examples.rb