moxml 0.1.14 → 0.1.16
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +117 -66
- data/Gemfile +1 -0
- data/README.adoc +11 -9
- data/Rakefile +34 -1
- data/TODO.remaining/1-entity-reference-adapter-support.md +157 -0
- data/TODO.remaining/2-entity-restoration-model-driven.md +169 -0
- data/TODO.remaining/3-entity-reference-test-coverage.md +170 -0
- data/TODO.remaining/4-lenient-entities-mode.md +106 -0
- data/TODO.remaining/5-fixture-integrity.md +65 -0
- data/TODO.remaining/6-ox-element-ordering-bug.md +36 -0
- data/TODO.remaining/7-headed-ox-limitations.md +95 -0
- data/TODO.remaining/8-xpath-predicate-gaps.md +68 -0
- data/TODO.remaining/9-cleanup-hygiene.md +42 -0
- data/TODO.remaining/README.md +54 -0
- data/benchmarks/generate_report.rb +1 -1
- data/docs/_pages/configuration.adoc +22 -19
- data/docs/_tutorials/namespace-handling.adoc +5 -5
- data/lib/moxml/adapter/base.rb +22 -3
- data/lib/moxml/adapter/customized_libxml/declaration.rb +1 -1
- data/lib/moxml/adapter/customized_libxml/entity_reference.rb +23 -0
- data/lib/moxml/adapter/customized_libxml.rb +18 -0
- data/lib/moxml/adapter/customized_oga.rb +10 -0
- data/lib/moxml/adapter/customized_ox/entity_reference.rb +25 -0
- data/lib/moxml/adapter/customized_ox.rb +12 -0
- data/lib/moxml/adapter/customized_rexml/entity_reference.rb +19 -0
- data/lib/moxml/adapter/customized_rexml/formatter.rb +44 -20
- data/lib/moxml/adapter/customized_rexml.rb +11 -0
- data/lib/moxml/adapter/headed_ox.rb +37 -14
- data/lib/moxml/adapter/libxml.rb +233 -119
- data/lib/moxml/adapter/nokogiri.rb +22 -11
- data/lib/moxml/adapter/oga.rb +64 -25
- data/lib/moxml/adapter/ox.rb +198 -42
- data/lib/moxml/adapter/rexml.rb +64 -13
- data/lib/moxml/attribute.rb +3 -0
- data/lib/moxml/builder.rb +78 -24
- data/lib/moxml/config.rb +24 -7
- data/lib/moxml/declaration.rb +4 -2
- data/lib/moxml/document.rb +8 -1
- data/lib/moxml/document_builder.rb +44 -37
- data/lib/moxml/element.rb +18 -5
- data/lib/moxml/entity_registry.rb +51 -1
- data/lib/moxml/native_attachment.rb +65 -0
- data/lib/moxml/node.rb +39 -50
- data/lib/moxml/node_set.rb +43 -15
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils.rb +1 -1
- data/lib/moxml/xpath/compiler.rb +4 -1
- data/lib/moxml.rb +1 -0
- data/scripts/format_xml.rb +16 -0
- data/scripts/pretty_format_xml.rb +14 -0
- data/spec/consistency/round_trip_spec.rb +3 -30
- data/spec/integration/all_adapters_spec.rb +1 -0
- data/spec/integration/headed_ox_integration_spec.rb +0 -2
- data/spec/integration/shared_examples/edge_cases.rb +7 -4
- data/spec/integration/shared_examples/integration_workflows.rb +3 -3
- data/spec/integration/shared_examples/node_wrappers/cdata_behavior.rb +1 -1
- data/spec/integration/shared_examples/node_wrappers/entity_reference_behavior.rb +224 -0
- data/spec/integration/shared_examples/node_wrappers/node_behavior.rb +1 -1
- data/spec/moxml/adapter/headed_ox_spec.rb +8 -8
- data/spec/moxml/adapter/oga_spec.rb +46 -0
- data/spec/moxml/adapter/shared_examples/adapter_contract.rb +1 -12
- data/spec/moxml/allocation_benchmark_spec.rb +96 -0
- data/spec/moxml/allocation_guard_spec.rb +282 -0
- data/spec/moxml/builder_spec.rb +256 -0
- data/spec/moxml/config_spec.rb +11 -11
- data/spec/moxml/doctype_spec.rb +41 -0
- data/spec/moxml/lazy_parse_spec.rb +115 -0
- data/spec/moxml/namespace_uri_validation_spec.rb +11 -3
- data/spec/moxml/node_cache_spec.rb +110 -0
- data/spec/moxml/node_set_cache_spec.rb +90 -0
- data/spec/moxml/xml_utils_spec.rb +32 -0
- data/spec/moxml/xpath/axes_spec.rb +1 -1
- data/spec/moxml/xpath/compiler_spec.rb +2 -2
- data/spec/moxml/xpath/functions/position_functions_spec.rb +5 -5
- data/spec/moxml/xpath/functions/special_functions_spec.rb +1 -1
- data/spec/performance/memory_usage_spec.rb +0 -4
- data/spec/support/allocation_helper.rb +165 -0
- data/spec/support/w3c_namespace_helpers.rb +2 -1
- metadata +29 -2
data/lib/moxml/node.rb
CHANGED
|
@@ -16,8 +16,14 @@ module Moxml
|
|
|
16
16
|
|
|
17
17
|
def initialize(native, context)
|
|
18
18
|
@context = context
|
|
19
|
-
# @native = adapter.patch_node(native)
|
|
20
19
|
@native = native
|
|
20
|
+
@parent_node = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Update native reference after identity-changing operations
|
|
24
|
+
# (e.g., LibXML doc.root= creates a new Ruby wrapper)
|
|
25
|
+
def refresh_native!(new_native)
|
|
26
|
+
@native = new_native
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def document
|
|
@@ -29,9 +35,10 @@ module Moxml
|
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
def children
|
|
32
|
-
NodeSet.new(
|
|
38
|
+
@children ||= NodeSet.new(
|
|
33
39
|
adapter.children(@native).map { adapter.patch_node(_1, @native) },
|
|
34
40
|
context,
|
|
41
|
+
self,
|
|
35
42
|
)
|
|
36
43
|
end
|
|
37
44
|
|
|
@@ -46,29 +53,40 @@ module Moxml
|
|
|
46
53
|
def add_child(node)
|
|
47
54
|
node = prepare_node(node)
|
|
48
55
|
adapter.add_child(@native, node.native)
|
|
56
|
+
# Refresh native in case adapter changed identity (e.g., LibXML doc.root=)
|
|
57
|
+
refreshed = adapter.actual_native(node.native, @native)
|
|
58
|
+
node.refresh_native!(refreshed) if refreshed && refreshed != node.native
|
|
59
|
+
node.parent_node = self
|
|
60
|
+
invalidate_children_cache!
|
|
49
61
|
self
|
|
50
62
|
end
|
|
51
63
|
|
|
52
64
|
def add_previous_sibling(node)
|
|
53
65
|
node = prepare_node(node)
|
|
54
66
|
adapter.add_previous_sibling(@native, node.native)
|
|
67
|
+
invalidate_parent_children_cache!
|
|
55
68
|
self
|
|
56
69
|
end
|
|
57
70
|
|
|
58
71
|
def add_next_sibling(node)
|
|
59
72
|
node = prepare_node(node)
|
|
60
73
|
adapter.add_next_sibling(@native, node.native)
|
|
74
|
+
invalidate_parent_children_cache!
|
|
61
75
|
self
|
|
62
76
|
end
|
|
63
77
|
|
|
64
78
|
def remove
|
|
79
|
+
invalidate_parent_children_cache!
|
|
65
80
|
adapter.remove(@native)
|
|
81
|
+
invalidate_children_cache!
|
|
66
82
|
self
|
|
67
83
|
end
|
|
68
84
|
|
|
69
85
|
def replace(node)
|
|
70
86
|
node = prepare_node(node)
|
|
87
|
+
invalidate_parent_children_cache!
|
|
71
88
|
adapter.replace(@native, node.native)
|
|
89
|
+
invalidate_children_cache!
|
|
72
90
|
self
|
|
73
91
|
end
|
|
74
92
|
|
|
@@ -219,6 +237,11 @@ module Moxml
|
|
|
219
237
|
klass.new(node, context)
|
|
220
238
|
end
|
|
221
239
|
|
|
240
|
+
# Internal: Set the parent node for cache invalidation tracking.
|
|
241
|
+
# Called by NodeSet, Document, Element when establishing parent-child
|
|
242
|
+
# relationships. Public to allow cross-class usage within Moxml internals.
|
|
243
|
+
attr_writer :parent_node
|
|
244
|
+
|
|
222
245
|
protected
|
|
223
246
|
|
|
224
247
|
def adapter
|
|
@@ -229,6 +252,18 @@ module Moxml
|
|
|
229
252
|
context.config.adapter
|
|
230
253
|
end
|
|
231
254
|
|
|
255
|
+
# Invalidate cached children. Called by mutation methods
|
|
256
|
+
# and by Element attribute/namespace caches.
|
|
257
|
+
def invalidate_children_cache!
|
|
258
|
+
@children = nil
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Invalidate parent's cached children when this node
|
|
262
|
+
# is removed/replaced from its parent's child list.
|
|
263
|
+
def invalidate_parent_children_cache!
|
|
264
|
+
@parent_node&.invalidate_children_cache!
|
|
265
|
+
end
|
|
266
|
+
|
|
232
267
|
private
|
|
233
268
|
|
|
234
269
|
def prepare_node(node)
|
|
@@ -260,54 +295,8 @@ module Moxml
|
|
|
260
295
|
return options[:declaration] if options.key?(:declaration)
|
|
261
296
|
return options.fetch(:declaration, false) unless is_a?(Document)
|
|
262
297
|
|
|
263
|
-
# For Document nodes,
|
|
264
|
-
|
|
265
|
-
# Native state reflects programmatic changes (e.g., add/remove)
|
|
266
|
-
|
|
267
|
-
adapter_name = adapter.to_s.split("::").last
|
|
268
|
-
|
|
269
|
-
case adapter_name
|
|
270
|
-
when "Nokogiri"
|
|
271
|
-
# Nokogiri: if @xml_decl is explicitly set, use that state
|
|
272
|
-
# Otherwise, trust wrapper flag (for parsed documents)
|
|
273
|
-
if native.respond_to?(:instance_variable_defined?) &&
|
|
274
|
-
native.instance_variable_defined?(:@xml_decl)
|
|
275
|
-
# Explicitly set (programmatically added) - check if nil
|
|
276
|
-
!native.instance_variable_get(:@xml_decl).nil?
|
|
277
|
-
else
|
|
278
|
-
# Not set (parsed document) - trust wrapper flag
|
|
279
|
-
has_xml_declaration
|
|
280
|
-
end
|
|
281
|
-
when "Rexml"
|
|
282
|
-
# REXML: check @xml_declaration instance variable
|
|
283
|
-
# If not defined (parsed doc), trust wrapper flag
|
|
284
|
-
if native.respond_to?(:instance_variable_defined?) &&
|
|
285
|
-
native.instance_variable_defined?(:@xml_declaration)
|
|
286
|
-
# Explicitly set - check if nil
|
|
287
|
-
!native.instance_variable_get(:@xml_declaration).nil?
|
|
288
|
-
else
|
|
289
|
-
# Not set (parsed document) - trust wrapper flag
|
|
290
|
-
has_xml_declaration
|
|
291
|
-
end
|
|
292
|
-
when "Oga"
|
|
293
|
-
native.respond_to?(:xml_declaration) && !native.xml_declaration.nil?
|
|
294
|
-
when "Ox", "HeadedOx"
|
|
295
|
-
# Ox stores declaration in document attributes
|
|
296
|
-
native[:version] || native[:encoding] || native[:standalone]
|
|
297
|
-
when "Libxml"
|
|
298
|
-
# LibXML stores declaration wrapper as instance variable
|
|
299
|
-
if native.respond_to?(:instance_variable_defined?) &&
|
|
300
|
-
native.instance_variable_defined?(:@moxml_declaration)
|
|
301
|
-
# Explicitly set - check if nil
|
|
302
|
-
!native.instance_variable_get(:@moxml_declaration).nil?
|
|
303
|
-
else
|
|
304
|
-
# Not set - trust wrapper flag
|
|
305
|
-
has_xml_declaration
|
|
306
|
-
end
|
|
307
|
-
else
|
|
308
|
-
# Fallback - trust wrapper flag
|
|
309
|
-
has_xml_declaration
|
|
310
|
-
end
|
|
298
|
+
# For Document nodes, delegate to adapter for native state check
|
|
299
|
+
adapter.has_declaration?(@native, self)
|
|
311
300
|
end
|
|
312
301
|
end
|
|
313
302
|
end
|
data/lib/moxml/node_set.rb
CHANGED
|
@@ -6,60 +6,72 @@ module Moxml
|
|
|
6
6
|
|
|
7
7
|
attr_reader :nodes, :context
|
|
8
8
|
|
|
9
|
-
def initialize(nodes, context)
|
|
9
|
+
def initialize(nodes, context, parent_node = nil)
|
|
10
10
|
@nodes = Array(nodes)
|
|
11
11
|
@context = context
|
|
12
|
+
@wrapped = Array.new(@nodes.size)
|
|
13
|
+
@parent_node = parent_node
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def each
|
|
15
17
|
return to_enum(:each) unless block_given?
|
|
16
18
|
|
|
17
|
-
nodes.
|
|
19
|
+
@nodes.each_with_index do |node, i|
|
|
20
|
+
@wrapped[i] ||= wrap_with_parent(node)
|
|
21
|
+
yield @wrapped[i]
|
|
22
|
+
end
|
|
18
23
|
self
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
def [](index)
|
|
22
27
|
case index
|
|
23
28
|
when Integer
|
|
24
|
-
|
|
29
|
+
actual = index.negative? ? @nodes.size + index : index
|
|
30
|
+
return nil unless actual >= 0 && actual < @nodes.size
|
|
31
|
+
|
|
32
|
+
@wrapped[actual] ||= wrap_with_parent(@nodes[actual])
|
|
25
33
|
when Range
|
|
26
|
-
|
|
34
|
+
self.class.new(@nodes[index], @context)
|
|
27
35
|
end
|
|
28
36
|
end
|
|
29
37
|
|
|
30
38
|
def first(n = nil)
|
|
31
39
|
if n.nil?
|
|
32
|
-
|
|
40
|
+
@nodes.empty? ? nil : self[0]
|
|
33
41
|
else
|
|
34
|
-
|
|
42
|
+
n.times.filter_map { |i| self[i] }
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
|
|
38
46
|
def last
|
|
39
|
-
|
|
47
|
+
@nodes.empty? ? nil : self[@nodes.size - 1]
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
def empty?
|
|
43
|
-
nodes.empty?
|
|
51
|
+
@nodes.empty?
|
|
44
52
|
end
|
|
45
53
|
|
|
46
54
|
def size
|
|
47
|
-
nodes.size
|
|
55
|
+
@nodes.size
|
|
48
56
|
end
|
|
49
57
|
alias length size
|
|
50
58
|
|
|
51
59
|
def to_a
|
|
52
|
-
|
|
60
|
+
@nodes.each_with_index do |_node, i|
|
|
61
|
+
@wrapped[i] ||= wrap_with_parent(@nodes[i])
|
|
62
|
+
end
|
|
63
|
+
@wrapped.compact
|
|
53
64
|
end
|
|
54
65
|
|
|
55
66
|
def +(other)
|
|
56
|
-
self.class.new(nodes + other.nodes, context)
|
|
67
|
+
self.class.new(@nodes + other.nodes, @context)
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
def <<(node)
|
|
60
71
|
# If it's a wrapped Moxml node, unwrap to native before storing
|
|
61
72
|
native_node = node.respond_to?(:native) ? node.native : node
|
|
62
73
|
@nodes << native_node
|
|
74
|
+
@wrapped << nil
|
|
63
75
|
self
|
|
64
76
|
end
|
|
65
77
|
alias push <<
|
|
@@ -78,14 +90,14 @@ module Moxml
|
|
|
78
90
|
true
|
|
79
91
|
end
|
|
80
92
|
end
|
|
81
|
-
self.class.new(unique_natives, context)
|
|
93
|
+
self.class.new(unique_natives, @context)
|
|
82
94
|
end
|
|
83
95
|
|
|
84
96
|
def ==(other)
|
|
85
97
|
self.class == other.class &&
|
|
86
98
|
length == other.length &&
|
|
87
|
-
nodes.each_with_index.all? do |
|
|
88
|
-
|
|
99
|
+
@nodes.each_with_index.all? do |_node, index|
|
|
100
|
+
self[index] == other[index]
|
|
89
101
|
end
|
|
90
102
|
end
|
|
91
103
|
|
|
@@ -103,8 +115,24 @@ module Moxml
|
|
|
103
115
|
def delete(node)
|
|
104
116
|
# If it's a wrapped Moxml node, unwrap to native
|
|
105
117
|
native_node = node.respond_to?(:native) ? node.native : node
|
|
106
|
-
@nodes.
|
|
118
|
+
idx = @nodes.index(native_node)
|
|
119
|
+
if idx
|
|
120
|
+
@nodes.delete_at(idx)
|
|
121
|
+
@wrapped.delete_at(idx)
|
|
122
|
+
else
|
|
123
|
+
@nodes.delete(native_node)
|
|
124
|
+
end
|
|
107
125
|
self
|
|
108
126
|
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def wrap_with_parent(native_node)
|
|
131
|
+
wrapped = Moxml::Node.wrap(native_node, @context)
|
|
132
|
+
if @parent_node && wrapped
|
|
133
|
+
wrapped.parent_node = @parent_node
|
|
134
|
+
end
|
|
135
|
+
wrapped
|
|
136
|
+
end
|
|
109
137
|
end
|
|
110
138
|
end
|
data/lib/moxml/version.rb
CHANGED
data/lib/moxml/xml_utils.rb
CHANGED
data/lib/moxml/xpath/compiler.rb
CHANGED
|
@@ -1738,7 +1738,10 @@ module Moxml
|
|
|
1738
1738
|
until visit.empty?
|
|
1739
1739
|
current = visit.pop
|
|
1740
1740
|
|
|
1741
|
-
|
|
1741
|
+
# Function name is stored in :value field, not children
|
|
1742
|
+
if (current.type == :call || current.type == :function) && current.value == name
|
|
1743
|
+
return true
|
|
1744
|
+
end
|
|
1742
1745
|
|
|
1743
1746
|
current.children.each do |child|
|
|
1744
1747
|
visit << child if child.is_a?(AST::Node)
|
data/lib/moxml.rb
CHANGED
|
@@ -43,6 +43,7 @@ require_relative "moxml/builder"
|
|
|
43
43
|
require_relative "moxml/config"
|
|
44
44
|
require_relative "moxml/context"
|
|
45
45
|
require_relative "moxml/entity_registry"
|
|
46
|
+
require_relative "moxml/native_attachment"
|
|
46
47
|
require_relative "moxml/adapter"
|
|
47
48
|
require_relative "moxml/xpath"
|
|
48
49
|
require_relative "moxml/sax"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "nokogiri"
|
|
5
|
+
|
|
6
|
+
input = ARGV[0]
|
|
7
|
+
output = ARGV[1] || input
|
|
8
|
+
|
|
9
|
+
abort "Usage: #{$PROGRAM_NAME} <input.xml> [output.xml]" unless input
|
|
10
|
+
abort "File not found: #{input}" unless File.exist?(input)
|
|
11
|
+
|
|
12
|
+
doc = Nokogiri::XML(File.read(input), &:noblanks)
|
|
13
|
+
formatted = doc.to_xml(indent: 2)
|
|
14
|
+
|
|
15
|
+
File.write(output, formatted)
|
|
16
|
+
puts "Formatted #{input}#{" -> #{output}" if output != input}"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "nokogiri"
|
|
5
|
+
|
|
6
|
+
input = ARGV[0] or abort "Usage: #{$0} <input.xml> [output.xml]"
|
|
7
|
+
output = ARGV[1] || input
|
|
8
|
+
|
|
9
|
+
xml = File.read(input)
|
|
10
|
+
doc = Nokogiri::XML(xml, &:noblanks)
|
|
11
|
+
formatted = doc.to_xml(indent: 2)
|
|
12
|
+
|
|
13
|
+
File.write(output, formatted)
|
|
14
|
+
puts "Written to #{output}"
|
|
@@ -62,14 +62,9 @@ def traverse_with_consistent_order(element, elements_array)
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
if element.respond_to?(:children)
|
|
65
|
-
#
|
|
65
|
+
# Only traverse element children (skip text, comment, cdata nodes)
|
|
66
66
|
children = element.children.select do |child|
|
|
67
|
-
|
|
68
|
-
child.respond_to?(:name) &&
|
|
69
|
-
child.name &&
|
|
70
|
-
!child.name.empty? &&
|
|
71
|
-
child.name != "text" &&
|
|
72
|
-
child.name != "comment"
|
|
67
|
+
child.respond_to?(:element?) && child.element?
|
|
73
68
|
end
|
|
74
69
|
|
|
75
70
|
# CRITICAL: Enhanced sorting with multiple criteria for stability
|
|
@@ -324,29 +319,7 @@ EXAMPLE_TIMEOUT = ENV.fetch("MOXML_ROUNDTRIP_TIMEOUT", 120).to_i
|
|
|
324
319
|
# Fixture cache — loaded once, shared across all examples.
|
|
325
320
|
FIXTURE_CACHE = {}
|
|
326
321
|
|
|
327
|
-
|
|
328
|
-
# These (fixture_relative_path, source_adapter, target_adapter) tuples fail the
|
|
329
|
-
# elements_with_attributes comparison because Ox produces elements in a different
|
|
330
|
-
# order. The semantic equivalence check (double round-trip) still passes.
|
|
331
|
-
# TODO: Investigate and fix the root cause in ox adapter element ordering.
|
|
332
|
-
KNOWN_ELEMENT_ORDERING_ISSUES = Set.new([
|
|
333
|
-
# niso-jats/element_citation.xml - Ox produces different element ordering
|
|
334
|
-
["niso-jats/element_citation.xml", :nokogiri, :ox],
|
|
335
|
-
["niso-jats/element_citation.xml", :ox, :nokogiri],
|
|
336
|
-
["niso-jats/element_citation.xml", :ox, :oga],
|
|
337
|
-
["niso-jats/element_citation.xml", :oga, :ox],
|
|
338
|
-
["niso-jats/element_citation.xml", :rexml, :ox],
|
|
339
|
-
["niso-jats/element_citation.xml", :ox, :rexml],
|
|
340
|
-
["niso-jats/pnas_sample.xml", :nokogiri, :rexml],
|
|
341
|
-
["niso-jats/pnas_sample.xml", :rexml, :nokogiri],
|
|
342
|
-
# metanorma fixtures with similar issues
|
|
343
|
-
["metanorma/collection1nested.xml", :nokogiri, :ox],
|
|
344
|
-
["metanorma/collection1nested.xml", :ox, :nokogiri],
|
|
345
|
-
["metanorma/collection1nested.xml", :ox, :oga],
|
|
346
|
-
["metanorma/collection1nested.xml", :oga, :ox],
|
|
347
|
-
["metanorma/collection1nested.xml", :rexml, :ox],
|
|
348
|
-
["metanorma/collection1nested.xml", :ox, :rexml],
|
|
349
|
-
])
|
|
322
|
+
KNOWN_ELEMENT_ORDERING_ISSUES = Set.new([])
|
|
350
323
|
|
|
351
324
|
RSpec.describe "Round-trip XML Testing", :round_trip do
|
|
352
325
|
# Explicit adapter names for clarity and maintainability.
|
|
@@ -187,7 +187,6 @@ RSpec.describe "HeadedOx Integration" do
|
|
|
187
187
|
end
|
|
188
188
|
|
|
189
189
|
it "selects last node with last()" do
|
|
190
|
-
skip "HeadedOx limitation: last() in predicate context needs temporary nodeset. See docs/HEADED_OX_LIMITATIONS.md"
|
|
191
190
|
last_book = doc.xpath("//book[position() = last()]")
|
|
192
191
|
|
|
193
192
|
expect(last_book.size).to eq(1)
|
|
@@ -198,7 +197,6 @@ RSpec.describe "HeadedOx Integration" do
|
|
|
198
197
|
|
|
199
198
|
describe "real-world use cases" do
|
|
200
199
|
it "finds books by author and price range" do
|
|
201
|
-
skip "HeadedOx limitation: Text content access from nested elements needs investigation. See docs/HEADED_OX_LIMITATIONS.md"
|
|
202
200
|
results = doc.xpath(
|
|
203
201
|
"//book[contains(author, 'Alice') and @price < 20]",
|
|
204
202
|
)
|
|
@@ -36,7 +36,7 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
36
36
|
pending "Ox doesn't escape the end token"
|
|
37
37
|
end
|
|
38
38
|
if context.config.adapter_name == :headed_ox
|
|
39
|
-
skip "HeadedOx limitation: Ox doesn't escape CDATA end markers. See docs/
|
|
39
|
+
skip "HeadedOx limitation: Ox doesn't escape CDATA end markers. See docs/_pages/headed-ox-limitations.adoc"
|
|
40
40
|
end
|
|
41
41
|
cdata_text = "]]>]]>]]>"
|
|
42
42
|
doc = context.create_document
|
|
@@ -90,11 +90,14 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
90
90
|
pending "Ox doesn't have a native XPath"
|
|
91
91
|
end
|
|
92
92
|
if context.config.adapter_name == :headed_ox
|
|
93
|
-
skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/
|
|
93
|
+
skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/_pages/headed-ox-limitations.adoc"
|
|
94
94
|
end
|
|
95
95
|
if context.config.adapter_name == :libxml
|
|
96
96
|
skip "LibXML cannot query empty default namespace with XPath (documented limitation)"
|
|
97
97
|
end
|
|
98
|
+
if context.config.adapter_name == :nokogiri
|
|
99
|
+
skip "Nokogiri XPath does not support querying empty namespace with xmlns prefix mapping"
|
|
100
|
+
end
|
|
98
101
|
xml = <<~XML
|
|
99
102
|
<root xmlns="http://default1.org">
|
|
100
103
|
<child xmlns="http://default2.org">
|
|
@@ -113,7 +116,7 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
113
116
|
pending "Ox doesn't have a native XPath"
|
|
114
117
|
end
|
|
115
118
|
if context.config.adapter_name == :headed_ox
|
|
116
|
-
skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/
|
|
119
|
+
skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/_pages/headed-ox-limitations.adoc"
|
|
117
120
|
end
|
|
118
121
|
|
|
119
122
|
xml = <<~XML
|
|
@@ -133,7 +136,7 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
133
136
|
describe "attribute edge cases" do
|
|
134
137
|
it "handles attributes with same local name but different namespaces" do
|
|
135
138
|
if context.config.adapter_name == :headed_ox
|
|
136
|
-
skip "HeadedOx limitation: Namespace-prefixed attribute access needs Ox namespace API enhancement. See docs/
|
|
139
|
+
skip "HeadedOx limitation: Namespace-prefixed attribute access needs Ox namespace API enhancement. See docs/_pages/headed-ox-limitations.adoc"
|
|
137
140
|
end
|
|
138
141
|
if context.config.adapter_name == :ox
|
|
139
142
|
skip "Ox doesn't have a native XPath"
|
|
@@ -60,7 +60,7 @@ RSpec.shared_examples "Moxml Integration" do
|
|
|
60
60
|
pending "Ox doesn't support namespace-aware XPath with predicates"
|
|
61
61
|
end
|
|
62
62
|
if context.config.adapter_name == :headed_ox
|
|
63
|
-
skip "HeadedOx limitation: Namespace-aware XPath with predicates needs investigation. See docs/
|
|
63
|
+
skip "HeadedOx limitation: Namespace-aware XPath with predicates needs investigation. See docs/_pages/headed-ox-limitations.adoc"
|
|
64
64
|
end
|
|
65
65
|
# Test XPath queries
|
|
66
66
|
#
|
|
@@ -80,7 +80,7 @@ RSpec.shared_examples "Moxml Integration" do
|
|
|
80
80
|
pending "Ox doesn't have a native XPath"
|
|
81
81
|
end
|
|
82
82
|
if context.config.adapter_name == :headed_ox
|
|
83
|
-
skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/
|
|
83
|
+
skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/_pages/headed-ox-limitations.adoc"
|
|
84
84
|
end
|
|
85
85
|
xml = <<~XML
|
|
86
86
|
<root xmlns="http://default.org" xmlns:a="http://a.org" xmlns:b="http://b.org">
|
|
@@ -123,7 +123,7 @@ RSpec.shared_examples "Moxml Integration" do
|
|
|
123
123
|
|
|
124
124
|
it "handles complex modifications" do
|
|
125
125
|
if context.config.adapter_name == :headed_ox
|
|
126
|
-
skip "HeadedOx limitation: Parent setter not implemented. Requires Ox node reparenting API. See docs/
|
|
126
|
+
skip "HeadedOx limitation: Parent setter not implemented. Requires Ox node reparenting API. See docs/_pages/headed-ox-limitations.adoc"
|
|
127
127
|
end
|
|
128
128
|
if context.config.adapter_name == :ox
|
|
129
129
|
skip "Ox doesn't have a native XPath"
|
|
@@ -41,7 +41,7 @@ RSpec.shared_examples "Moxml::Cdata" do
|
|
|
41
41
|
pending "Ox doesn't escape the end token"
|
|
42
42
|
end
|
|
43
43
|
if context.config.adapter_name == :headed_ox
|
|
44
|
-
skip "HeadedOx limitation: Ox doesn't escape CDATA end markers. See docs/
|
|
44
|
+
skip "HeadedOx limitation: Ox doesn't escape CDATA end markers. See docs/_pages/headed-ox-limitations.adoc"
|
|
45
45
|
end
|
|
46
46
|
cdata.content = "content]]>more"
|
|
47
47
|
expect(cdata.to_xml).to eq("<![CDATA[content]]]]><![CDATA[>more]]>")
|