moxml 0.1.13 → 0.1.15
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 +3 -1
- data/docs/_pages/configuration.adoc +22 -19
- data/docs/_tutorials/namespace-handling.adoc +5 -5
- data/lib/moxml/adapter/base.rb +8 -3
- 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/xml_generator.rb +2 -2
- 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 +2 -0
- data/lib/moxml/adapter/customized_rexml.rb +11 -0
- data/lib/moxml/adapter/headed_ox.rb +9 -3
- data/lib/moxml/adapter/libxml.rb +76 -62
- data/lib/moxml/adapter/nokogiri.rb +4 -5
- data/lib/moxml/adapter/oga.rb +50 -26
- data/lib/moxml/adapter/ox.rb +189 -41
- data/lib/moxml/adapter/rexml.rb +27 -8
- data/lib/moxml/attribute.rb +3 -0
- data/lib/moxml/builder.rb +1 -0
- data/lib/moxml/config.rb +7 -7
- data/lib/moxml/document.rb +5 -1
- data/lib/moxml/document_builder.rb +37 -31
- data/lib/moxml/element.rb +13 -5
- data/lib/moxml/entity_registry.rb +36 -0
- data/lib/moxml/node.rb +23 -2
- data/lib/moxml/node_set.rb +43 -15
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils.rb +1 -1
- data/spec/integration/shared_examples/edge_cases.rb +3 -0
- data/spec/moxml/adapter/oga_spec.rb +62 -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 +22 -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/support/allocation_helper.rb +165 -0
- data/spec/support/w3c_namespace_helpers.rb +2 -1
- metadata +15 -2
data/lib/moxml/adapter/ox.rb
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
require_relative "base"
|
|
4
4
|
require "ox"
|
|
5
5
|
require "stringio"
|
|
6
|
-
require_relative "customized_ox
|
|
7
|
-
require_relative "customized_ox/attribute"
|
|
8
|
-
require_relative "customized_ox/namespace"
|
|
6
|
+
require_relative "customized_ox"
|
|
9
7
|
|
|
10
8
|
# insert :parent methods to all Ox classes inherit the Node class
|
|
11
9
|
Ox::Node.attr_accessor :parent
|
|
@@ -17,16 +15,19 @@ module Moxml
|
|
|
17
15
|
replace_children(doc, [element])
|
|
18
16
|
end
|
|
19
17
|
|
|
20
|
-
def parse(xml,
|
|
18
|
+
def parse(xml, options = {}, _context = nil)
|
|
21
19
|
native_doc = begin
|
|
22
20
|
result = ::Ox.parse(xml)
|
|
23
21
|
|
|
24
22
|
# result can be either Document or Element
|
|
25
23
|
if result.is_a?(::Ox::Document)
|
|
24
|
+
assign_parents(result)
|
|
25
|
+
validate_single_root(result) if options[:strict]
|
|
26
26
|
result
|
|
27
27
|
else
|
|
28
28
|
doc = ::Ox::Document.new
|
|
29
29
|
doc << result
|
|
30
|
+
assign_parents(doc)
|
|
30
31
|
doc
|
|
31
32
|
end
|
|
32
33
|
rescue ::Ox::ParseError => e
|
|
@@ -37,7 +38,7 @@ module Moxml
|
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
ctx = _context || Context.new(:ox)
|
|
40
|
-
|
|
41
|
+
Document.new(native_doc, ctx)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
# SAX parsing implementation for Ox
|
|
@@ -50,7 +51,7 @@ module Moxml
|
|
|
50
51
|
bridge = OxSAXBridge.new(handler)
|
|
51
52
|
|
|
52
53
|
# Parse using Ox's SAX parser
|
|
53
|
-
xml_string = xml.
|
|
54
|
+
xml_string = xml.is_a?(IO) || xml.is_a?(StringIO) ? xml.read : xml.to_s
|
|
54
55
|
|
|
55
56
|
begin
|
|
56
57
|
::Ox.sax_parse(bridge, StringIO.new(xml_string))
|
|
@@ -77,6 +78,14 @@ module Moxml
|
|
|
77
78
|
content
|
|
78
79
|
end
|
|
79
80
|
|
|
81
|
+
def create_native_entity_reference(name)
|
|
82
|
+
::Moxml::Adapter::CustomizedOx::EntityReference.new(name)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def entity_reference_name(node)
|
|
86
|
+
node.name if node.is_a?(::Moxml::Adapter::CustomizedOx::EntityReference)
|
|
87
|
+
end
|
|
88
|
+
|
|
80
89
|
def create_native_cdata(content, _owner_doc = nil)
|
|
81
90
|
::Ox::CData.new(content)
|
|
82
91
|
end
|
|
@@ -121,11 +130,11 @@ module Moxml
|
|
|
121
130
|
end
|
|
122
131
|
|
|
123
132
|
def set_namespace(element, ns)
|
|
124
|
-
return unless element.
|
|
133
|
+
return unless element.is_a?(::Ox::Element) || element.is_a?(::Ox::Node)
|
|
125
134
|
|
|
126
135
|
prefix = ns.prefix
|
|
127
136
|
# attributes don't have attributes but can have a namespace prefix
|
|
128
|
-
if element.
|
|
137
|
+
if element.is_a?(::Ox::Element)
|
|
129
138
|
set_attribute(element, ns.expanded_prefix,
|
|
130
139
|
ns.uri)
|
|
131
140
|
end
|
|
@@ -136,8 +145,7 @@ module Moxml
|
|
|
136
145
|
|
|
137
146
|
def namespace(element)
|
|
138
147
|
prefix =
|
|
139
|
-
if element.
|
|
140
|
-
# attribute
|
|
148
|
+
if element.is_a?(::Moxml::Adapter::CustomizedOx::Attribute)
|
|
141
149
|
element.prefix
|
|
142
150
|
elsif element.name.include?(":")
|
|
143
151
|
element.name.split(":").first
|
|
@@ -145,7 +153,7 @@ module Moxml
|
|
|
145
153
|
attr_name = ["xmlns", prefix].compact.join(":")
|
|
146
154
|
|
|
147
155
|
([element] + ancestors(element)).each do |node|
|
|
148
|
-
next unless node.
|
|
156
|
+
next unless node.is_a?(::Ox::Element) && node.attributes
|
|
149
157
|
|
|
150
158
|
if node[attr_name]
|
|
151
159
|
return ::Moxml::Adapter::CustomizedOx::Namespace.new(
|
|
@@ -176,6 +184,7 @@ module Moxml
|
|
|
176
184
|
when ::Ox::Instruct then :processing_instruction
|
|
177
185
|
when ::Ox::Element then :element
|
|
178
186
|
when ::Ox::DocType then :doctype
|
|
187
|
+
when ::Moxml::Adapter::CustomizedOx::EntityReference then :entity_reference
|
|
179
188
|
when ::Moxml::Adapter::CustomizedOx::Namespace then :banespace
|
|
180
189
|
when ::Moxml::Adapter::CustomizedOx::Attribute then :attribute
|
|
181
190
|
else :unknown
|
|
@@ -194,10 +203,9 @@ module Moxml
|
|
|
194
203
|
end
|
|
195
204
|
|
|
196
205
|
def set_node_name(node, name)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
node.value = name
|
|
206
|
+
case node
|
|
207
|
+
when ::Ox::Element then node.name = name
|
|
208
|
+
when ::Ox::Instruct then node.value = name
|
|
201
209
|
end
|
|
202
210
|
end
|
|
203
211
|
|
|
@@ -219,7 +227,7 @@ module Moxml
|
|
|
219
227
|
else node
|
|
220
228
|
end
|
|
221
229
|
|
|
222
|
-
new_node.parent = parent if new_node.
|
|
230
|
+
new_node.parent = parent if new_node.is_a?(::Ox::Node)
|
|
223
231
|
|
|
224
232
|
new_node
|
|
225
233
|
end
|
|
@@ -231,18 +239,25 @@ module Moxml
|
|
|
231
239
|
node.value]
|
|
232
240
|
# when ::Moxml::Adapter::CustomizedOx::Attribute then { node.name => node.value }
|
|
233
241
|
when ::Moxml::Adapter::CustomizedOx::Text then node.value
|
|
242
|
+
when ::Moxml::Adapter::CustomizedOx::EntityReference then node
|
|
234
243
|
else node
|
|
235
244
|
end
|
|
236
245
|
end
|
|
237
246
|
|
|
238
247
|
def children(node)
|
|
239
|
-
return [] unless node.
|
|
248
|
+
return [] unless node.is_a?(::Ox::Element) || node.is_a?(::Ox::Document)
|
|
240
249
|
|
|
241
|
-
node.nodes || []
|
|
250
|
+
result = node.nodes || []
|
|
251
|
+
# Ox doesn't set parent references during parsing.
|
|
252
|
+
# Set them here so parent/sibling navigation works.
|
|
253
|
+
result.each do |child|
|
|
254
|
+
child.parent = node if child.respond_to?(:parent=)
|
|
255
|
+
end
|
|
256
|
+
result
|
|
242
257
|
end
|
|
243
258
|
|
|
244
259
|
def parent(node)
|
|
245
|
-
node.parent if node.
|
|
260
|
+
node.parent if node.is_a?(::Ox::Node)
|
|
246
261
|
end
|
|
247
262
|
|
|
248
263
|
def next_sibling(node)
|
|
@@ -272,9 +287,7 @@ module Moxml
|
|
|
272
287
|
end
|
|
273
288
|
|
|
274
289
|
def attributes(element)
|
|
275
|
-
unless element.
|
|
276
|
-
return []
|
|
277
|
-
end
|
|
290
|
+
return [] unless element.is_a?(::Ox::Element) && element.attributes
|
|
278
291
|
|
|
279
292
|
element.attributes.filter_map do |name, value|
|
|
280
293
|
next if name.to_s.start_with?("xmlns")
|
|
@@ -324,7 +337,7 @@ module Moxml
|
|
|
324
337
|
end
|
|
325
338
|
|
|
326
339
|
def get_attribute(element, name)
|
|
327
|
-
return unless element.
|
|
340
|
+
return unless element.is_a?(::Ox::HasAttrs) && element.attributes
|
|
328
341
|
unless element.attributes.key?(name.to_s) || element.attributes.key?(name.to_s.to_sym)
|
|
329
342
|
return
|
|
330
343
|
end
|
|
@@ -342,7 +355,7 @@ module Moxml
|
|
|
342
355
|
end
|
|
343
356
|
|
|
344
357
|
def remove_attribute(element, name)
|
|
345
|
-
return unless element.
|
|
358
|
+
return unless element.is_a?(::Ox::HasAttrs) && element.attributes
|
|
346
359
|
|
|
347
360
|
element.attributes.delete(name.to_s)
|
|
348
361
|
element.attributes.delete(name.to_s.to_sym)
|
|
@@ -367,15 +380,24 @@ module Moxml
|
|
|
367
380
|
end
|
|
368
381
|
end
|
|
369
382
|
|
|
370
|
-
child.parent = element if child.
|
|
383
|
+
child.parent = element if child.is_a?(::Ox::Node)
|
|
371
384
|
element.nodes ||= []
|
|
372
385
|
element.nodes << child
|
|
386
|
+
|
|
387
|
+
# Mark document if EntityReference is added (avoids tree scan in serialize)
|
|
388
|
+
if child.is_a?(::Moxml::Adapter::CustomizedOx::EntityReference)
|
|
389
|
+
root = element
|
|
390
|
+
while root.is_a?(::Ox::Node) && root.parent
|
|
391
|
+
root = root.parent
|
|
392
|
+
end
|
|
393
|
+
root&.instance_variable_set(:@moxml_entity_refs, true)
|
|
394
|
+
end
|
|
373
395
|
end
|
|
374
396
|
|
|
375
397
|
def add_previous_sibling(node, sibling)
|
|
376
398
|
return unless (parent = parent(node))
|
|
377
399
|
|
|
378
|
-
if sibling.
|
|
400
|
+
if sibling.is_a?(::Ox::Node)
|
|
379
401
|
sibling.parent&.nodes&.delete(sibling)
|
|
380
402
|
sibling.parent = parent
|
|
381
403
|
end
|
|
@@ -386,7 +408,7 @@ module Moxml
|
|
|
386
408
|
def add_next_sibling(node, sibling)
|
|
387
409
|
return unless (parent = parent(node))
|
|
388
410
|
|
|
389
|
-
if sibling.
|
|
411
|
+
if sibling.is_a?(::Ox::Node)
|
|
390
412
|
sibling.parent&.nodes&.delete(sibling)
|
|
391
413
|
sibling.parent = parent
|
|
392
414
|
end
|
|
@@ -421,7 +443,7 @@ module Moxml
|
|
|
421
443
|
|
|
422
444
|
return unless (parent = parent(node))
|
|
423
445
|
|
|
424
|
-
new_node.parent = parent if new_node.
|
|
446
|
+
new_node.parent = parent if new_node.is_a?(::Ox::Node)
|
|
425
447
|
idx = parent.nodes.index(node)
|
|
426
448
|
parent.nodes[idx] = new_node if idx
|
|
427
449
|
end
|
|
@@ -429,20 +451,40 @@ module Moxml
|
|
|
429
451
|
def replace_children(node, new_children)
|
|
430
452
|
node.remove_children_by_path("*")
|
|
431
453
|
new_children.each do |child|
|
|
432
|
-
child.parent = node if child.
|
|
454
|
+
child.parent = node if child.is_a?(::Ox::Node)
|
|
433
455
|
node << child
|
|
434
456
|
end
|
|
435
457
|
node
|
|
436
458
|
end
|
|
437
459
|
|
|
460
|
+
def assign_parents(node, parent = nil)
|
|
461
|
+
node.parent = parent if node.respond_to?(:parent=) && parent
|
|
462
|
+
return unless node.respond_to?(:nodes)
|
|
463
|
+
|
|
464
|
+
node.nodes&.each do |child|
|
|
465
|
+
assign_parents(child, node)
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def validate_single_root(document)
|
|
470
|
+
elements = document.nodes&.grep(::Ox::Element) || []
|
|
471
|
+
return unless elements.size > 1
|
|
472
|
+
|
|
473
|
+
raise Moxml::ParseError.new(
|
|
474
|
+
"Multiple root elements found",
|
|
475
|
+
source: nil,
|
|
476
|
+
)
|
|
477
|
+
end
|
|
478
|
+
|
|
438
479
|
def text_content(node)
|
|
439
480
|
return "" if node.nil?
|
|
440
481
|
|
|
441
482
|
case node
|
|
442
483
|
when String then node.to_s
|
|
443
484
|
when ::Moxml::Adapter::CustomizedOx::Text then node.value
|
|
485
|
+
when ::Moxml::Adapter::CustomizedOx::EntityReference then ""
|
|
444
486
|
else
|
|
445
|
-
return "" unless node.
|
|
487
|
+
return "" unless node.is_a?(::Ox::Element) || node.is_a?(::Ox::Document)
|
|
446
488
|
|
|
447
489
|
node.nodes.map do |n|
|
|
448
490
|
text_content(n)
|
|
@@ -451,7 +493,7 @@ module Moxml
|
|
|
451
493
|
end
|
|
452
494
|
|
|
453
495
|
def inner_text(node)
|
|
454
|
-
return "" unless node.
|
|
496
|
+
return "" unless node.is_a?(::Ox::Element) || node.is_a?(::Ox::Document)
|
|
455
497
|
|
|
456
498
|
node.nodes.grep(String).join
|
|
457
499
|
end
|
|
@@ -499,7 +541,7 @@ module Moxml
|
|
|
499
541
|
|
|
500
542
|
def namespace_definitions(node)
|
|
501
543
|
([node] + ancestors(node)).reverse.each_with_object({}) do |n, namespaces|
|
|
502
|
-
next unless n.
|
|
544
|
+
next unless n.is_a?(::Ox::Element) && n.attributes
|
|
503
545
|
|
|
504
546
|
n.attributes.each do |name, value|
|
|
505
547
|
next unless name.to_s.start_with?("xmlns")
|
|
@@ -575,18 +617,30 @@ module Moxml
|
|
|
575
617
|
end
|
|
576
618
|
|
|
577
619
|
def serialize(node, options = {})
|
|
620
|
+
# Fast path: skip EntityReference scan for documents (most common case)
|
|
621
|
+
if node.is_a?(::Ox::Document) &&
|
|
622
|
+
!node.instance_variable_get(:@moxml_entity_refs)
|
|
623
|
+
return serialize_standard(node, options)
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
if tree_has_entity_references?(node)
|
|
627
|
+
serialize_custom(node, options)
|
|
628
|
+
else
|
|
629
|
+
serialize_standard(node, options)
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
private
|
|
634
|
+
|
|
635
|
+
def serialize_standard(node, options = {})
|
|
578
636
|
output = ""
|
|
579
637
|
if node.is_a?(::Ox::Document)
|
|
580
|
-
# Check if we should include declaration
|
|
581
|
-
# Priority: explicit option > document attributes
|
|
582
638
|
should_include_decl = if options.key?(:no_declaration)
|
|
583
639
|
!options[:no_declaration]
|
|
584
640
|
else
|
|
585
|
-
# Check if document has declaration attributes
|
|
586
641
|
node[:version] || node[:encoding] || node[:standalone]
|
|
587
642
|
end
|
|
588
643
|
|
|
589
|
-
# Only add declaration if should_include_decl is true
|
|
590
644
|
if should_include_decl
|
|
591
645
|
version = node[:version] || "1.0"
|
|
592
646
|
encoding = options[:encoding] || node[:encoding]
|
|
@@ -598,8 +652,7 @@ module Moxml
|
|
|
598
652
|
end
|
|
599
653
|
|
|
600
654
|
ox_options = {
|
|
601
|
-
indent: -1,
|
|
602
|
-
# with_xml: true,
|
|
655
|
+
indent: -1,
|
|
603
656
|
with_instructions: true,
|
|
604
657
|
encoding: options[:encoding],
|
|
605
658
|
no_empty: options[:expand_empty],
|
|
@@ -607,7 +660,102 @@ module Moxml
|
|
|
607
660
|
output + ::Ox.dump(node, ox_options)
|
|
608
661
|
end
|
|
609
662
|
|
|
610
|
-
|
|
663
|
+
def tree_has_entity_references?(node)
|
|
664
|
+
case node
|
|
665
|
+
when ::Moxml::Adapter::CustomizedOx::EntityReference
|
|
666
|
+
true
|
|
667
|
+
when ::Ox::Element
|
|
668
|
+
node.nodes&.any? do |child|
|
|
669
|
+
tree_has_entity_references?(child)
|
|
670
|
+
end || false
|
|
671
|
+
when ::Ox::Document
|
|
672
|
+
node.nodes&.any? do |child|
|
|
673
|
+
tree_has_entity_references?(child)
|
|
674
|
+
end || false
|
|
675
|
+
else
|
|
676
|
+
false
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def serialize_custom(node, options = {})
|
|
681
|
+
output = +""
|
|
682
|
+
if node.is_a?(::Ox::Document)
|
|
683
|
+
should_include_decl = if options.key?(:no_declaration)
|
|
684
|
+
!options[:no_declaration]
|
|
685
|
+
else
|
|
686
|
+
node[:version] || node[:encoding] || node[:standalone]
|
|
687
|
+
end
|
|
688
|
+
if should_include_decl
|
|
689
|
+
version = node[:version] || "1.0"
|
|
690
|
+
encoding = options[:encoding] || node[:encoding]
|
|
691
|
+
standalone = node[:standalone]
|
|
692
|
+
output << "<?xml version=\"#{version}\""
|
|
693
|
+
output << " encoding=\"#{encoding}\"" if encoding
|
|
694
|
+
output << " standalone=\"#{standalone}\"" if standalone
|
|
695
|
+
output << "?>"
|
|
696
|
+
end
|
|
697
|
+
(node.nodes || []).each do |child|
|
|
698
|
+
output << serialize_node_custom(child)
|
|
699
|
+
end
|
|
700
|
+
else
|
|
701
|
+
output << serialize_node_custom(node)
|
|
702
|
+
end
|
|
703
|
+
output
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def serialize_node_custom(node)
|
|
707
|
+
case node
|
|
708
|
+
when ::Ox::Element then serialize_element_custom(node)
|
|
709
|
+
when String then escape_xml_text(node)
|
|
710
|
+
when ::Moxml::Adapter::CustomizedOx::Text then escape_xml_text(node.value)
|
|
711
|
+
when ::Moxml::Adapter::CustomizedOx::EntityReference then "&#{node.name};"
|
|
712
|
+
when ::Ox::CData then "<![CDATA[#{node.value}]]>"
|
|
713
|
+
when ::Ox::Comment then "<!--#{node.value}-->"
|
|
714
|
+
when ::Ox::Instruct then "<?#{node.target} #{node.value || ''}?>"
|
|
715
|
+
when ::Ox::DocType then "<!DOCTYPE #{node.value}>"
|
|
716
|
+
else ""
|
|
717
|
+
end
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
def serialize_element_custom(elem)
|
|
721
|
+
output = "<#{elem.name}"
|
|
722
|
+
elem.attributes.each do |name, value|
|
|
723
|
+
output << " #{name}=\"#{escape_xml_attribute(value.to_s)}\""
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
if elem.nodes.nil? || elem.nodes.empty?
|
|
727
|
+
output << "/>"
|
|
728
|
+
return output
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
output << ">"
|
|
732
|
+
elem.nodes.each do |child|
|
|
733
|
+
output << serialize_node_custom(child)
|
|
734
|
+
end
|
|
735
|
+
output << "</#{elem.name}>"
|
|
736
|
+
output
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
def escape_xml_text(text)
|
|
740
|
+
text.to_s.gsub(/[<>&]/) do |match|
|
|
741
|
+
case match
|
|
742
|
+
when "<" then "<"
|
|
743
|
+
when ">" then ">"
|
|
744
|
+
when "&" then "&"
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
def escape_xml_attribute(value)
|
|
750
|
+
value.to_s.gsub(/[<>&"]/) do |match|
|
|
751
|
+
case match
|
|
752
|
+
when "<" then "<"
|
|
753
|
+
when ">" then ">"
|
|
754
|
+
when "&" then "&"
|
|
755
|
+
when '"' then """
|
|
756
|
+
end
|
|
757
|
+
end
|
|
758
|
+
end
|
|
611
759
|
|
|
612
760
|
# Translate a subset of XPath to Ox locate() syntax
|
|
613
761
|
# Supports: //element, /path/to/element, .//element, element[@attr]
|
|
@@ -659,7 +807,7 @@ module Moxml
|
|
|
659
807
|
|
|
660
808
|
result = nil
|
|
661
809
|
traverse(root) do |node|
|
|
662
|
-
next unless node.
|
|
810
|
+
next unless node.is_a?(::Ox::Element) || node.is_a?(::Ox::Document)
|
|
663
811
|
|
|
664
812
|
node.nodes&.each do |child|
|
|
665
813
|
if child.equal?(target_node)
|
|
@@ -676,7 +824,7 @@ module Moxml
|
|
|
676
824
|
return unless node
|
|
677
825
|
|
|
678
826
|
yield node
|
|
679
|
-
return unless node.
|
|
827
|
+
return unless node.is_a?(::Ox::Element) || node.is_a?(::Ox::Document)
|
|
680
828
|
|
|
681
829
|
node.nodes&.each { |child| traverse(child, &block) }
|
|
682
830
|
end
|
data/lib/moxml/adapter/rexml.rb
CHANGED
|
@@ -4,7 +4,7 @@ require_relative "base"
|
|
|
4
4
|
require "rexml/document"
|
|
5
5
|
require "rexml/xpath"
|
|
6
6
|
require "set"
|
|
7
|
-
require_relative "customized_rexml
|
|
7
|
+
require_relative "customized_rexml"
|
|
8
8
|
|
|
9
9
|
module Moxml
|
|
10
10
|
module Adapter
|
|
@@ -54,7 +54,7 @@ module Moxml
|
|
|
54
54
|
|
|
55
55
|
bridge = REXMLSAX2Bridge.new(handler)
|
|
56
56
|
|
|
57
|
-
xml_string = xml.
|
|
57
|
+
xml_string = xml.is_a?(IO) || xml.is_a?(StringIO) ? xml.read : xml.to_s
|
|
58
58
|
source = ::REXML::IOSource.new(StringIO.new(xml_string))
|
|
59
59
|
|
|
60
60
|
parser = ::REXML::Parsers::SAX2Parser.new(source)
|
|
@@ -77,6 +77,14 @@ module Moxml
|
|
|
77
77
|
::REXML::Text.new(content.to_s, true, nil)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
def create_native_entity_reference(name)
|
|
81
|
+
::Moxml::Adapter::CustomizedRexml::EntityReference.new(name)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def entity_reference_name(node)
|
|
85
|
+
node.name if node.is_a?(::Moxml::Adapter::CustomizedRexml::EntityReference)
|
|
86
|
+
end
|
|
87
|
+
|
|
80
88
|
def create_native_cdata(content, _owner_doc = nil)
|
|
81
89
|
::REXML::CData.new(content.to_s)
|
|
82
90
|
end
|
|
@@ -124,6 +132,7 @@ module Moxml
|
|
|
124
132
|
when ::REXML::Instruction then :processing_instruction
|
|
125
133
|
when ::REXML::DocType then :doctype
|
|
126
134
|
when ::REXML::XMLDecl then :declaration
|
|
135
|
+
when ::Moxml::Adapter::CustomizedRexml::EntityReference then :entity_reference
|
|
127
136
|
else :unknown
|
|
128
137
|
end
|
|
129
138
|
end
|
|
@@ -149,13 +158,15 @@ module Moxml
|
|
|
149
158
|
end
|
|
150
159
|
|
|
151
160
|
def duplicate_node(node)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
if node.respond_to?(:deep_clone)
|
|
162
|
+
node.deep_clone
|
|
163
|
+
else
|
|
164
|
+
Marshal.load(Marshal.dump(node))
|
|
165
|
+
end
|
|
155
166
|
end
|
|
156
167
|
|
|
157
168
|
def children(node)
|
|
158
|
-
return [] unless node.
|
|
169
|
+
return [] unless node.is_a?(::REXML::Parent)
|
|
159
170
|
|
|
160
171
|
# Get all children and filter out empty text nodes between elements
|
|
161
172
|
result = node.children.reject do |child|
|
|
@@ -229,7 +240,7 @@ module Moxml
|
|
|
229
240
|
end
|
|
230
241
|
|
|
231
242
|
def attributes(element)
|
|
232
|
-
return [] unless element.
|
|
243
|
+
return [] unless element.is_a?(::REXML::Element)
|
|
233
244
|
|
|
234
245
|
# Only return non-namespace attributes
|
|
235
246
|
element.attributes.values
|
|
@@ -280,6 +291,12 @@ module Moxml
|
|
|
280
291
|
case child
|
|
281
292
|
when String
|
|
282
293
|
element.add_text(child)
|
|
294
|
+
when ::Moxml::Adapter::CustomizedRexml::EntityReference
|
|
295
|
+
# REXML doesn't support custom node types in its tree.
|
|
296
|
+
# Store alongside native children via instance variable.
|
|
297
|
+
refs = element.instance_variable_get(:@moxml_entity_refs) || []
|
|
298
|
+
refs << child
|
|
299
|
+
element.instance_variable_set(:@moxml_entity_refs, refs)
|
|
283
300
|
else
|
|
284
301
|
element.add(child)
|
|
285
302
|
end
|
|
@@ -373,6 +390,8 @@ module Moxml
|
|
|
373
390
|
case node
|
|
374
391
|
when ::REXML::Text, ::REXML::CData
|
|
375
392
|
node.value.to_s
|
|
393
|
+
when ::Moxml::Adapter::CustomizedRexml::EntityReference
|
|
394
|
+
""
|
|
376
395
|
when ::REXML::Element
|
|
377
396
|
# Extract text recursively from all children to match other adapters
|
|
378
397
|
extract_text_recursively(node)
|
|
@@ -428,7 +447,7 @@ module Moxml
|
|
|
428
447
|
# add a namespace prefix to the element name AND a namespace definition
|
|
429
448
|
def set_namespace(element, ns)
|
|
430
449
|
prefix = ns.name.to_s.empty? ? "xmlns" : ns.name.to_s
|
|
431
|
-
if element.
|
|
450
|
+
if element.is_a?(::REXML::Element)
|
|
432
451
|
element.add_namespace(prefix,
|
|
433
452
|
ns.value)
|
|
434
453
|
end
|
data/lib/moxml/attribute.rb
CHANGED
data/lib/moxml/builder.rb
CHANGED
data/lib/moxml/config.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Moxml
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
NAMESPACE_VALIDATION_MODES = %i[strict lenient].freeze
|
|
28
28
|
|
|
29
29
|
attr_reader :adapter_name
|
|
30
30
|
attr_accessor :strict_parsing,
|
|
@@ -35,7 +35,7 @@ module Moxml
|
|
|
35
35
|
:preload_entity_sets,
|
|
36
36
|
:entity_load_mode,
|
|
37
37
|
:entity_provider,
|
|
38
|
-
:
|
|
38
|
+
:namespace_validation_mode
|
|
39
39
|
|
|
40
40
|
def initialize(adapter_name = nil, strict_parsing = nil,
|
|
41
41
|
default_encoding = nil)
|
|
@@ -49,7 +49,7 @@ module Moxml
|
|
|
49
49
|
@preload_entity_sets = []
|
|
50
50
|
@entity_load_mode = :required
|
|
51
51
|
@entity_provider = nil
|
|
52
|
-
@
|
|
52
|
+
@namespace_validation_mode = :strict
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def adapter=(name)
|
|
@@ -86,14 +86,14 @@ module Moxml
|
|
|
86
86
|
@entity_load_mode = mode
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
def
|
|
89
|
+
def namespace_validation_mode=(mode)
|
|
90
90
|
mode = mode.to_sym
|
|
91
|
-
unless
|
|
91
|
+
unless NAMESPACE_VALIDATION_MODES.include?(mode)
|
|
92
92
|
raise ArgumentError,
|
|
93
|
-
"Invalid
|
|
93
|
+
"Invalid namespace_validation_mode: #{mode}. Must be one of: #{NAMESPACE_VALIDATION_MODES.join(', ')}"
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
@
|
|
96
|
+
@namespace_validation_mode = mode
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
# Backward compatibility: convert old boolean to new symbol
|
data/lib/moxml/document.rb
CHANGED
|
@@ -26,11 +26,13 @@ module Moxml
|
|
|
26
26
|
|
|
27
27
|
def root=(element)
|
|
28
28
|
adapter.set_root(@native, element.native)
|
|
29
|
+
element.instance_variable_set(:@parent_node, self)
|
|
30
|
+
invalidate_children_cache!
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def root
|
|
32
34
|
root_element = adapter.root(@native)
|
|
33
|
-
root_element ? Element.
|
|
35
|
+
root_element ? Element.new(root_element, context) : nil
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def create_element(name)
|
|
@@ -91,6 +93,8 @@ module Moxml
|
|
|
91
93
|
else
|
|
92
94
|
adapter.add_child(@native, node.native)
|
|
93
95
|
end
|
|
96
|
+
node.instance_variable_set(:@parent_node, self)
|
|
97
|
+
invalidate_children_cache!
|
|
94
98
|
self
|
|
95
99
|
end
|
|
96
100
|
|