moxml 0.5.4 → 0.5.6
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/release.yml +3 -1
- data/lib/moxml/adapter/base.rb +8 -0
- data/lib/moxml/adapter/customized_leptris/document_pi.rb +34 -0
- data/lib/moxml/adapter/customized_leptris.rb +3 -1
- data/lib/moxml/adapter/leptris.rb +56 -9
- data/lib/moxml/adapter/libxml.rb +4 -0
- data/lib/moxml/adapter/ox.rb +4 -0
- data/lib/moxml/config.rb +18 -2
- data/lib/moxml/node.rb +5 -5
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/adapter/leptris_spec.rb +48 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f2b8ff3a745345a5d0462bd246f0ee8b463713fcad2dcf99590813ffd49aa27
|
|
4
|
+
data.tar.gz: 8930bcdaee831095c3a5b576d4f19a36591fd1a3884e0fb1f0aebb1c4c91485f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7c64568fa5c5a013bee81b37e9329590817c5d0db567c6f8c609de48e6782d75544e10bfe00929d0768e04a10591eef54c3a79e1ed3acfab2aa34dc1a11eb64
|
|
7
|
+
data.tar.gz: 6eebca4d31617a36da0df9f471bafc7085556b7216af8bb6138df0c9e5c2d8982fdfd6bd8ad897a3c88bdc06367c30fef9ccc9418575480ce0b97546a40af38c
|
|
@@ -30,6 +30,8 @@ jobs:
|
|
|
30
30
|
with:
|
|
31
31
|
next_version: ${{ github.event.inputs.next_version }}
|
|
32
32
|
gated: ${{ inputs.gated == true }}
|
|
33
|
+
# No rubygems-api-key: the gem publishes through its configured
|
|
34
|
+
# RubyGems OIDC trusted publisher. pat_token covers the version
|
|
35
|
+
# bump push to main.
|
|
33
36
|
secrets:
|
|
34
|
-
rubygems-api-key: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
|
|
35
37
|
pat_token: ${{ secrets.GITHUB_TOKEN }}
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -173,6 +173,14 @@ namespace_validation_mode: :strict)
|
|
|
173
173
|
node
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
+
# Whether children() results need per-child patch_node
|
|
177
|
+
# rewriting. Adapters whose natives arrive pre-wrapped (all but
|
|
178
|
+
# ox and libxml) answer false so the wrapper layer can skip
|
|
179
|
+
# the identity map over every child list.
|
|
180
|
+
def patches_children?
|
|
181
|
+
false
|
|
182
|
+
end
|
|
183
|
+
|
|
176
184
|
# Whether the subtree at native can contain entity markers.
|
|
177
185
|
# Marker-tracking adapters override this so the post-serialize
|
|
178
186
|
# restore can skip its full-output scans on marker-free
|
|
@@ -0,0 +1,34 @@
|
|
|
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.
|
|
13
|
+
class DocumentPI
|
|
14
|
+
attr_accessor :target, :data, :parent_doc
|
|
15
|
+
alias content data
|
|
16
|
+
alias content= data=
|
|
17
|
+
|
|
18
|
+
def initialize(target, data = "", parent_doc = nil)
|
|
19
|
+
@target = target
|
|
20
|
+
@data = data.to_s
|
|
21
|
+
@parent_doc = parent_doc
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_xml
|
|
25
|
+
data.empty? ? "<?#{target}?>" : "<?#{target} #{data}?>"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ==(other)
|
|
29
|
+
other.is_a?(self.class) && target == other.target && data == other.data
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Moxml
|
|
4
4
|
module Adapter
|
|
5
5
|
# Pure-Ruby node kinds that libleptris has no native representation
|
|
6
|
-
# for: XML declarations, programmatic DOCTYPEs,
|
|
6
|
+
# for: XML declarations, programmatic DOCTYPEs, entity references,
|
|
7
|
+
# and document-level processing instructions.
|
|
7
8
|
# The adapter stores them via NativeAttachment and serializes them
|
|
8
9
|
# from #to_xml.
|
|
9
10
|
module CustomizedLeptris
|
|
@@ -11,6 +12,7 @@ module Moxml
|
|
|
11
12
|
autoload :Doctype, "moxml/adapter/customized_leptris/doctype"
|
|
12
13
|
autoload :EntityReference, "moxml/adapter/customized_leptris/entity_reference"
|
|
13
14
|
autoload :TextSegment, "moxml/adapter/customized_leptris/text_segment"
|
|
15
|
+
autoload :DocumentPI, "moxml/adapter/customized_leptris/document_pi"
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
18
|
end
|
|
@@ -215,7 +215,8 @@ module Moxml
|
|
|
215
215
|
def entity_bearing?(native)
|
|
216
216
|
case native
|
|
217
217
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
218
|
-
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment
|
|
218
|
+
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment,
|
|
219
|
+
CustomizedLeptris::DocumentPI
|
|
219
220
|
true
|
|
220
221
|
else
|
|
221
222
|
doc = native.document
|
|
@@ -231,7 +232,7 @@ module Moxml
|
|
|
231
232
|
when CustomizedLeptris::EntityReference then :entity_reference
|
|
232
233
|
when ::Leptris::XML::CDATA then :cdata
|
|
233
234
|
when ::Leptris::XML::Comment then :comment
|
|
234
|
-
when ::Leptris::XML::ProcessingInstruction then :processing_instruction
|
|
235
|
+
when ::Leptris::XML::ProcessingInstruction, CustomizedLeptris::DocumentPI then :processing_instruction
|
|
235
236
|
when ::Leptris::XML::Text, CustomizedLeptris::TextSegment then :text
|
|
236
237
|
when ::Leptris::XML::Element then :element
|
|
237
238
|
when ::Leptris::XML::Attr then :attribute
|
|
@@ -241,13 +242,14 @@ module Moxml
|
|
|
241
242
|
|
|
242
243
|
def node_name(node)
|
|
243
244
|
return node.root_name if node.is_a?(::Leptris::XML::DocType)
|
|
245
|
+
return node.target if node.is_a?(CustomizedLeptris::DocumentPI)
|
|
244
246
|
|
|
245
247
|
node.name.to_s.dup.force_encoding("UTF-8")
|
|
246
248
|
end
|
|
247
249
|
|
|
248
250
|
def set_node_name(node, name)
|
|
249
251
|
case node
|
|
250
|
-
when ::Leptris::XML::ProcessingInstruction then node.target = name
|
|
252
|
+
when ::Leptris::XML::ProcessingInstruction, CustomizedLeptris::DocumentPI then node.target = name
|
|
251
253
|
else node.name = name
|
|
252
254
|
end
|
|
253
255
|
end
|
|
@@ -260,6 +262,8 @@ module Moxml
|
|
|
260
262
|
CustomizedLeptris::Doctype.new(node.name, node.external_id, node.system_id)
|
|
261
263
|
when CustomizedLeptris::EntityReference
|
|
262
264
|
CustomizedLeptris::EntityReference.new(node.name)
|
|
265
|
+
when CustomizedLeptris::DocumentPI
|
|
266
|
+
CustomizedLeptris::DocumentPI.new(node.target, node.data, node.parent_doc)
|
|
263
267
|
else
|
|
264
268
|
node.dup
|
|
265
269
|
end
|
|
@@ -270,7 +274,13 @@ module Moxml
|
|
|
270
274
|
when ::Leptris::XML::Document
|
|
271
275
|
assemble_document_children(node)
|
|
272
276
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
273
|
-
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment
|
|
277
|
+
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment,
|
|
278
|
+
CustomizedLeptris::DocumentPI,
|
|
279
|
+
# Terminal node kinds pay an FFI round trip for an empty
|
|
280
|
+
# list; unfiltered recursions visit every text node.
|
|
281
|
+
::Leptris::XML::Text, ::Leptris::XML::Comment,
|
|
282
|
+
::Leptris::XML::CDATA, ::Leptris::XML::ProcessingInstruction,
|
|
283
|
+
::Leptris::XML::Attr
|
|
274
284
|
[]
|
|
275
285
|
else
|
|
276
286
|
natives = node.children.to_a
|
|
@@ -316,7 +326,8 @@ module Moxml
|
|
|
316
326
|
def parent(node)
|
|
317
327
|
case node
|
|
318
328
|
when ::Leptris::XML::Document then nil
|
|
319
|
-
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype
|
|
329
|
+
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
330
|
+
CustomizedLeptris::DocumentPI then node.parent_doc
|
|
320
331
|
when CustomizedLeptris::TextSegment, CustomizedLeptris::EntityReference then node.parent
|
|
321
332
|
else
|
|
322
333
|
# The binding reports the root element as parentless; the
|
|
@@ -442,6 +453,12 @@ module Moxml
|
|
|
442
453
|
attachments.delete(node.parent_doc, :doctype) if node.parent_doc
|
|
443
454
|
when CustomizedLeptris::EntityReference
|
|
444
455
|
marker_text_for(node.parent, node.name)&.unlink
|
|
456
|
+
when CustomizedLeptris::DocumentPI
|
|
457
|
+
raise Moxml::NotImplementedError.new(
|
|
458
|
+
"libleptris has no document-level PI removal",
|
|
459
|
+
adapter: :leptris,
|
|
460
|
+
feature: :remove,
|
|
461
|
+
)
|
|
445
462
|
else
|
|
446
463
|
node.unlink
|
|
447
464
|
end
|
|
@@ -736,7 +753,7 @@ module Moxml
|
|
|
736
753
|
# binding, so a Text branch first would swallow CDATA nodes.
|
|
737
754
|
case node
|
|
738
755
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
739
|
-
CustomizedLeptris::EntityReference
|
|
756
|
+
CustomizedLeptris::EntityReference, CustomizedLeptris::DocumentPI
|
|
740
757
|
return node.to_xml
|
|
741
758
|
when ::Leptris::XML::CDATA
|
|
742
759
|
return XmlEmitter.cdata(node.content)
|
|
@@ -858,9 +875,7 @@ module Moxml
|
|
|
858
875
|
native = native_doctype_xml(doc)
|
|
859
876
|
parts << native if native
|
|
860
877
|
|
|
861
|
-
(doc
|
|
862
|
-
parts << (data.to_s.empty? ? "<?#{target}?>" : "<?#{target} #{data}?>")
|
|
863
|
-
end
|
|
878
|
+
document_pi_nodes(doc).each { |pi| parts << pi.to_xml }
|
|
864
879
|
|
|
865
880
|
parts << raw_serialize(doc.root, options) if doc.root
|
|
866
881
|
|
|
@@ -930,6 +945,21 @@ module Moxml
|
|
|
930
945
|
attachments.get(native, :had_source_declaration) ? true : false
|
|
931
946
|
end
|
|
932
947
|
|
|
948
|
+
# Document-level PI pseudo-nodes, materialized once from the C
|
|
949
|
+
# list and cached per document: children and serialization read
|
|
950
|
+
# the same objects, so wrapper mutations round-trip. Build the
|
|
951
|
+
# cache BEFORE appending a PI with add_pi, or the C-side
|
|
952
|
+
# addition would be double-counted.
|
|
953
|
+
def document_pi_nodes(doc)
|
|
954
|
+
attachments.get(doc, :doc_pi_nodes) || begin
|
|
955
|
+
nodes = doc.processing_instructions.map do |(target, data)|
|
|
956
|
+
CustomizedLeptris::DocumentPI.new(target, data, doc)
|
|
957
|
+
end
|
|
958
|
+
attachments.set(doc, :doc_pi_nodes, nodes)
|
|
959
|
+
nodes
|
|
960
|
+
end
|
|
961
|
+
end
|
|
962
|
+
|
|
933
963
|
def assemble_document_children(doc)
|
|
934
964
|
children = []
|
|
935
965
|
|
|
@@ -939,6 +969,15 @@ module Moxml
|
|
|
939
969
|
doctype_wrapper = attachments.get(doc, :doctype)
|
|
940
970
|
children << doctype_wrapper if doctype_wrapper
|
|
941
971
|
|
|
972
|
+
# Document-level PIs live outside the element tree in
|
|
973
|
+
# libleptris (a flat list; its serializer emits them before
|
|
974
|
+
# the root). Listed after the DOCTYPE to match the order
|
|
975
|
+
# serialize_document emits, so children and serialization
|
|
976
|
+
# agree. Epilog anchoring is not representable in the C
|
|
977
|
+
# model — a known divergence from the Nokogiri-shaped
|
|
978
|
+
# contract the other adapters expose.
|
|
979
|
+
children.concat(document_pi_nodes(doc))
|
|
980
|
+
|
|
942
981
|
children << doc.root if doc.root
|
|
943
982
|
|
|
944
983
|
texts = attachments.get(doc, :document_text)
|
|
@@ -961,7 +1000,15 @@ module Moxml
|
|
|
961
1000
|
when ::Leptris::XML::Element
|
|
962
1001
|
doc.root = child
|
|
963
1002
|
when ::Leptris::XML::ProcessingInstruction
|
|
1003
|
+
document_pi_nodes(doc)
|
|
964
1004
|
doc.add_pi(child.target, child.content.to_s)
|
|
1005
|
+
document_pi_nodes(doc) << CustomizedLeptris::DocumentPI.new(
|
|
1006
|
+
child.target, child.content.to_s, doc
|
|
1007
|
+
)
|
|
1008
|
+
when CustomizedLeptris::DocumentPI
|
|
1009
|
+
document_pi_nodes(doc)
|
|
1010
|
+
doc.add_pi(child.target, child.data)
|
|
1011
|
+
document_pi_nodes(doc) << child
|
|
965
1012
|
when ::Leptris::XML::Text
|
|
966
1013
|
texts = attachments.get(doc, :document_text) || []
|
|
967
1014
|
texts << child
|
data/lib/moxml/adapter/libxml.rb
CHANGED
data/lib/moxml/adapter/ox.rb
CHANGED
data/lib/moxml/config.rb
CHANGED
|
@@ -11,10 +11,13 @@ module Moxml
|
|
|
11
11
|
# when the installed leptris supports programmatic document
|
|
12
12
|
# construction (Leptris::XML::Document.create); otherwise moxml
|
|
13
13
|
# falls back to FALLBACK_ADAPTER so released-gem environments keep
|
|
14
|
-
# working.
|
|
14
|
+
# working. Under Opal the default is oga only when the vendored
|
|
15
|
+
# pure-Ruby fork was compiled into the bundle — stock oga requires
|
|
16
|
+
# its C extension and cannot load there; otherwise rexml.
|
|
15
17
|
PREFERRED_ADAPTER = :leptris
|
|
16
18
|
FALLBACK_ADAPTER = :nokogiri
|
|
17
19
|
OPAL_DEFAULT_ADAPTER = :oga
|
|
20
|
+
OPAL_FALLBACK_ADAPTER = :rexml
|
|
18
21
|
|
|
19
22
|
# Entity loading modes:
|
|
20
23
|
# - :required - Must load entities, raise error if unavailable (default)
|
|
@@ -35,13 +38,26 @@ module Moxml
|
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
def runtime_default_adapter
|
|
38
|
-
return
|
|
41
|
+
return opal_runtime_adapter if RUBY_ENGINE == "opal"
|
|
39
42
|
|
|
40
43
|
return PREFERRED_ADAPTER if leptris_preferred_available?
|
|
41
44
|
|
|
42
45
|
detect_loaded_adapter || FALLBACK_ADAPTER
|
|
43
46
|
end
|
|
44
47
|
|
|
48
|
+
# Stock oga requires its C extension, so under Opal the oga
|
|
49
|
+
# adapter can only come from the vendored pure-Ruby fork — a
|
|
50
|
+
# repo-only artifact the released gem does not ship. Bundles
|
|
51
|
+
# that compiled it in (moxml's own harness) keep oga; everyone
|
|
52
|
+
# else gets the rexml adapter, whose Opal compat ships in-gem.
|
|
53
|
+
# `defined?` is safe here: this branch never runs under MRI,
|
|
54
|
+
# where it would trigger autoload resolution.
|
|
55
|
+
def opal_runtime_adapter
|
|
56
|
+
return OPAL_DEFAULT_ADAPTER if defined?(Moxml::Adapter::Oga)
|
|
57
|
+
|
|
58
|
+
OPAL_FALLBACK_ADAPTER
|
|
59
|
+
end
|
|
60
|
+
|
|
45
61
|
# True when the leptris gem is installed AND new enough for the
|
|
46
62
|
# adapter (it needs programmatic document construction, which
|
|
47
63
|
# released 1.1.x lacks). Memoized: the require probe runs once.
|
data/lib/moxml/node.rb
CHANGED
|
@@ -38,11 +38,11 @@ module Moxml
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def children
|
|
41
|
-
@children ||=
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
self
|
|
45
|
-
|
|
41
|
+
@children ||= begin
|
|
42
|
+
natives = adapter.children(@native)
|
|
43
|
+
natives = natives.map { adapter.patch_node(_1, @native) } if adapter.patches_children?
|
|
44
|
+
NodeSet.new(natives, context, self)
|
|
45
|
+
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def next_sibling
|
data/lib/moxml/version.rb
CHANGED
|
@@ -81,6 +81,54 @@ RSpec.describe Moxml::Adapter::Leptris do
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
describe "document-level processing instructions" do
|
|
85
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
86
|
+
|
|
87
|
+
it "lists document PIs as children with the root" do
|
|
88
|
+
doc = ctx.parse('<?xml version="1.0"?><?pi-prolog before?><root/><?pi-epilog after?>')
|
|
89
|
+
kids = doc.children.to_a
|
|
90
|
+
|
|
91
|
+
expect(kids.map(&:class)).to eq(
|
|
92
|
+
[Moxml::ProcessingInstruction, Moxml::ProcessingInstruction, Moxml::Element],
|
|
93
|
+
)
|
|
94
|
+
expect(kids.select(&:processing_instruction?).map(&:target)).to eq(%w[pi-prolog pi-epilog])
|
|
95
|
+
expect(kids[0].content).to eq("before")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "round-trips mutations and additions through serialization" do
|
|
99
|
+
doc = ctx.parse("<?pi-original x?><root/>")
|
|
100
|
+
pi = doc.children.to_a[0]
|
|
101
|
+
pi.target = "renamed"
|
|
102
|
+
pi.content = "changed"
|
|
103
|
+
expect(doc.to_xml).to include("<?renamed changed?>")
|
|
104
|
+
|
|
105
|
+
doc.add_child(doc.create_processing_instruction("added", "now"))
|
|
106
|
+
expect(doc.to_xml).to include("<?added now?>")
|
|
107
|
+
expect(doc.children.to_a.select(&:processing_instruction?).map(&:target)).to eq(%w[renamed added])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "serializes children and document output in agreement" do
|
|
111
|
+
# libleptris stores document PIs as one flat pre-root list (no
|
|
112
|
+
# epilog anchoring); children and to_xml must at least agree.
|
|
113
|
+
doc = ctx.parse('<?xml version="1.0"?><?pi-a 1?><root/><?pi-b 2?>')
|
|
114
|
+
from_children = doc.children.to_a.select(&:processing_instruction?)
|
|
115
|
+
.map(&:to_xml).join
|
|
116
|
+
from_document = doc.to_xml.sub(%r{\A<\?xml[^>]*\?>}, "")
|
|
117
|
+
|
|
118
|
+
expect(from_document).to start_with(from_children)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "reports the tracked native for a re-added document PI" do
|
|
122
|
+
doc = ctx.parse("<?pi-src orig?><root/>")
|
|
123
|
+
moved = doc.children.to_a[0]
|
|
124
|
+
target_doc = ctx.parse("<other/>")
|
|
125
|
+
target_doc.add_child(moved)
|
|
126
|
+
|
|
127
|
+
expect(target_doc.to_xml).to include("<?pi-src orig?>")
|
|
128
|
+
expect(target_doc.children.to_a.select(&:processing_instruction?).map(&:target)).to include("pi-src")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
84
132
|
describe "entity-marker tracking" do
|
|
85
133
|
let(:ctx) { Moxml.new(:leptris) }
|
|
86
134
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -123,6 +123,7 @@ files:
|
|
|
123
123
|
- lib/moxml/adapter/customized_leptris.rb
|
|
124
124
|
- lib/moxml/adapter/customized_leptris/declaration.rb
|
|
125
125
|
- lib/moxml/adapter/customized_leptris/doctype.rb
|
|
126
|
+
- lib/moxml/adapter/customized_leptris/document_pi.rb
|
|
126
127
|
- lib/moxml/adapter/customized_leptris/entity_reference.rb
|
|
127
128
|
- lib/moxml/adapter/customized_leptris/text_segment.rb
|
|
128
129
|
- lib/moxml/adapter/customized_libxml.rb
|