moxml 0.1.21 → 0.1.23
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/opal.yml +37 -0
- data/.gitignore +1 -0
- data/.rspec-opal +5 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +680 -110
- data/Gemfile +6 -0
- data/Rakefile +70 -0
- data/lib/compat/opal/rexml/namespace.rb +59 -0
- data/lib/compat/opal/rexml/parsers/baseparser.rb +1016 -0
- data/lib/compat/opal/rexml/source.rb +214 -0
- data/lib/compat/opal/rexml/text.rb +426 -0
- data/lib/compat/opal/rexml/xmltokens.rb +45 -0
- data/lib/compat/opal/rexml_compat.rb +77 -0
- data/lib/moxml/adapter/customized_oga/xml_declaration.rb +8 -1
- data/lib/moxml/adapter/customized_rexml/formatter.rb +11 -10
- data/lib/moxml/adapter/headed_ox.rb +2 -6
- data/lib/moxml/adapter/libxml/entity_ref_registry.rb +4 -2
- data/lib/moxml/adapter/libxml/entity_restorer.rb +3 -1
- data/lib/moxml/adapter/libxml.rb +22 -24
- data/lib/moxml/adapter/nokogiri.rb +24 -33
- data/lib/moxml/adapter/oga.rb +47 -84
- data/lib/moxml/adapter/ox.rb +43 -41
- data/lib/moxml/adapter/rexml.rb +29 -33
- data/lib/moxml/adapter.rb +38 -8
- data/lib/moxml/config.rb +16 -3
- data/lib/moxml/document.rb +2 -8
- data/lib/moxml/entity_registry.rb +40 -31
- data/lib/moxml/entity_registry_opal_data.rb +2138 -0
- data/lib/moxml/node.rb +27 -26
- data/lib/moxml/sax/namespace_splitter.rb +54 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils.rb +10 -1
- data/lib/moxml.rb +7 -0
- data/spec/consistency/adapter_parity_spec.rb +1 -1
- data/spec/integration/all_adapters_spec.rb +2 -1
- data/spec/integration/shared_examples/line_ending_behavior.rb +56 -0
- data/spec/integration/w3c_namespace_spec.rb +1 -1
- data/spec/moxml/adapter/libxml_internals_spec.rb +4 -2
- data/spec/moxml/adapter/ox_spec.rb +8 -0
- data/spec/moxml/adapter/platform_spec.rb +70 -0
- data/spec/moxml/adapter/shared_examples/adapter_contract.rb +0 -6
- data/spec/moxml/config_spec.rb +33 -0
- data/spec/moxml/entity_registry_spec.rb +10 -0
- data/spec/moxml/native_attachment/opal_spec.rb +39 -2
- data/spec/moxml/node_type_map_spec.rb +43 -0
- data/spec/moxml/opal_rexml_adapter_spec.rb +14 -0
- data/spec/moxml/opal_smoke_spec.rb +61 -0
- data/spec/moxml/sax/namespace_splitter_spec.rb +67 -0
- data/spec/moxml/text_spec.rb +1 -1
- data/spec/spec_helper.rb +32 -13
- data/spec/support/opal.rb +16 -0
- metadata +19 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# backtick_javascript: true
|
|
4
|
+
|
|
5
|
+
require "corelib/array/pack"
|
|
6
|
+
|
|
7
|
+
unless defined?(StringScanner::Version)
|
|
8
|
+
class StringScanner
|
|
9
|
+
Version = "3.0.8"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
unless String.method_defined?(:force_encoding)
|
|
14
|
+
class String
|
|
15
|
+
def force_encoding(*)
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
unless defined?(Encoding)
|
|
22
|
+
module ::Encoding
|
|
23
|
+
UTF_8 = "UTF-8"
|
|
24
|
+
ASCII_8BIT = "ASCII-8BIT"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
unless String.method_defined?(:encode)
|
|
29
|
+
class String
|
|
30
|
+
def encode(*)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Opal defines mutable String methods as raising NotImplementedError.
|
|
37
|
+
# Override with functional equivalents that return new strings.
|
|
38
|
+
class String
|
|
39
|
+
def <<(str)
|
|
40
|
+
`return self + #{str}.to_s`
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def chomp!(sep = nil)
|
|
44
|
+
`
|
|
45
|
+
var r = #{chomp(sep)};
|
|
46
|
+
return r === self ? nil : r;
|
|
47
|
+
`
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def gsub!(pattern, replacement, &block)
|
|
51
|
+
`
|
|
52
|
+
var r = #{gsub(pattern, replacement, &block)};
|
|
53
|
+
return r === self ? nil : r;
|
|
54
|
+
`
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def squeeze!(*sets)
|
|
58
|
+
`
|
|
59
|
+
var r = #{squeeze(*sets)};
|
|
60
|
+
return r === self ? nil : r;
|
|
61
|
+
`
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def strip!
|
|
65
|
+
`
|
|
66
|
+
var r = #{strip};
|
|
67
|
+
return r === self ? nil : r;
|
|
68
|
+
`
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class StringIO
|
|
73
|
+
def <<(str)
|
|
74
|
+
write(str)
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -8,10 +8,17 @@ module Moxml
|
|
|
8
8
|
class XmlDeclaration < ::Oga::XML::XmlDeclaration
|
|
9
9
|
def initialize(options = {})
|
|
10
10
|
@version = options[:version] || "1.0"
|
|
11
|
-
# encoding is optional, but Oga sets it to UTF-8 by default
|
|
12
11
|
@encoding = options[:encoding]
|
|
13
12
|
@standalone = options[:standalone]
|
|
14
13
|
end
|
|
14
|
+
|
|
15
|
+
def to_xml
|
|
16
|
+
parts = ["<?xml"]
|
|
17
|
+
parts << %( version="#{version}") if version
|
|
18
|
+
parts << %( encoding="#{encoding}") if encoding
|
|
19
|
+
parts << %( standalone="#{standalone}") if standalone
|
|
20
|
+
"#{parts.join}?>"
|
|
21
|
+
end
|
|
15
22
|
end
|
|
16
23
|
end
|
|
17
24
|
end
|
|
@@ -27,8 +27,11 @@ module Moxml
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def indented?
|
|
31
|
+
!@indentation.empty?
|
|
32
|
+
end
|
|
33
|
+
|
|
30
34
|
def write_element(node, output)
|
|
31
|
-
# output << ' ' * @level
|
|
32
35
|
output << "<#{node.expanded_name}"
|
|
33
36
|
write_attributes(node, output)
|
|
34
37
|
|
|
@@ -45,18 +48,16 @@ module Moxml
|
|
|
45
48
|
|
|
46
49
|
output << ">"
|
|
47
50
|
|
|
48
|
-
# Check for mixed content
|
|
49
51
|
has_text = node.children.any? { |c| c.is_a?(::REXML::Text) && !c.to_s.strip.empty? }
|
|
50
52
|
has_elements = node.children.any?(::REXML::Element)
|
|
51
|
-
|
|
53
|
+
indent_children = indented? && has_elements && !has_text
|
|
52
54
|
|
|
53
55
|
# Handle children based on content type
|
|
54
56
|
all_children_empty = node.children.empty? && !(entity_refs && !entity_refs.empty?)
|
|
55
57
|
unless all_children_empty
|
|
56
|
-
@level += @indentation.length
|
|
58
|
+
@level += @indentation.length if indent_children
|
|
57
59
|
|
|
58
60
|
if entity_refs && !entity_refs.empty? && child_sequence
|
|
59
|
-
# Interleave native children with entity refs using tracked sequence
|
|
60
61
|
eref_idx = 0
|
|
61
62
|
native_idx = 0
|
|
62
63
|
child_sequence.each do |type|
|
|
@@ -69,10 +70,12 @@ module Moxml
|
|
|
69
70
|
child.to_s.strip.empty? &&
|
|
70
71
|
!(child.next_sibling.nil? && child.previous_sibling.nil?)
|
|
71
72
|
|
|
73
|
+
output << "\n" << (" " * @level) if indent_children
|
|
72
74
|
write(child, output)
|
|
73
75
|
end
|
|
74
76
|
when :eref
|
|
75
77
|
if eref_idx < entity_refs.size
|
|
78
|
+
output << "\n" << (" " * @level) if indent_children
|
|
76
79
|
write(entity_refs[eref_idx], output)
|
|
77
80
|
eref_idx += 1
|
|
78
81
|
end
|
|
@@ -80,24 +83,22 @@ module Moxml
|
|
|
80
83
|
end
|
|
81
84
|
else
|
|
82
85
|
node.children.each_with_index do |child, _index|
|
|
83
|
-
# Skip insignificant whitespace
|
|
84
86
|
next if child.is_a?(::REXML::Text) &&
|
|
85
87
|
child.to_s.strip.empty? &&
|
|
86
88
|
!(child.next_sibling.nil? && child.previous_sibling.nil?)
|
|
87
89
|
|
|
90
|
+
output << "\n" << (" " * @level) if indent_children
|
|
88
91
|
write(child, output)
|
|
89
92
|
end
|
|
90
93
|
end
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
unless mixed
|
|
95
|
+
if indent_children
|
|
94
96
|
@level -= @indentation.length
|
|
95
|
-
|
|
97
|
+
output << "\n" << (" " * @level)
|
|
96
98
|
end
|
|
97
99
|
end
|
|
98
100
|
|
|
99
101
|
output << "</#{node.expanded_name}>"
|
|
100
|
-
# output << "\n" unless mixed
|
|
101
102
|
end
|
|
102
103
|
|
|
103
104
|
def write_text(node, output)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
return if RUBY_ENGINE == "opal"
|
|
4
|
+
|
|
3
5
|
require_relative "ox"
|
|
4
6
|
require_relative "../xpath"
|
|
5
7
|
# Force load XPath modules (autoload doesn't work well with relative requires in examples)
|
|
@@ -66,14 +68,8 @@ module Moxml
|
|
|
66
68
|
# @param [Hash] namespaces Namespace prefix mappings
|
|
67
69
|
# @return [Array, Object] Native node array or scalar value
|
|
68
70
|
def xpath(node, expression, namespaces = {})
|
|
69
|
-
# If we receive a native node, wrap it first
|
|
70
|
-
# Document#xpath passes @native, but our compiled XPath needs Moxml nodes
|
|
71
71
|
unless node.is_a?(Moxml::Node)
|
|
72
|
-
# Determine the context from the node if possible
|
|
73
|
-
# For now, create a basic context for wrapped nodes
|
|
74
72
|
ctx = Context.new(:headed_ox)
|
|
75
|
-
|
|
76
|
-
# Wrap the native node - don't rebuild the whole document
|
|
77
73
|
node = Moxml::Node.wrap(node, ctx)
|
|
78
74
|
end
|
|
79
75
|
|
|
@@ -9,7 +9,8 @@ module Moxml
|
|
|
9
9
|
ENTITY_REFS_KEY = :_entity_ref_pairs
|
|
10
10
|
CHILD_SEQUENCE_KEY = :_child_seq_pairs
|
|
11
11
|
NON_WHITESPACE_RE = /\S/
|
|
12
|
-
private_constant :ENTITY_REFS_KEY, :CHILD_SEQUENCE_KEY,
|
|
12
|
+
private_constant :ENTITY_REFS_KEY, :CHILD_SEQUENCE_KEY,
|
|
13
|
+
:NON_WHITESPACE_RE
|
|
13
14
|
|
|
14
15
|
def initialize(attachments, doc)
|
|
15
16
|
@attachments = attachments
|
|
@@ -34,7 +35,8 @@ module Moxml
|
|
|
34
35
|
if existing
|
|
35
36
|
existing << :eref
|
|
36
37
|
else
|
|
37
|
-
seq_by_path[path] =
|
|
38
|
+
seq_by_path[path] =
|
|
39
|
+
Array.new(count_native_children(element), :native)
|
|
38
40
|
seq_by_path[path] << :eref
|
|
39
41
|
@attachments.set(@doc, CHILD_SEQUENCE_KEY, seq_by_path)
|
|
40
42
|
end
|
|
@@ -76,7 +76,9 @@ module Moxml
|
|
|
76
76
|
def append_chunk(parent, type, payload)
|
|
77
77
|
case type
|
|
78
78
|
when :text
|
|
79
|
-
parent.add_child(::Moxml::Text.new(
|
|
79
|
+
parent.add_child(::Moxml::Text.new(
|
|
80
|
+
@adapter.create_native_text(payload), @ctx
|
|
81
|
+
))
|
|
80
82
|
when :eref
|
|
81
83
|
parent.add_child(
|
|
82
84
|
::Moxml::EntityReference.new(
|
data/lib/moxml/adapter/libxml.rb
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
return if RUBY_ENGINE == "opal"
|
|
4
|
+
|
|
3
5
|
require_relative "base"
|
|
4
6
|
require "libxml"
|
|
5
7
|
require_relative "customized_libxml"
|
|
8
|
+
require_relative "../sax/namespace_splitter"
|
|
6
9
|
|
|
7
10
|
module Moxml
|
|
8
11
|
module Adapter
|
|
@@ -1179,7 +1182,8 @@ module Moxml
|
|
|
1179
1182
|
end
|
|
1180
1183
|
|
|
1181
1184
|
ESCAPE_XML_RE = /[&<>"]/
|
|
1182
|
-
ESCAPE_XML_MAP = { "&" => "&", "<" => "<", ">" => ">",
|
|
1185
|
+
ESCAPE_XML_MAP = { "&" => "&", "<" => "<", ">" => ">",
|
|
1186
|
+
'"' => """ }.freeze
|
|
1183
1187
|
private_constant :ESCAPE_XML_RE, :ESCAPE_XML_MAP
|
|
1184
1188
|
|
|
1185
1189
|
def escape_xml(text)
|
|
@@ -1275,7 +1279,13 @@ module Moxml
|
|
|
1275
1279
|
# attachment query that otherwise fires for every element under
|
|
1276
1280
|
# Monitor#synchronize.
|
|
1277
1281
|
eref_active = doc_eref_active?(elem.doc) if eref_active.nil?
|
|
1278
|
-
entity_refs, child_sequence = eref_active
|
|
1282
|
+
entity_refs, child_sequence = if eref_active
|
|
1283
|
+
lookup_entity_ref_serialization(elem)
|
|
1284
|
+
else
|
|
1285
|
+
[
|
|
1286
|
+
nil, nil
|
|
1287
|
+
]
|
|
1288
|
+
end
|
|
1279
1289
|
|
|
1280
1290
|
# Always use verbose format <tag></tag> for consistency with other adapters
|
|
1281
1291
|
output << ">"
|
|
@@ -1619,8 +1629,14 @@ module Moxml
|
|
|
1619
1629
|
# duplicated — callers that need the subtree use deep_duplicate_node.
|
|
1620
1630
|
def shallow_duplicate_element(native_node)
|
|
1621
1631
|
new_node = ::LibXML::XML::Node.new(native_node.name)
|
|
1622
|
-
|
|
1623
|
-
|
|
1632
|
+
if native_node.is_a?(::LibXML::XML::Node)
|
|
1633
|
+
copy_element_namespaces(native_node,
|
|
1634
|
+
new_node)
|
|
1635
|
+
end
|
|
1636
|
+
if native_node.attributes?
|
|
1637
|
+
copy_element_attributes(native_node,
|
|
1638
|
+
new_node)
|
|
1639
|
+
end
|
|
1624
1640
|
new_node
|
|
1625
1641
|
end
|
|
1626
1642
|
|
|
@@ -1656,6 +1672,7 @@ module Moxml
|
|
|
1656
1672
|
# @private
|
|
1657
1673
|
class LibXMLSAXBridge
|
|
1658
1674
|
include ::LibXML::XML::SaxParser::Callbacks
|
|
1675
|
+
include Moxml::SAX::NamespaceSplitter
|
|
1659
1676
|
|
|
1660
1677
|
def initialize(handler)
|
|
1661
1678
|
@handler = handler
|
|
@@ -1672,26 +1689,7 @@ module Moxml
|
|
|
1672
1689
|
end
|
|
1673
1690
|
|
|
1674
1691
|
def on_start_element(name, attributes)
|
|
1675
|
-
|
|
1676
|
-
attr_hash = {}
|
|
1677
|
-
ns_hash = {}
|
|
1678
|
-
|
|
1679
|
-
attributes&.each do |attr_name, attr_value|
|
|
1680
|
-
if attr_name.to_s.start_with?("xmlns")
|
|
1681
|
-
# Namespace declaration
|
|
1682
|
-
prefix = if attr_name.to_s == "xmlns"
|
|
1683
|
-
nil
|
|
1684
|
-
else
|
|
1685
|
-
attr_name.to_s.sub(
|
|
1686
|
-
"xmlns:", ""
|
|
1687
|
-
)
|
|
1688
|
-
end
|
|
1689
|
-
ns_hash[prefix] = attr_value
|
|
1690
|
-
else
|
|
1691
|
-
attr_hash[attr_name.to_s] = attr_value
|
|
1692
|
-
end
|
|
1693
|
-
end
|
|
1694
|
-
|
|
1692
|
+
attr_hash, ns_hash = split_attributes_and_namespaces(attributes)
|
|
1695
1693
|
@handler.on_start_element(name.to_s, attr_hash, ns_hash)
|
|
1696
1694
|
end
|
|
1697
1695
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
return if RUBY_ENGINE == "opal"
|
|
4
|
+
|
|
3
5
|
require_relative "base"
|
|
4
6
|
require "nokogiri"
|
|
7
|
+
require_relative "../sax/namespace_splitter"
|
|
5
8
|
|
|
6
9
|
module Moxml
|
|
7
10
|
module Adapter
|
|
@@ -242,25 +245,22 @@ module Moxml
|
|
|
242
245
|
end
|
|
243
246
|
|
|
244
247
|
def add_child(element, child)
|
|
245
|
-
# Special handling for declarations on Nokogiri documents
|
|
246
248
|
if element.is_a?(::Nokogiri::XML::Document) &&
|
|
247
249
|
child.is_a?(::Nokogiri::XML::ProcessingInstruction) &&
|
|
248
250
|
child.name == "xml"
|
|
249
|
-
# Set document's xml_decl property
|
|
250
251
|
version = declaration_attribute(child, "version") || "1.0"
|
|
251
252
|
encoding = declaration_attribute(child, "encoding")
|
|
252
253
|
standalone = declaration_attribute(child, "standalone")
|
|
253
254
|
|
|
254
|
-
# Store declaration state in attachment map
|
|
255
255
|
attachments.set(element, :xml_decl, {
|
|
256
256
|
version: version,
|
|
257
257
|
encoding: encoding,
|
|
258
258
|
standalone: standalone,
|
|
259
259
|
}.compact)
|
|
260
|
+
return
|
|
260
261
|
end
|
|
261
262
|
|
|
262
263
|
if node_type(child) == :doctype
|
|
263
|
-
# avoid exceptions: cannot reparent Nokogiri::XML::DTD there
|
|
264
264
|
element.create_internal_subset(
|
|
265
265
|
child.name, child.external_id, child.system_id
|
|
266
266
|
)
|
|
@@ -394,23 +394,28 @@ module Moxml
|
|
|
394
394
|
save_options |= ::Nokogiri::XML::Node::SaveOptions::FORMAT
|
|
395
395
|
end
|
|
396
396
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
save_options |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION if xml_decl.nil?
|
|
397
|
+
custom_decl = nil
|
|
398
|
+
if options[:no_declaration]
|
|
399
|
+
save_options |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
|
|
400
|
+
elsif attachments.key?(node, :xml_decl) && (xml_decl = attachments.get(node, :xml_decl))
|
|
401
|
+
save_options |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
|
|
402
|
+
attrs = ["version=\"#{xml_decl[:version]}\""]
|
|
403
|
+
attrs << "encoding=\"#{xml_decl[:encoding]}\"" if xml_decl[:encoding]
|
|
404
|
+
attrs << "standalone=\"#{xml_decl[:standalone]}\"" if xml_decl[:standalone]
|
|
405
|
+
custom_decl = "<?xml #{attrs.join(' ')}?>"
|
|
407
406
|
end
|
|
408
407
|
|
|
409
|
-
node.to_xml(
|
|
408
|
+
result = node.to_xml(
|
|
410
409
|
indent: options[:indent],
|
|
411
410
|
encoding: options[:encoding],
|
|
412
411
|
save_with: save_options,
|
|
413
412
|
)
|
|
413
|
+
|
|
414
|
+
if custom_decl
|
|
415
|
+
result = "#{custom_decl}\n#{result}"
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
result
|
|
414
419
|
end
|
|
415
420
|
|
|
416
421
|
def has_declaration?(native_doc, wrapper)
|
|
@@ -446,6 +451,8 @@ module Moxml
|
|
|
446
451
|
#
|
|
447
452
|
# @private
|
|
448
453
|
class NokogiriSAXBridge < ::Nokogiri::XML::SAX::Document
|
|
454
|
+
include Moxml::SAX::NamespaceSplitter
|
|
455
|
+
|
|
449
456
|
def initialize(handler)
|
|
450
457
|
super()
|
|
451
458
|
@handler = handler
|
|
@@ -462,24 +469,8 @@ module Moxml
|
|
|
462
469
|
end
|
|
463
470
|
|
|
464
471
|
def start_element(name, attributes = [])
|
|
465
|
-
|
|
466
|
-
attr_hash
|
|
467
|
-
namespaces_hash = {}
|
|
468
|
-
|
|
469
|
-
attributes.each do |attr|
|
|
470
|
-
attr_name = attr[0]
|
|
471
|
-
attr_value = attr[1]
|
|
472
|
-
|
|
473
|
-
if attr_name.start_with?("xmlns")
|
|
474
|
-
# Namespace declaration
|
|
475
|
-
prefix = attr_name == "xmlns" ? nil : attr_name.sub("xmlns:", "")
|
|
476
|
-
namespaces_hash[prefix] = attr_value
|
|
477
|
-
else
|
|
478
|
-
attr_hash[attr_name] = attr_value
|
|
479
|
-
end
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
@handler.on_start_element(name, attr_hash, namespaces_hash)
|
|
472
|
+
attr_hash, ns_hash = split_attributes_and_namespaces(attributes)
|
|
473
|
+
@handler.on_start_element(name, attr_hash, ns_hash)
|
|
483
474
|
end
|
|
484
475
|
|
|
485
476
|
def end_element(name)
|
data/lib/moxml/adapter/oga.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "base"
|
|
4
4
|
require_relative "customized_oga"
|
|
5
5
|
require "oga"
|
|
6
|
+
require_relative "../sax/namespace_splitter"
|
|
6
7
|
|
|
7
8
|
module Moxml
|
|
8
9
|
module Adapter
|
|
@@ -288,11 +289,25 @@ module Moxml
|
|
|
288
289
|
child_or_text
|
|
289
290
|
end
|
|
290
291
|
|
|
291
|
-
# Special handling for declarations on Oga documents
|
|
292
292
|
if element.is_a?(::Oga::XML::Document) &&
|
|
293
293
|
child.is_a?(::Oga::XML::XmlDeclaration)
|
|
294
|
-
# Track declaration state in attachment map
|
|
295
294
|
attachments.set(element, :xml_declaration, child)
|
|
295
|
+
return
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Insert doctype before root element in document
|
|
299
|
+
if element.is_a?(::Oga::XML::Document) && child.is_a?(::Oga::XML::Doctype)
|
|
300
|
+
root_idx = nil
|
|
301
|
+
element.children.each_with_index do |n, i|
|
|
302
|
+
if n.is_a?(::Oga::XML::Element)
|
|
303
|
+
root_idx = i
|
|
304
|
+
break
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
if root_idx
|
|
308
|
+
element.children.insert(root_idx, child)
|
|
309
|
+
return
|
|
310
|
+
end
|
|
296
311
|
end
|
|
297
312
|
|
|
298
313
|
element.children << child
|
|
@@ -464,86 +479,53 @@ module Moxml
|
|
|
464
479
|
|
|
465
480
|
private
|
|
466
481
|
|
|
482
|
+
def declaration_to_xml(decl)
|
|
483
|
+
parts = ["<?xml"]
|
|
484
|
+
parts << %( version="#{decl.version}") if decl.version
|
|
485
|
+
parts << %( encoding="#{decl.encoding}") if decl.encoding
|
|
486
|
+
parts << %( standalone="#{decl.standalone}") if decl.standalone
|
|
487
|
+
"#{parts.join}?>"
|
|
488
|
+
end
|
|
489
|
+
|
|
467
490
|
def serialize_without_entity_processing(node, options = {})
|
|
468
|
-
# Oga's XmlGenerator doesn't support options directly
|
|
469
|
-
# We need to handle declaration options ourselves for Document nodes
|
|
470
491
|
if node.is_a?(::Oga::XML::Document)
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
effective_xml_declaration = node.xml_declaration || attachments.get(
|
|
474
|
-
node, :xml_declaration
|
|
475
|
-
)
|
|
492
|
+
effective_xml_declaration = attachments.get(node, :xml_declaration)
|
|
493
|
+
|
|
476
494
|
should_include_decl = if options.key?(:no_declaration)
|
|
477
495
|
!options[:no_declaration]
|
|
478
496
|
elsif options.key?(:declaration)
|
|
479
497
|
options[:declaration]
|
|
480
498
|
else
|
|
481
|
-
|
|
482
|
-
effective_xml_declaration ? true : false
|
|
499
|
+
effective_xml_declaration || node.xml_declaration ? true : false
|
|
483
500
|
end
|
|
484
501
|
|
|
485
|
-
|
|
486
|
-
# This prevents duplicate declarations when document already has one
|
|
487
|
-
has_existing_declaration = node.children.any?(::Oga::XML::XmlDeclaration)
|
|
488
|
-
|
|
489
|
-
if should_include_decl && !effective_xml_declaration && !has_existing_declaration
|
|
490
|
-
# Need to add declaration - create default one
|
|
491
|
-
output = []
|
|
492
|
-
output << '<?xml version="1.0" encoding="UTF-8"?>'
|
|
493
|
-
output << "\n"
|
|
494
|
-
|
|
495
|
-
# Serialize doctype if present
|
|
496
|
-
output << node.doctype.to_xml << "\n" if node.doctype
|
|
497
|
-
|
|
498
|
-
# Serialize children
|
|
499
|
-
node.children.each do |child|
|
|
500
|
-
output << ::Moxml::Adapter::CustomizedOga::XmlGenerator.new(child).to_xml
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
return output.join
|
|
504
|
-
elsif !should_include_decl
|
|
505
|
-
# Skip xml_declaration
|
|
506
|
-
output = []
|
|
507
|
-
|
|
508
|
-
# Serialize doctype if present
|
|
509
|
-
output << node.doctype.to_xml << "\n" if node.doctype
|
|
510
|
-
|
|
511
|
-
# Serialize root and other children
|
|
512
|
-
node.children.each do |child|
|
|
513
|
-
next if child.is_a?(::Oga::XML::XmlDeclaration)
|
|
502
|
+
output = []
|
|
514
503
|
|
|
515
|
-
|
|
504
|
+
if should_include_decl
|
|
505
|
+
decl = effective_xml_declaration || node.xml_declaration
|
|
506
|
+
if decl
|
|
507
|
+
output << declaration_to_xml(decl)
|
|
508
|
+
else
|
|
509
|
+
output << '<?xml version="1.0" encoding="UTF-8"?>'
|
|
516
510
|
end
|
|
517
|
-
|
|
518
|
-
return output.join
|
|
511
|
+
output << "\n"
|
|
519
512
|
end
|
|
520
|
-
end
|
|
521
513
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
))
|
|
527
|
-
if node.is_a?(::Oga::XML::Document) && effective_xml_declaration
|
|
528
|
-
# Document has declaration - use custom handling to avoid duplicates
|
|
529
|
-
output = []
|
|
530
|
-
xml_declaration_serialized = false
|
|
514
|
+
if node.doctype
|
|
515
|
+
output << node.doctype.to_xml
|
|
516
|
+
output << "\n"
|
|
517
|
+
end
|
|
531
518
|
|
|
532
|
-
# Serialize children, but skip XmlDeclaration if it would cause duplication
|
|
533
519
|
node.children.each do |child|
|
|
534
|
-
|
|
535
|
-
next if xml_declaration && xml_declaration_serialized
|
|
536
|
-
|
|
537
|
-
xml_declaration_serialized = true if xml_declaration
|
|
520
|
+
next if child.is_a?(::Oga::XML::XmlDeclaration)
|
|
538
521
|
|
|
539
522
|
output << ::Moxml::Adapter::CustomizedOga::XmlGenerator.new(child).to_xml
|
|
540
523
|
end
|
|
541
524
|
|
|
542
|
-
output.join
|
|
543
|
-
else
|
|
544
|
-
# Normal case - use XmlGenerator directly
|
|
545
|
-
::Moxml::Adapter::CustomizedOga::XmlGenerator.new(node).to_xml
|
|
525
|
+
return output.join
|
|
546
526
|
end
|
|
527
|
+
|
|
528
|
+
::Moxml::Adapter::CustomizedOga::XmlGenerator.new(node).to_xml
|
|
547
529
|
end
|
|
548
530
|
end
|
|
549
531
|
end
|
|
@@ -555,6 +537,8 @@ module Moxml
|
|
|
555
537
|
#
|
|
556
538
|
# @private
|
|
557
539
|
class OgaSAXBridge
|
|
540
|
+
include Moxml::SAX::NamespaceSplitter
|
|
541
|
+
|
|
558
542
|
def initialize(handler)
|
|
559
543
|
@handler = handler
|
|
560
544
|
end
|
|
@@ -563,29 +547,8 @@ module Moxml
|
|
|
563
547
|
# namespace may be nil
|
|
564
548
|
# attributes is an array of [name, value] pairs
|
|
565
549
|
def on_element(namespace, name, attributes)
|
|
566
|
-
# Build full qualified name if namespace present
|
|
567
550
|
element_name = namespace ? "#{namespace}:#{name}" : name
|
|
568
|
-
|
|
569
|
-
# Convert Oga attributes to hash
|
|
570
|
-
attr_hash = {}
|
|
571
|
-
ns_hash = {}
|
|
572
|
-
|
|
573
|
-
# Oga delivers attributes as array of [name, value] pairs
|
|
574
|
-
attributes.each do |attr_name, attr_value|
|
|
575
|
-
if attr_name.to_s.start_with?("xmlns")
|
|
576
|
-
prefix = if attr_name.to_s == "xmlns"
|
|
577
|
-
nil
|
|
578
|
-
else
|
|
579
|
-
attr_name.to_s.sub(
|
|
580
|
-
"xmlns:", ""
|
|
581
|
-
)
|
|
582
|
-
end
|
|
583
|
-
ns_hash[prefix] = attr_value
|
|
584
|
-
else
|
|
585
|
-
attr_hash[attr_name.to_s] = attr_value
|
|
586
|
-
end
|
|
587
|
-
end
|
|
588
|
-
|
|
551
|
+
attr_hash, ns_hash = split_attributes_and_namespaces(attributes)
|
|
589
552
|
@handler.on_start_element(element_name, attr_hash, ns_hash)
|
|
590
553
|
end
|
|
591
554
|
|