moxml 0.1.26 → 0.2.0
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/.github/workflows/windows.yml +62 -0
- data/Gemfile +3 -1
- data/README.adoc +69 -6
- data/lib/compat/opal/moxml_boot.rb +5 -0
- data/lib/moxml/adapter/base.rb +53 -49
- data/lib/moxml/adapter/customized_leptris/declaration.rb +32 -0
- data/lib/moxml/adapter/customized_leptris/doctype.rb +31 -0
- data/lib/moxml/adapter/customized_leptris/entity_reference.rb +12 -0
- data/lib/moxml/adapter/customized_leptris/text_segment.rb +26 -0
- data/lib/moxml/adapter/customized_leptris.rb +16 -0
- data/lib/moxml/adapter/customized_libxml/cdata.rb +3 -12
- data/lib/moxml/adapter/customized_libxml/comment.rb +2 -9
- data/lib/moxml/adapter/customized_libxml/declaration.rb +1 -10
- data/lib/moxml/adapter/customized_libxml/node.rb +8 -0
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +2 -10
- data/lib/moxml/adapter/customized_libxml/text.rb +2 -9
- data/lib/moxml/adapter/customized_oga/entity_decoder.rb +37 -0
- data/lib/moxml/adapter/customized_oga/raw_value_override.rb +52 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +4 -34
- data/lib/moxml/adapter/customized_oga.rb +2 -0
- data/lib/moxml/adapter/customized_ox/entity_reference.rb +1 -17
- data/lib/moxml/adapter/customized_rexml/entity_reference.rb +1 -11
- data/lib/moxml/adapter/headed_ox.rb +9 -128
- data/lib/moxml/adapter/leptris.rb +965 -0
- data/lib/moxml/adapter/libxml.rb +131 -99
- data/lib/moxml/adapter/nokogiri.rb +29 -8
- data/lib/moxml/adapter/oga.rb +91 -54
- data/lib/moxml/adapter/ox.rb +93 -137
- data/lib/moxml/adapter/rexml.rb +61 -50
- data/lib/moxml/adapter.rb +2 -1
- data/lib/moxml/attribute.rb +8 -4
- data/lib/moxml/attribute_resolver.rb +189 -0
- data/lib/moxml/config.rb +29 -3
- data/lib/moxml/context.rb +12 -0
- data/lib/moxml/element.rb +29 -12
- data/lib/moxml/entity/reference.rb +28 -0
- data/lib/moxml/entity.rb +125 -0
- data/lib/moxml/node.rb +90 -1
- data/lib/moxml/sax/namespace_splitter.rb +5 -2
- data/lib/moxml/signature/model/key/dsa_key_value.rb +1 -2
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_emitter.rb +71 -0
- data/lib/moxml/xpath/compiler.rb +60 -7
- data/lib/moxml/xpath/parser.rb +22 -15
- data/lib/moxml.rb +3 -0
- data/spec/examples/readme_examples_spec.rb +0 -4
- data/spec/examples/xpath_examples_spec.rb +0 -11
- data/spec/integration/all_adapters_spec.rb +17 -0
- data/spec/integration/sax_parity_spec.rb +58 -0
- data/spec/integration/shared_examples/edge_cases.rb +0 -10
- data/spec/integration/shared_examples/integration_workflows.rb +0 -10
- data/spec/integration/shared_examples/node_wrappers/attribute_behavior.rb +98 -0
- data/spec/integration/shared_examples/node_wrappers/namespace_behavior.rb +26 -0
- data/spec/integration/shared_examples/node_wrappers/node_behavior.rb +0 -8
- data/spec/moxml/adapter/leptris_spec.rb +75 -0
- data/spec/moxml/adapter/ox_spec.rb +20 -6
- data/spec/moxml/adapter/platform_spec.rb +15 -2
- data/spec/moxml/adapter/shared_examples/adapter_contract.rb +180 -0
- data/spec/moxml/attribute_resolver_spec.rb +108 -0
- data/spec/moxml/doctype_spec.rb +1 -1
- data/spec/moxml/entity_spec.rb +76 -0
- data/spec/moxml/node_spec.rb +104 -0
- data/spec/moxml/sax_entity_parity_spec.rb +358 -0
- data/spec/moxml/xml_emitter_spec.rb +64 -0
- data/spec/moxml/xpath/axes_spec.rb +72 -0
- data/spec/moxml/xpath/parser_spec.rb +6 -0
- data/spec/moxml/xpath_capabilities_spec.rb +5 -3
- data/spec/performance/benchmark_spec.rb +13 -6
- data/spec/performance/thread_safety_spec.rb +0 -4
- data/spec/spec_helper.rb +5 -1
- metadata +21 -2
data/lib/moxml/adapter/libxml.rb
CHANGED
|
@@ -28,15 +28,7 @@ module Moxml
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def to_xml
|
|
31
|
-
|
|
32
|
-
if @external_id && !@external_id.empty?
|
|
33
|
-
output << " PUBLIC \"#{@external_id}\""
|
|
34
|
-
output << " \"#{@system_id}\"" if @system_id
|
|
35
|
-
elsif @system_id && !@system_id.empty?
|
|
36
|
-
output << " SYSTEM \"#{@system_id}\""
|
|
37
|
-
end
|
|
38
|
-
output << ">"
|
|
39
|
-
output
|
|
31
|
+
XmlEmitter.doctype_xml(@name, @external_id, @system_id)
|
|
40
32
|
end
|
|
41
33
|
end
|
|
42
34
|
|
|
@@ -57,6 +49,7 @@ module Moxml
|
|
|
57
49
|
|
|
58
50
|
WRAPPER_NODE_TYPE_MAP = {
|
|
59
51
|
DoctypeWrapper => :doctype,
|
|
52
|
+
CustomizedLibxml::Declaration => :declaration,
|
|
60
53
|
CustomizedLibxml::Element => :element,
|
|
61
54
|
CustomizedLibxml::Text => :text,
|
|
62
55
|
CustomizedLibxml::Cdata => :cdata,
|
|
@@ -91,7 +84,7 @@ module Moxml
|
|
|
91
84
|
xml_string = preprocess_entities(xml_string)
|
|
92
85
|
|
|
93
86
|
# Extract DOCTYPE before parsing
|
|
94
|
-
doctype_match = xml_string.match(/<!DOCTYPE\s+(
|
|
87
|
+
doctype_match = xml_string.match(/<!DOCTYPE\s+([^\s>]+)(?:\s+PUBLIC\s+"([^"]+)"\s+"([^"]+)"|\s+SYSTEM\s+"([^"]+)")?\s*>/i)
|
|
95
88
|
|
|
96
89
|
native_doc = begin
|
|
97
90
|
# Handle both string and file inputs
|
|
@@ -379,6 +372,11 @@ module Moxml
|
|
|
379
372
|
native_doc&.root
|
|
380
373
|
end
|
|
381
374
|
|
|
375
|
+
def line_number(node)
|
|
376
|
+
native = unpatch_node(node)
|
|
377
|
+
native.is_a?(::LibXML::XML::Node) ? native.line_num : nil
|
|
378
|
+
end
|
|
379
|
+
|
|
382
380
|
def attributes(element)
|
|
383
381
|
native_elem = unpatch_node(element)
|
|
384
382
|
return [] unless native_elem
|
|
@@ -437,9 +435,13 @@ module Moxml
|
|
|
437
435
|
native_elem.attributes.get_attribute(name_str)
|
|
438
436
|
end
|
|
439
437
|
else
|
|
440
|
-
#
|
|
441
|
-
|
|
442
|
-
|
|
438
|
+
# node[]= is ambiguous when a namespace-bound attribute
|
|
439
|
+
# shares the local name (it clobbers that attribute);
|
|
440
|
+
# create or replace the no-namespace attribute explicitly.
|
|
441
|
+
native_elem.each_attr do |attr|
|
|
442
|
+
attr.remove! if !attr.ns && attr.name == name_str
|
|
443
|
+
end if native_elem.attributes?
|
|
444
|
+
::LibXML::XML::Attr.new(native_elem, name_str, value_str)
|
|
443
445
|
end
|
|
444
446
|
end
|
|
445
447
|
|
|
@@ -467,30 +469,46 @@ module Moxml
|
|
|
467
469
|
native_elem = unpatch_node(element)
|
|
468
470
|
return nil unless native_elem
|
|
469
471
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
# If name contains ':', try to get as namespaced attribute
|
|
475
|
-
if name.to_s.include?(":")
|
|
476
|
-
prefix, local_name = name.to_s.split(":", 2)
|
|
472
|
+
name_s = name.to_s
|
|
473
|
+
if name_s.include?(":")
|
|
474
|
+
prefix, local_name = name_s.split(":", 2)
|
|
477
475
|
# Try to find attribute by namespace
|
|
478
476
|
if native_elem.attributes?
|
|
479
477
|
native_elem.each_attr do |attr|
|
|
480
|
-
if
|
|
478
|
+
if [local_name, name_s].include?(attr.name)
|
|
481
479
|
# Check if attribute's namespace matches the prefix
|
|
482
480
|
if attr.ns && attr.ns.prefix == prefix
|
|
483
481
|
return attr.value
|
|
484
|
-
elsif attr.name ==
|
|
482
|
+
elsif attr.name == name_s
|
|
485
483
|
# Fallback: attribute name includes the prefix
|
|
486
484
|
return attr.value
|
|
487
485
|
end
|
|
488
486
|
end
|
|
489
487
|
end
|
|
490
488
|
end
|
|
489
|
+
|
|
490
|
+
return nil
|
|
491
491
|
end
|
|
492
492
|
|
|
493
|
-
|
|
493
|
+
# Bare name: node[name_s] is ambiguous when a namespaced
|
|
494
|
+
# attribute shares the local name (issue #94), so prefer the
|
|
495
|
+
# namespace-less attribute explicitly.
|
|
496
|
+
if native_elem.attributes?
|
|
497
|
+
namespaced_value = nil
|
|
498
|
+
native_elem.each_attr do |attr|
|
|
499
|
+
next if attr.name.to_s.start_with?("xmlns")
|
|
500
|
+
next unless attr.name == name_s
|
|
501
|
+
|
|
502
|
+
if attr.ns.nil?
|
|
503
|
+
return attr.value
|
|
504
|
+
elsif namespaced_value.nil?
|
|
505
|
+
namespaced_value = attr.value
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
return namespaced_value unless namespaced_value.nil?
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
native_elem[name_s]
|
|
494
512
|
end
|
|
495
513
|
|
|
496
514
|
def remove_attribute(element, name)
|
|
@@ -502,6 +520,10 @@ module Moxml
|
|
|
502
520
|
attr&.remove!
|
|
503
521
|
end
|
|
504
522
|
|
|
523
|
+
def remove_attribute_native(attr)
|
|
524
|
+
attr&.remove!
|
|
525
|
+
end
|
|
526
|
+
|
|
505
527
|
def set_attribute_name(attribute, new_name)
|
|
506
528
|
# LibXML attributes cannot be renamed directly
|
|
507
529
|
# We must work at the element level
|
|
@@ -730,26 +752,52 @@ module Moxml
|
|
|
730
752
|
|
|
731
753
|
def set_text_content(node, content)
|
|
732
754
|
native_node = unpatch_node(node)
|
|
733
|
-
|
|
755
|
+
return unless native_node.is_a?(::LibXML::XML::Node)
|
|
756
|
+
|
|
757
|
+
# Text wrapper: swap with new_text to preserve verbatim storage.
|
|
758
|
+
# Element wrapper: let libxml manage the child text node.
|
|
759
|
+
if native_node.text?
|
|
760
|
+
replace_native_verbatim(node, ::LibXML::XML::Node.new_text(content.to_s))
|
|
761
|
+
else
|
|
762
|
+
native_node.content = content.to_s
|
|
763
|
+
end
|
|
734
764
|
end
|
|
735
765
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
766
|
+
# Replace `old_native` with `fresh`, preserving position. Both
|
|
767
|
+
# neighbor pointers are set explicitly to match `replace`'s
|
|
768
|
+
# semantics (see #replace above).
|
|
769
|
+
def swap_native_in_place(old_native, fresh)
|
|
770
|
+
parent = old_native.parent
|
|
771
|
+
return if parent.nil?
|
|
741
772
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
773
|
+
prev_sibling = old_native.prev
|
|
774
|
+
next_sibling = old_native.next
|
|
775
|
+
old_native.remove!
|
|
776
|
+
prev_sibling.next = fresh if prev_sibling
|
|
777
|
+
next_sibling.prev = fresh if next_sibling
|
|
778
|
+
parent << fresh unless prev_sibling || next_sibling
|
|
779
|
+
end
|
|
780
|
+
private :swap_native_in_place
|
|
781
|
+
|
|
782
|
+
# libxml-ruby's node.content= pre-escapes; building a fresh node via
|
|
783
|
+
# new_<kind> stores content verbatim. Swap in place and re-point the
|
|
784
|
+
# wrapper.
|
|
785
|
+
def replace_native_verbatim(node, fresh)
|
|
786
|
+
native = unpatch_node(node)
|
|
787
|
+
return unless native.is_a?(::LibXML::XML::Node)
|
|
788
|
+
|
|
789
|
+
swap_native_in_place(native, fresh)
|
|
790
|
+
node.replace_native!(fresh) if node.is_a?(CustomizedLibxml::Node)
|
|
791
|
+
end
|
|
792
|
+
private :replace_native_verbatim
|
|
793
|
+
|
|
794
|
+
def cdata_content(node)
|
|
795
|
+
# libxml stores CDATA payload verbatim; no decoding needed.
|
|
796
|
+
unpatch_node(node)&.content
|
|
747
797
|
end
|
|
748
798
|
|
|
749
799
|
def set_cdata_content(node, content)
|
|
750
|
-
|
|
751
|
-
# CDATA content should NOT be escaped
|
|
752
|
-
native_node.content = content.to_s if native_node
|
|
800
|
+
replace_native_verbatim(node, ::LibXML::XML::Node.new_cdata(content.to_s))
|
|
753
801
|
end
|
|
754
802
|
|
|
755
803
|
def comment_content(node)
|
|
@@ -758,8 +806,7 @@ module Moxml
|
|
|
758
806
|
end
|
|
759
807
|
|
|
760
808
|
def set_comment_content(node, content)
|
|
761
|
-
|
|
762
|
-
native_node.content = content.to_s if native_node
|
|
809
|
+
replace_native_verbatim(node, ::LibXML::XML::Node.new_comment(content.to_s))
|
|
763
810
|
end
|
|
764
811
|
|
|
765
812
|
def processing_instruction_target(node)
|
|
@@ -768,22 +815,18 @@ module Moxml
|
|
|
768
815
|
end
|
|
769
816
|
|
|
770
817
|
def processing_instruction_content(node)
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
# LibXML may HTML-escape the content, un-escape it
|
|
774
|
-
return nil unless content
|
|
775
|
-
|
|
776
|
-
content.gsub(""", '"')
|
|
777
|
-
.gsub("'", "'")
|
|
778
|
-
.gsub("<", "<")
|
|
779
|
-
.gsub(">", ">")
|
|
780
|
-
.gsub("&", "&")
|
|
818
|
+
# XML 1.0 §2.6: PI content is verbatim — no entity resolution.
|
|
819
|
+
unpatch_node(node)&.content
|
|
781
820
|
end
|
|
782
821
|
|
|
783
822
|
def set_processing_instruction_content(node, content)
|
|
784
823
|
native_node = unpatch_node(node)
|
|
785
|
-
|
|
786
|
-
|
|
824
|
+
return unless native_node.is_a?(::LibXML::XML::Node)
|
|
825
|
+
|
|
826
|
+
replace_native_verbatim(
|
|
827
|
+
node,
|
|
828
|
+
::LibXML::XML::Node.new_pi(native_node.name, content.to_s),
|
|
829
|
+
)
|
|
787
830
|
end
|
|
788
831
|
|
|
789
832
|
def create_native_namespace(element, prefix, uri)
|
|
@@ -804,9 +847,10 @@ module Moxml
|
|
|
804
847
|
|
|
805
848
|
def set_namespace(element, ns)
|
|
806
849
|
native_elem = unpatch_node(element)
|
|
807
|
-
return unless native_elem
|
|
850
|
+
return element unless native_elem
|
|
808
851
|
|
|
809
|
-
native_elem.namespaces.namespace = ns
|
|
852
|
+
native_elem.namespaces.namespace = ns if ns
|
|
853
|
+
element
|
|
810
854
|
end
|
|
811
855
|
|
|
812
856
|
def namespace(element)
|
|
@@ -867,14 +911,18 @@ module Moxml
|
|
|
867
911
|
# LibXML requires ALL prefixes in the XPath to be registered
|
|
868
912
|
ns_context = build_xpath_namespaces(native_node, namespaces)
|
|
869
913
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
914
|
+
found = if ns_context.empty?
|
|
915
|
+
native_node.find(expression)
|
|
916
|
+
else
|
|
917
|
+
native_node.find(expression, ns_context)
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
# find returns an XPath::Object for node results, scalars for
|
|
921
|
+
# count()/string()/boolean functions (adapter contract)
|
|
922
|
+
return found if found.is_a?(Float) || found.is_a?(String) ||
|
|
923
|
+
[true, false].include?(found)
|
|
875
924
|
|
|
876
|
-
|
|
877
|
-
results.map { |n| patch_node(n) }
|
|
925
|
+
found.to_a.map { |n| patch_node(n) }
|
|
878
926
|
rescue ::LibXML::XML::Error => e
|
|
879
927
|
raise Moxml::XPathError.new(
|
|
880
928
|
e.message,
|
|
@@ -1109,9 +1157,9 @@ module Moxml
|
|
|
1109
1157
|
|
|
1110
1158
|
seen_ns[prefix] = true
|
|
1111
1159
|
output << if prefix.nil? || prefix.empty?
|
|
1112
|
-
" xmlns=\"#{
|
|
1160
|
+
" xmlns=\"#{XmlEmitter.escape_attribute(uri)}\""
|
|
1113
1161
|
else
|
|
1114
|
-
" xmlns:#{prefix}=\"#{
|
|
1162
|
+
" xmlns:#{prefix}=\"#{XmlEmitter.escape_attribute(uri)}\""
|
|
1115
1163
|
end
|
|
1116
1164
|
end
|
|
1117
1165
|
end
|
|
@@ -1127,7 +1175,7 @@ module Moxml
|
|
|
1127
1175
|
else
|
|
1128
1176
|
attr.name
|
|
1129
1177
|
end
|
|
1130
|
-
output << " #{attr_name}=\"#{
|
|
1178
|
+
output << " #{attr_name}=\"#{XmlEmitter.escape_attribute(attr.value)}\""
|
|
1131
1179
|
end
|
|
1132
1180
|
end
|
|
1133
1181
|
|
|
@@ -1167,7 +1215,7 @@ module Moxml
|
|
|
1167
1215
|
when ::LibXML::XML::Node::ELEMENT_NODE
|
|
1168
1216
|
serialize_element(node)
|
|
1169
1217
|
when ::LibXML::XML::Node::TEXT_NODE
|
|
1170
|
-
escape_text(node.content)
|
|
1218
|
+
XmlEmitter.escape_text(node.content)
|
|
1171
1219
|
when ::LibXML::XML::Node::CDATA_SECTION_NODE
|
|
1172
1220
|
"<![CDATA[#{node.content}]]>"
|
|
1173
1221
|
when ::LibXML::XML::Node::COMMENT_NODE
|
|
@@ -1179,28 +1227,6 @@ module Moxml
|
|
|
1179
1227
|
end
|
|
1180
1228
|
end
|
|
1181
1229
|
|
|
1182
|
-
def escape_text(text)
|
|
1183
|
-
text.to_s
|
|
1184
|
-
.gsub("&", "&")
|
|
1185
|
-
.gsub("<", "<")
|
|
1186
|
-
.gsub(">", ">")
|
|
1187
|
-
end
|
|
1188
|
-
|
|
1189
|
-
ESCAPE_XML_RE = /[&<>"]/
|
|
1190
|
-
ESCAPE_XML_MAP = { "&" => "&", "<" => "<", ">" => ">",
|
|
1191
|
-
'"' => """ }.freeze
|
|
1192
|
-
private_constant :ESCAPE_XML_RE, :ESCAPE_XML_MAP
|
|
1193
|
-
|
|
1194
|
-
def escape_xml(text)
|
|
1195
|
-
# One gsub pass with a Hash replacement allocates a single new
|
|
1196
|
-
# string. The previous chained gsubs allocated three throwaway
|
|
1197
|
-
# strings on every call (very hot for attribute-heavy XML).
|
|
1198
|
-
str = text.is_a?(String) ? text : text.to_s
|
|
1199
|
-
return str unless str.match?(ESCAPE_XML_RE)
|
|
1200
|
-
|
|
1201
|
-
str.gsub(ESCAPE_XML_RE, ESCAPE_XML_MAP)
|
|
1202
|
-
end
|
|
1203
|
-
|
|
1204
1230
|
def import_and_add(doc, element, child)
|
|
1205
1231
|
return unless element && child
|
|
1206
1232
|
|
|
@@ -1357,9 +1383,9 @@ module Moxml
|
|
|
1357
1383
|
|
|
1358
1384
|
def format_ns_declaration(prefix, uri)
|
|
1359
1385
|
if prefix.nil? || prefix.empty?
|
|
1360
|
-
" xmlns=\"#{
|
|
1386
|
+
" xmlns=\"#{XmlEmitter.escape_attribute(uri)}\""
|
|
1361
1387
|
else
|
|
1362
|
-
" xmlns:#{prefix}=\"#{
|
|
1388
|
+
" xmlns:#{prefix}=\"#{XmlEmitter.escape_attribute(uri)}\""
|
|
1363
1389
|
end
|
|
1364
1390
|
end
|
|
1365
1391
|
|
|
@@ -1370,7 +1396,7 @@ module Moxml
|
|
|
1370
1396
|
next if attr.name.start_with?("xmlns")
|
|
1371
1397
|
|
|
1372
1398
|
attr_name = attr.ns&.prefix ? "#{attr.ns.prefix}:#{attr.name}" : attr.name
|
|
1373
|
-
output << " #{attr_name}=\"#{
|
|
1399
|
+
output << " #{attr_name}=\"#{XmlEmitter.escape_attribute(attr.value)}\""
|
|
1374
1400
|
end
|
|
1375
1401
|
end
|
|
1376
1402
|
|
|
@@ -1510,11 +1536,6 @@ module Moxml
|
|
|
1510
1536
|
end
|
|
1511
1537
|
end
|
|
1512
1538
|
|
|
1513
|
-
def remove_indentation(xml_string)
|
|
1514
|
-
# Remove all newlines and extra spaces between tags
|
|
1515
|
-
xml_string.gsub(/>\s+</, "><").gsub(/\n\s*/, "")
|
|
1516
|
-
end
|
|
1517
|
-
|
|
1518
1539
|
def collect_namespace_definitions(node)
|
|
1519
1540
|
ns_defs = {}
|
|
1520
1541
|
|
|
@@ -1693,13 +1714,24 @@ module Moxml
|
|
|
1693
1714
|
@handler.on_end_document
|
|
1694
1715
|
end
|
|
1695
1716
|
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1717
|
+
# libxml strips prefixes and namespace declarations from the
|
|
1718
|
+
# plain on_start_element callback; the _ns variants carry
|
|
1719
|
+
# them. The namespaces hash uses nil for the default
|
|
1720
|
+
# declaration — the same key convention as every other
|
|
1721
|
+
# bridge's split. Attribute name prefixes are lost by the
|
|
1722
|
+
# binding in both callbacks (documented limitation).
|
|
1723
|
+
def on_start_element_ns(name, attributes, prefix, _uri, namespaces)
|
|
1724
|
+
normalized = (attributes || {}).map { |k, v| [k.to_s, v] }
|
|
1725
|
+
attr_hash, = split_attributes_and_namespaces(normalized) do |v|
|
|
1726
|
+
Moxml::Adapter::Base.decode_entities(v)
|
|
1727
|
+
end
|
|
1728
|
+
qualified = prefix ? "#{prefix}:#{name}" : name.to_s
|
|
1729
|
+
@handler.on_start_element(qualified, attr_hash, namespaces || {})
|
|
1699
1730
|
end
|
|
1700
1731
|
|
|
1701
|
-
def
|
|
1702
|
-
|
|
1732
|
+
def on_end_element_ns(name, prefix, _uri)
|
|
1733
|
+
qualified = prefix ? "#{prefix}:#{name}" : name.to_s
|
|
1734
|
+
@handler.on_end_element(qualified)
|
|
1703
1735
|
end
|
|
1704
1736
|
|
|
1705
1737
|
def on_characters(chars)
|
|
@@ -144,6 +144,7 @@ module Moxml
|
|
|
144
144
|
|
|
145
145
|
def set_namespace(element, ns)
|
|
146
146
|
element.namespace = ns
|
|
147
|
+
element
|
|
147
148
|
end
|
|
148
149
|
|
|
149
150
|
def namespace(element)
|
|
@@ -216,21 +217,36 @@ module Moxml
|
|
|
216
217
|
document.is_a?(::Nokogiri::XML::Document) ? document.root : document.children.first
|
|
217
218
|
end
|
|
218
219
|
|
|
219
|
-
def
|
|
220
|
-
|
|
220
|
+
def line_number(node)
|
|
221
|
+
node.line
|
|
221
222
|
end
|
|
222
223
|
|
|
223
|
-
def
|
|
224
|
-
|
|
224
|
+
def attribute_element(attr)
|
|
225
|
+
attr.parent
|
|
225
226
|
end
|
|
226
227
|
|
|
227
228
|
def set_attribute(element, name, value)
|
|
228
229
|
element[name.to_s] = value.to_s
|
|
229
230
|
end
|
|
230
231
|
|
|
232
|
+
def attributes(element)
|
|
233
|
+
# attribute_nodes, not attributes.values: the attributes Hash
|
|
234
|
+
# is keyed by local name, so xmi:type and type collide and
|
|
235
|
+
# one is silently dropped (issue #94).
|
|
236
|
+
element.attribute_nodes
|
|
237
|
+
end
|
|
238
|
+
|
|
231
239
|
def get_attribute(element, name)
|
|
232
|
-
|
|
233
|
-
|
|
240
|
+
name_s = name.to_s
|
|
241
|
+
if name_s.include?(":")
|
|
242
|
+
prefix, local = name_s.split(":", 2)
|
|
243
|
+
href = element.namespace_definitions
|
|
244
|
+
.find { |ns| ns.prefix == prefix }&.href
|
|
245
|
+
element.attribute_with_ns(local, href)
|
|
246
|
+
else
|
|
247
|
+
element.attribute_with_ns(name_s, nil) ||
|
|
248
|
+
element.attributes[name_s]
|
|
249
|
+
end
|
|
234
250
|
end
|
|
235
251
|
|
|
236
252
|
def get_attribute_value(element, name)
|
|
@@ -360,7 +376,10 @@ module Moxml
|
|
|
360
376
|
end
|
|
361
377
|
|
|
362
378
|
def xpath(node, expression, namespaces = nil)
|
|
363
|
-
node.xpath(expression, namespaces)
|
|
379
|
+
result = node.xpath(expression, namespaces)
|
|
380
|
+
# Adapter contract: Array<native> | scalar (count(),
|
|
381
|
+
# string-length(), boolean functions return scalars).
|
|
382
|
+
result.is_a?(Enumerable) ? result.to_a : result
|
|
364
383
|
rescue ::Nokogiri::XML::XPath::SyntaxError => e
|
|
365
384
|
raise Moxml::XPathError.new(
|
|
366
385
|
e.message,
|
|
@@ -471,7 +490,9 @@ module Moxml
|
|
|
471
490
|
end
|
|
472
491
|
|
|
473
492
|
def start_element(name, attributes = [])
|
|
474
|
-
attr_hash, ns_hash = split_attributes_and_namespaces(attributes)
|
|
493
|
+
attr_hash, ns_hash = split_attributes_and_namespaces(attributes) do |v|
|
|
494
|
+
Moxml::Adapter::Base.decode_entities(v)
|
|
495
|
+
end
|
|
475
496
|
@handler.on_start_element(name, attr_hash, ns_hash)
|
|
476
497
|
end
|
|
477
498
|
|