moxml 0.5.136 → 0.5.138
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/lib/moxml/adapter/customized_leptris.rb +0 -1
- data/lib/moxml/adapter/leptris/document_parts.rb +14 -3
- data/lib/moxml/adapter/leptris/markers.rb +1 -2
- data/lib/moxml/adapter/leptris/serialize.rb +1 -1
- data/lib/moxml/adapter/leptris.rb +3 -25
- data/lib/moxml/adapter/libxml.rb +13 -0
- data/lib/moxml/adapter/nokogiri.rb +5 -1
- data/lib/moxml/adapter/rexml.rb +5 -2
- data/lib/moxml/attribute_resolver.rb +23 -0
- data/lib/moxml/element.rb +7 -3
- data/lib/moxml/version.rb +1 -1
- data/spec/consistency/xpath_idioms_spec.rb +62 -0
- data/spec/integration/shared_examples/node_wrappers/attribute_behavior.rb +35 -0
- data/spec/moxml/adapter/leptris_document_declaration_spec.rb +42 -0
- metadata +4 -3
- data/lib/moxml/adapter/customized_leptris/document_pi.rb +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20467d4026cf631dd85798aaaa47a53526e1a6c8e26b1583ac896e56bb0fa4b4
|
|
4
|
+
data.tar.gz: 3d3829939332dfd24a1d552308d10fd7c0dbbe6eed998a5292ec5a5d82d53def
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 353a615d10c7755a3d03a5dd13988eb57b1f30b2bc68f1506e68bc48d053deca42fd968cbba67f5df10cd14dff7da757bfd15f89a734cd1e607e34d3b319eb0a
|
|
7
|
+
data.tar.gz: bfa917028338657c3c09d91f32a964a653f4e50bc8d5abd1d0c7cf404d284d2efb216b0e62df1f17f1d731ef636c6d60bbe27a5f587ccca2c45bb65aa93c8e39
|
|
@@ -12,7 +12,6 @@ module Moxml
|
|
|
12
12
|
autoload :Doctype, "moxml/adapter/customized_leptris/doctype"
|
|
13
13
|
autoload :EntityReference, "moxml/adapter/customized_leptris/entity_reference"
|
|
14
14
|
autoload :TextSegment, "moxml/adapter/customized_leptris/text_segment"
|
|
15
|
-
autoload :DocumentPI, "moxml/adapter/customized_leptris/document_pi"
|
|
16
15
|
end
|
|
17
16
|
end
|
|
18
17
|
end
|
|
@@ -36,7 +36,13 @@ module Moxml
|
|
|
36
36
|
# Nokogiri-shaped contract, epilog anchoring included (issue
|
|
37
37
|
# #130). Built documents reflect their parts immediately
|
|
38
38
|
# since leptris-ruby 1.9.32 (leptris-ruby#91).
|
|
39
|
-
|
|
39
|
+
# leptris-ruby#459 lists the declaration among the binding
|
|
40
|
+
# children; the Nokogiri-shaped facade contract never lists
|
|
41
|
+
# it (the declaration is a preamble part, not a child node)
|
|
42
|
+
# — filtered at the listing seam.
|
|
43
|
+
children.concat(
|
|
44
|
+
doc.children.to_a.grep_v(::Leptris::XML::Declaration),
|
|
45
|
+
)
|
|
40
46
|
|
|
41
47
|
texts = attachments.get(doc, :document_text)
|
|
42
48
|
children.concat(texts) if texts
|
|
@@ -83,8 +89,6 @@ module Moxml
|
|
|
83
89
|
doc.root = child
|
|
84
90
|
when ::Leptris::XML::ProcessingInstruction
|
|
85
91
|
doc.add_pi(child.target, child.content.to_s)
|
|
86
|
-
when CustomizedLeptris::DocumentPI
|
|
87
|
-
doc.add_pi(child.target, child.data)
|
|
88
92
|
when ::Leptris::XML::Text
|
|
89
93
|
texts = attachments.get(doc, :document_text) || []
|
|
90
94
|
texts << child
|
|
@@ -158,6 +162,13 @@ module Moxml
|
|
|
158
162
|
next
|
|
159
163
|
end
|
|
160
164
|
|
|
165
|
+
# leptris-ruby#459 exposes the declaration as a
|
|
166
|
+
# Document#children entry; the preamble above owns the
|
|
167
|
+
# emission (a second line here duplicates it — and the
|
|
168
|
+
# 1.9.360 0-arity Declaration#to_xml raises under the
|
|
169
|
+
# options-passing call).
|
|
170
|
+
next if child.is_a?(::Leptris::XML::Declaration)
|
|
171
|
+
|
|
161
172
|
parts << raw_serialize(child, options) << "\n"
|
|
162
173
|
end
|
|
163
174
|
|
|
@@ -20,8 +20,7 @@ module Moxml
|
|
|
20
20
|
|
|
21
21
|
case native
|
|
22
22
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
23
|
-
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment
|
|
24
|
-
CustomizedLeptris::DocumentPI
|
|
23
|
+
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment
|
|
25
24
|
true
|
|
26
25
|
else
|
|
27
26
|
# Parentless elements come only from Iterparse —
|
|
@@ -86,7 +86,7 @@ module Moxml
|
|
|
86
86
|
# binding, so a Text branch first would swallow CDATA nodes.
|
|
87
87
|
case node
|
|
88
88
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
89
|
-
CustomizedLeptris::EntityReference
|
|
89
|
+
CustomizedLeptris::EntityReference
|
|
90
90
|
return node.to_xml
|
|
91
91
|
when ::Leptris::XML::CDATA
|
|
92
92
|
return XmlEmitter.cdata(node.content)
|
|
@@ -1639,7 +1639,7 @@ module Moxml
|
|
|
1639
1639
|
when ::Leptris::XML::Text, CustomizedLeptris::TextSegment then :text
|
|
1640
1640
|
when ::Leptris::XML::Attr, ::Leptris::XML::ResultAttr then :attribute
|
|
1641
1641
|
when ::Leptris::XML::Comment then :comment
|
|
1642
|
-
when ::Leptris::XML::ProcessingInstruction
|
|
1642
|
+
when ::Leptris::XML::ProcessingInstruction then :processing_instruction
|
|
1643
1643
|
when ::Leptris::XML::Document then :document
|
|
1644
1644
|
when ::Leptris::XML::DocType, CustomizedLeptris::Doctype then :doctype
|
|
1645
1645
|
when CustomizedLeptris::Declaration then :declaration
|
|
@@ -1666,7 +1666,6 @@ module Moxml
|
|
|
1666
1666
|
|
|
1667
1667
|
def node_name(node)
|
|
1668
1668
|
return node.root_name if node.is_a?(::Leptris::XML::DocType)
|
|
1669
|
-
return node.target if node.is_a?(CustomizedLeptris::DocumentPI)
|
|
1670
1669
|
|
|
1671
1670
|
if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
|
|
1672
1671
|
# PIs expose no name through the native layer.
|
|
@@ -1684,7 +1683,7 @@ module Moxml
|
|
|
1684
1683
|
def set_node_name(node, name)
|
|
1685
1684
|
node = to_binding(node) if NATIVE_READ_LAYER
|
|
1686
1685
|
case node
|
|
1687
|
-
when ::Leptris::XML::ProcessingInstruction
|
|
1686
|
+
when ::Leptris::XML::ProcessingInstruction then node.target = name
|
|
1688
1687
|
else node.name = name
|
|
1689
1688
|
end
|
|
1690
1689
|
end
|
|
@@ -1698,8 +1697,6 @@ module Moxml
|
|
|
1698
1697
|
CustomizedLeptris::Doctype.new(node.name, node.external_id, node.system_id)
|
|
1699
1698
|
when CustomizedLeptris::EntityReference
|
|
1700
1699
|
CustomizedLeptris::EntityReference.new(node.name)
|
|
1701
|
-
when CustomizedLeptris::DocumentPI
|
|
1702
|
-
CustomizedLeptris::DocumentPI.new(node.target, node.data, node.parent_doc)
|
|
1703
1700
|
else
|
|
1704
1701
|
node.dup
|
|
1705
1702
|
end
|
|
@@ -1796,7 +1793,6 @@ module Moxml
|
|
|
1796
1793
|
assemble_document_children(node)
|
|
1797
1794
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
1798
1795
|
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment,
|
|
1799
|
-
CustomizedLeptris::DocumentPI,
|
|
1800
1796
|
# Terminal node kinds pay an FFI round trip for an empty
|
|
1801
1797
|
# list; unfiltered recursions visit every text node.
|
|
1802
1798
|
::Leptris::XML::Text, ::Leptris::XML::Comment,
|
|
@@ -1832,8 +1828,7 @@ module Moxml
|
|
|
1832
1828
|
case node
|
|
1833
1829
|
when ::Leptris::XML::Element then root_parent(node)
|
|
1834
1830
|
when ::Leptris::XML::Document then nil
|
|
1835
|
-
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype
|
|
1836
|
-
CustomizedLeptris::DocumentPI then node.parent_doc
|
|
1831
|
+
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype then node.parent_doc
|
|
1837
1832
|
when CustomizedLeptris::TextSegment, CustomizedLeptris::EntityReference then node.parent
|
|
1838
1833
|
# Same body as the Element arm by design (frequency
|
|
1839
1834
|
# ordering); the generic kinds are cold.
|
|
@@ -2535,23 +2530,6 @@ module Moxml
|
|
|
2535
2530
|
end
|
|
2536
2531
|
when CustomizedLeptris::EntityReference
|
|
2537
2532
|
marker_text_for(node.parent, node.name)&.unlink
|
|
2538
|
-
when CustomizedLeptris::DocumentPI
|
|
2539
|
-
if NATIVE_DOC_PARTS && node.parent_doc
|
|
2540
|
-
pis = node.parent_doc.processing_instructions
|
|
2541
|
-
index = pis.index do |target, data|
|
|
2542
|
-
target == node.target && data == node.data
|
|
2543
|
-
end
|
|
2544
|
-
if index
|
|
2545
|
-
node.parent_doc.remove_pi(index)
|
|
2546
|
-
node.parent_doc = nil
|
|
2547
|
-
end
|
|
2548
|
-
else
|
|
2549
|
-
raise Moxml::NotImplementedError.new(
|
|
2550
|
-
"libleptris has no document-level PI removal",
|
|
2551
|
-
adapter: :leptris,
|
|
2552
|
-
feature: :remove,
|
|
2553
|
-
)
|
|
2554
|
-
end
|
|
2555
2533
|
else
|
|
2556
2534
|
begin
|
|
2557
2535
|
node.unlink
|
data/lib/moxml/adapter/libxml.rb
CHANGED
|
@@ -244,6 +244,9 @@ module Moxml
|
|
|
244
244
|
return NATIVE_NODE_TYPE_MAP[node.node_type] || :unknown
|
|
245
245
|
end
|
|
246
246
|
return :document if node.is_a?(::LibXML::XML::Document)
|
|
247
|
+
# Attr is a separate class (not ::Node) — attribute-terminal
|
|
248
|
+
# XPath results mint as plain Node without this.
|
|
249
|
+
return :attribute if node.is_a?(::LibXML::XML::Attr)
|
|
247
250
|
|
|
248
251
|
wrapper_type = WRAPPER_NODE_TYPE_MAP[node.class]
|
|
249
252
|
return wrapper_type if wrapper_type
|
|
@@ -949,6 +952,16 @@ module Moxml
|
|
|
949
952
|
native_node = unpatch_node(node)
|
|
950
953
|
return [] unless native_node
|
|
951
954
|
|
|
955
|
+
# libxml-ruby's Document#find returns EMPTY for relative
|
|
956
|
+
# paths (nokogiri/libxml2 evaluate them fine): a relative
|
|
957
|
+
# expression from the document node is equivalent to the
|
|
958
|
+
# absolute form — the document's only name-testable child
|
|
959
|
+
# is the root — so rewrite instead of losing the match.
|
|
960
|
+
if native_node.is_a?(::LibXML::XML::Document) &&
|
|
961
|
+
expression.match?(/\A[A-Za-z_*@]/)
|
|
962
|
+
expression = "/#{expression}"
|
|
963
|
+
end
|
|
964
|
+
|
|
952
965
|
# Build namespace context for LibXML
|
|
953
966
|
# LibXML requires ALL prefixes in the XPath to be registered
|
|
954
967
|
ns_context = build_xpath_namespaces(native_node, namespaces)
|
|
@@ -626,7 +626,11 @@ module Moxml
|
|
|
626
626
|
end
|
|
627
627
|
|
|
628
628
|
def xpath(node, expression, namespaces = nil)
|
|
629
|
-
|
|
629
|
+
# An explicit bindings hash — even EMPTY — makes nokogiri
|
|
630
|
+
# register a prefix context, which disables libxml2's
|
|
631
|
+
# literal xmlns: default-namespace handling. Only pass the
|
|
632
|
+
# hash when the caller actually bound something.
|
|
633
|
+
result = namespaces.nil? || namespaces.empty? ? node.xpath(expression) : node.xpath(expression, namespaces)
|
|
630
634
|
# Adapter contract: Array<native> | LazyNodeSet | scalar
|
|
631
635
|
# (count(), string-length(), boolean functions return
|
|
632
636
|
# scalars). The native node-set passes through lazily —
|
data/lib/moxml/adapter/rexml.rb
CHANGED
|
@@ -604,10 +604,13 @@ module Moxml
|
|
|
604
604
|
end
|
|
605
605
|
|
|
606
606
|
def xpath(node, expression, namespaces = {})
|
|
607
|
+
# XPath.match for every expression: get_elements only
|
|
608
|
+
# handles simple child-name paths — attribute terminals,
|
|
609
|
+
# absolute paths and predicates silently degrade there.
|
|
607
610
|
if namespaces && !namespaces.empty?
|
|
608
|
-
::REXML::XPath.match(node, expression, namespaces)
|
|
611
|
+
::REXML::XPath.match(node, expression, namespaces).to_a
|
|
609
612
|
else
|
|
610
|
-
|
|
613
|
+
::REXML::XPath.match(node, expression).to_a
|
|
611
614
|
end
|
|
612
615
|
rescue ::REXML::ParseException => e
|
|
613
616
|
raise Moxml::XPathError.new(
|
|
@@ -142,6 +142,16 @@ module Moxml
|
|
|
142
142
|
# Remove the attribute named by XML-correct resolution (see
|
|
143
143
|
# #assign for the name semantics).
|
|
144
144
|
#
|
|
145
|
+
# #378: a bare name that matches no no-namespace attribute falls
|
|
146
|
+
# back to the SINGLE namespaced attribute carrying that local
|
|
147
|
+
# name — callers spelling the local part (the name
|
|
148
|
+
# attribute_nodes-style listings expose) expect removal, and the
|
|
149
|
+
# silent no-op broke remediation flows. Bare-name resolution
|
|
150
|
+
# keeps Namespaces 1.0 §5.2 semantics for reads; removal widens
|
|
151
|
+
# only when unambiguous. Several prefixed spellings sharing the
|
|
152
|
+
# local name remove nothing — guessing a prefix would corrupt
|
|
153
|
+
# documents.
|
|
154
|
+
#
|
|
145
155
|
# @return [Moxml::Attribute, nil] the removed attribute
|
|
146
156
|
def remove(element, name)
|
|
147
157
|
name = name.to_s
|
|
@@ -153,6 +163,8 @@ module Moxml
|
|
|
153
163
|
end
|
|
154
164
|
|
|
155
165
|
attr = resolve(element, name)
|
|
166
|
+
attr ||= resolve_unique_local(element, name) unless name.include?(":")
|
|
167
|
+
|
|
156
168
|
return nil unless attr
|
|
157
169
|
|
|
158
170
|
adapter.remove_attribute_native(attr.native)
|
|
@@ -160,6 +172,17 @@ module Moxml
|
|
|
160
172
|
attr
|
|
161
173
|
end
|
|
162
174
|
|
|
175
|
+
# The single namespaced attribute whose local name matches, or
|
|
176
|
+
# nil when none or several do (see #remove).
|
|
177
|
+
def resolve_unique_local(element, local)
|
|
178
|
+
matches = element.attributes.select do |attr|
|
|
179
|
+
attr_name = attr.name
|
|
180
|
+
local_name(attr_name) == local &&
|
|
181
|
+
!attribute_uri(element, attr, attr_name).nil?
|
|
182
|
+
end
|
|
183
|
+
matches.one? ? matches.first : nil
|
|
184
|
+
end
|
|
185
|
+
|
|
163
186
|
# XPath 1.0 attribute node test, sharing the resolver's
|
|
164
187
|
# expanded-name semantics:
|
|
165
188
|
#
|
data/lib/moxml/element.rb
CHANGED
|
@@ -233,11 +233,15 @@ module Moxml
|
|
|
233
233
|
adapter.attachments.set(@native, :children_source_docs, merged)
|
|
234
234
|
end
|
|
235
235
|
|
|
236
|
+
# Removes the attribute named by XML-correct resolution — a
|
|
237
|
+
# qualified name ("p:x"), or a bare name (the no-namespace
|
|
238
|
+
# attribute, falling back to the single namespaced attribute
|
|
239
|
+
# carrying that local name when no bare one exists, #378).
|
|
240
|
+
#
|
|
241
|
+
# @return [Moxml::Attribute, nil] the removed attribute, nil
|
|
242
|
+
# when nothing matched (nokogiri-compatible)
|
|
236
243
|
def remove_attribute(name)
|
|
237
|
-
# Both remove paths end in invalidate_attribute_cache!, which
|
|
238
|
-
# bumps the generation.
|
|
239
244
|
Moxml::AttributeResolver.remove(self, name)
|
|
240
|
-
self
|
|
241
245
|
end
|
|
242
246
|
|
|
243
247
|
# Append a raw XML fragment's top-level nodes as children.
|
data/lib/moxml/version.rb
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# Cross-adapter XPath idioms that nokogiri consumers rely on:
|
|
6
|
+
#
|
|
7
|
+
# - The literal xmlns: prefix resolves against the default namespace
|
|
8
|
+
# (the libxml2 quirk that is nokogiri's documented way to match
|
|
9
|
+
# default-namespaced elements; leptris carries it since engine
|
|
10
|
+
# 1.9.359 / leptris-ruby#469; moxml's own engine always has).
|
|
11
|
+
# - Attribute-terminal steps work in every path position
|
|
12
|
+
# (leptris-ruby#458's table: //a/@id, //@id, r/a/@id,
|
|
13
|
+
# //*[f()]/@id — fixed by the engine's VM path work).
|
|
14
|
+
RSpec.describe "XPath idioms across adapters" do
|
|
15
|
+
let(:default_ns_xml) do
|
|
16
|
+
'<svgmap xmlns="http://www.example.org/doc"><target href="a"/></svgmap>'
|
|
17
|
+
end
|
|
18
|
+
let(:plain_xml) { '<r><a id="x"><b/></a></r>' }
|
|
19
|
+
|
|
20
|
+
Moxml::Adapter::AVAILABLE_ADAPTERS.each do |adapter_name|
|
|
21
|
+
next if adapter_name == :headed_ox # same engine as :ox
|
|
22
|
+
|
|
23
|
+
context "with #{adapter_name}" do
|
|
24
|
+
let(:ctx) { Moxml.new(adapter_name) }
|
|
25
|
+
|
|
26
|
+
before do
|
|
27
|
+
if adapter_name == :leptris &&
|
|
28
|
+
Gem::Version.new(Leptris::VERSION) < Gem::Version.new("1.9.359.0")
|
|
29
|
+
skip "binding #{Leptris::VERSION} predates the xmlns: fix (1.9.359)"
|
|
30
|
+
end
|
|
31
|
+
if adapter_name == :oga
|
|
32
|
+
# oga's own Ruby XPath engine has no xmlns: default-ns
|
|
33
|
+
# support (third-party; explicit bindings work).
|
|
34
|
+
skip "oga's XPath engine does not implement the xmlns: idiom"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "matches default-namespaced elements via the xmlns: prefix" do
|
|
39
|
+
doc = ctx.parse(default_ns_xml)
|
|
40
|
+
targets = doc.root.xpath("./xmlns:target")
|
|
41
|
+
expect(targets.length).to eq(1)
|
|
42
|
+
expect(targets.first.name).to eq("target")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "keeps explicit namespace bindings winning over the idiom" do
|
|
46
|
+
doc = ctx.parse(default_ns_xml)
|
|
47
|
+
targets = doc.root.xpath("./xmlns:target", "xmlns" => "urn:bogus")
|
|
48
|
+
expect(targets).to be_empty
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "resolves attribute-terminal steps in every path position" do
|
|
52
|
+
doc = ctx.parse(plain_xml)
|
|
53
|
+
expect(doc.xpath("//a/@id").first&.value).to eq("x")
|
|
54
|
+
expect(doc.xpath("//@id").first&.value).to eq("x")
|
|
55
|
+
expect(doc.xpath("r/a/@id").first&.value).to eq("x")
|
|
56
|
+
expect(
|
|
57
|
+
doc.xpath("//*[local-name()='a']/@id").first&.value,
|
|
58
|
+
).to eq("x")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -126,6 +126,41 @@ RSpec.shared_examples "Moxml::Attribute" do
|
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
describe "removing by local name (#378)" do
|
|
130
|
+
before do
|
|
131
|
+
element.add_namespace("q", "http://x.org")
|
|
132
|
+
element["q:type"] = "namespaced"
|
|
133
|
+
element["type"] = "bare"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "removes the single namespaced attribute carrying that local name" do
|
|
137
|
+
element.remove_attribute("type")
|
|
138
|
+
element.remove_attribute("type")
|
|
139
|
+
|
|
140
|
+
expect(element["q:type"]).to be_nil
|
|
141
|
+
expect(element["type"]).to be_nil
|
|
142
|
+
expect(element.attributes).to be_empty
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "returns the removed attribute, nil when nothing matched" do
|
|
146
|
+
removed = element.remove_attribute("type")
|
|
147
|
+
|
|
148
|
+
expect(removed).to be_a(described_class)
|
|
149
|
+
expect(element["type"]).to be_nil
|
|
150
|
+
expect(element.remove_attribute("nonexistent")).to be_nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "removes nothing when several prefixes share the local name" do
|
|
154
|
+
element.remove_attribute("type")
|
|
155
|
+
element.add_namespace("other", "http://other.org")
|
|
156
|
+
element["other:type"] = "second"
|
|
157
|
+
|
|
158
|
+
expect(element.remove_attribute("type")).to be_nil
|
|
159
|
+
expect(element["q:type"]).to eq("namespaced")
|
|
160
|
+
expect(element["other:type"]).to eq("second")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
129
164
|
describe "manipulation" do
|
|
130
165
|
before do
|
|
131
166
|
element["test"] = "value"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# leptris-ruby#459 exposes the declaration as a Document#children
|
|
6
|
+
# entry (binding 1.9.360). The serialize child walk must skip it —
|
|
7
|
+
# the composed preamble owns the emission — or declaration-bearing
|
|
8
|
+
# round trips duplicate the line (1.9.359) or raise (1.9.360's
|
|
9
|
+
# 0-arity Declaration#to_xml under the options-passing call).
|
|
10
|
+
RSpec.describe "leptris document declaration child" do
|
|
11
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
12
|
+
let(:xml) { %(<?xml version="1.0" encoding="UTF-8"?>\n<documents></documents>) }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
unless Gem::Version.new(Leptris::VERSION) >= Gem::Version.new("1.9.360.0")
|
|
16
|
+
skip "binding #{Leptris::VERSION} predates the declaration child (1.9.360)"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "round trips byte-identical with exactly one declaration" do
|
|
21
|
+
doc = ctx.parse(xml)
|
|
22
|
+
out = doc.to_xml
|
|
23
|
+
|
|
24
|
+
expect(out).to eq("#{xml}\n")
|
|
25
|
+
expect(out.scan("<?xml").length).to eq(1)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "never lists the declaration among document children" do
|
|
29
|
+
doc = ctx.parse(xml)
|
|
30
|
+
first = doc.children.to_a
|
|
31
|
+
second = doc.children.to_a
|
|
32
|
+
|
|
33
|
+
expect(first.map(&:node_type)).to eq(%i[element])
|
|
34
|
+
expect(second.map(&:node_type)).to eq(first.map(&:node_type))
|
|
35
|
+
expect(doc.to_xml).to eq("#{xml}\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "keeps declaration-less documents unchanged" do
|
|
39
|
+
doc = ctx.parse("<r/>")
|
|
40
|
+
expect(doc.to_xml).to eq("<r></r>\n")
|
|
41
|
+
end
|
|
42
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moxml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.138
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Moxml is a unified XML manipulation library that provides a common API
|
|
@@ -134,7 +134,6 @@ files:
|
|
|
134
134
|
- lib/moxml/adapter/customized_leptris.rb
|
|
135
135
|
- lib/moxml/adapter/customized_leptris/declaration.rb
|
|
136
136
|
- lib/moxml/adapter/customized_leptris/doctype.rb
|
|
137
|
-
- lib/moxml/adapter/customized_leptris/document_pi.rb
|
|
138
137
|
- lib/moxml/adapter/customized_leptris/entity_reference.rb
|
|
139
138
|
- lib/moxml/adapter/customized_leptris/text_segment.rb
|
|
140
139
|
- lib/moxml/adapter/customized_libxml.rb
|
|
@@ -314,6 +313,7 @@ files:
|
|
|
314
313
|
- spec/consistency/eol_normalization_spec.rb
|
|
315
314
|
- spec/consistency/html_doctype_spec.rb
|
|
316
315
|
- spec/consistency/round_trip_spec.rb
|
|
316
|
+
- spec/consistency/xpath_idioms_spec.rb
|
|
317
317
|
- spec/consistency/xslt_spec.rb
|
|
318
318
|
- spec/examples/README.md
|
|
319
319
|
- spec/examples/attribute_examples_spec.rb
|
|
@@ -453,6 +453,7 @@ files:
|
|
|
453
453
|
- spec/moxml/adapter/leptris_attribute_snapshot_spec.rb
|
|
454
454
|
- spec/moxml/adapter/leptris_binding_cap_spec.rb
|
|
455
455
|
- spec/moxml/adapter/leptris_doc_parts_spec.rb
|
|
456
|
+
- spec/moxml/adapter/leptris_document_declaration_spec.rb
|
|
456
457
|
- spec/moxml/adapter/leptris_parse_skips_spec.rb
|
|
457
458
|
- spec/moxml/adapter/leptris_record_driver_spec.rb
|
|
458
459
|
- spec/moxml/adapter/leptris_sax_dup_attr_spec.rb
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Moxml
|
|
4
|
-
module Adapter
|
|
5
|
-
module CustomizedLeptris
|
|
6
|
-
# Document-level processing instruction. libleptris stores these
|
|
7
|
-
# outside the element tree (a flat, document-ordered list with no
|
|
8
|
-
# prolog/epilog anchoring — its serializer emits them all before
|
|
9
|
-
# the root), reachable through Document#processing_instructions
|
|
10
|
-
# as [target, data] pairs. This pseudo-native gives the pairs a
|
|
11
|
-
# node identity so the moxml contract can list them as document
|
|
12
|
-
# children (and answer mutations: creation routes native via
|
|
13
|
-
# add_pi; removal has no engine entry).
|
|
14
|
-
class DocumentPI
|
|
15
|
-
attr_accessor :target, :data, :parent_doc
|
|
16
|
-
alias content data
|
|
17
|
-
alias content= data=
|
|
18
|
-
|
|
19
|
-
def initialize(target, data = "", parent_doc = nil)
|
|
20
|
-
@target = target
|
|
21
|
-
@data = data.to_s
|
|
22
|
-
@parent_doc = parent_doc
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def to_xml
|
|
26
|
-
data.empty? ? "<?#{target}?>" : "<?#{target} #{data}?>"
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def ==(other)
|
|
30
|
-
other.is_a?(self.class) && target == other.target && data == other.data
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|