moxml 0.5.5 → 0.5.7
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/customized_leptris/document_pi.rb +34 -0
- data/lib/moxml/adapter/customized_leptris.rb +3 -1
- data/lib/moxml/adapter/leptris.rb +59 -12
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/adapter/leptris_spec.rb +58 -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: c0d0117dfa9a9f81f628f2200d09b21d93d148a1dd4d0614c98749c90cdb569e
|
|
4
|
+
data.tar.gz: 85de05e0fe6d7d596b8e39508adbcd98c312b63d16d10eb146caab8f147cdb08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a57ef921cc44f3706e541abce2bef21b5b9f91066d9bc3528be802a373d57a488fb36825bf13dcf083aa88572fe1d2618ddbbb0d4ebec9a3de93515006698974
|
|
7
|
+
data.tar.gz: db325b0220a05b14bc2e037250c75319a60be30e652d05a1519ea0d26b5ff3afa55031d467312e81388442cb4f172513ef64cfb42be37ddca4c9da4d46704e97
|
|
@@ -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 }}
|
|
@@ -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
|
|
@@ -271,6 +275,7 @@ module Moxml
|
|
|
271
275
|
assemble_document_children(node)
|
|
272
276
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
273
277
|
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment,
|
|
278
|
+
CustomizedLeptris::DocumentPI,
|
|
274
279
|
# Terminal node kinds pay an FFI round trip for an empty
|
|
275
280
|
# list; unfiltered recursions visit every text node.
|
|
276
281
|
::Leptris::XML::Text, ::Leptris::XML::Comment,
|
|
@@ -321,7 +326,8 @@ module Moxml
|
|
|
321
326
|
def parent(node)
|
|
322
327
|
case node
|
|
323
328
|
when ::Leptris::XML::Document then nil
|
|
324
|
-
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype
|
|
329
|
+
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
330
|
+
CustomizedLeptris::DocumentPI then node.parent_doc
|
|
325
331
|
when CustomizedLeptris::TextSegment, CustomizedLeptris::EntityReference then node.parent
|
|
326
332
|
else
|
|
327
333
|
# The binding reports the root element as parentless; the
|
|
@@ -447,6 +453,12 @@ module Moxml
|
|
|
447
453
|
attachments.delete(node.parent_doc, :doctype) if node.parent_doc
|
|
448
454
|
when CustomizedLeptris::EntityReference
|
|
449
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
|
+
)
|
|
450
462
|
else
|
|
451
463
|
node.unlink
|
|
452
464
|
end
|
|
@@ -741,7 +753,7 @@ module Moxml
|
|
|
741
753
|
# binding, so a Text branch first would swallow CDATA nodes.
|
|
742
754
|
case node
|
|
743
755
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
744
|
-
CustomizedLeptris::EntityReference
|
|
756
|
+
CustomizedLeptris::EntityReference, CustomizedLeptris::DocumentPI
|
|
745
757
|
return node.to_xml
|
|
746
758
|
when ::Leptris::XML::CDATA
|
|
747
759
|
return XmlEmitter.cdata(node.content)
|
|
@@ -847,6 +859,11 @@ module Moxml
|
|
|
847
859
|
# only walks the root subtree, so declaration, DOCTYPE, PIs and
|
|
848
860
|
# document-level text are assembled around it explicitly.
|
|
849
861
|
def serialize_document(doc, options)
|
|
862
|
+
# Nokogiri's document shape: every top-level part is
|
|
863
|
+
# newline-terminated, at any indent — declaration, DOCTYPE,
|
|
864
|
+
# document PIs, the root element, trailing newline after it.
|
|
865
|
+
# Document-level text is content, not structure: no added
|
|
866
|
+
# newline.
|
|
850
867
|
parts = []
|
|
851
868
|
|
|
852
869
|
include_decl = !options[:no_declaration] && options.fetch(:declaration) do
|
|
@@ -854,20 +871,18 @@ module Moxml
|
|
|
854
871
|
end
|
|
855
872
|
if include_decl
|
|
856
873
|
declaration = attachments.get(doc, :declaration)
|
|
857
|
-
parts << (declaration ? declaration.to_xml : default_declaration_xml(doc, options))
|
|
874
|
+
parts << (declaration ? declaration.to_xml : default_declaration_xml(doc, options)) << "\n"
|
|
858
875
|
end
|
|
859
876
|
|
|
860
877
|
doctype = attachments.get(doc, :doctype)
|
|
861
|
-
parts << doctype.to_xml if doctype
|
|
878
|
+
parts << doctype.to_xml << "\n" if doctype
|
|
862
879
|
|
|
863
880
|
native = native_doctype_xml(doc)
|
|
864
|
-
parts << native if native
|
|
881
|
+
parts << native << "\n" if native
|
|
865
882
|
|
|
866
|
-
(doc
|
|
867
|
-
parts << (data.to_s.empty? ? "<?#{target}?>" : "<?#{target} #{data}?>")
|
|
868
|
-
end
|
|
883
|
+
document_pi_nodes(doc).each { |pi| parts << pi.to_xml << "\n" }
|
|
869
884
|
|
|
870
|
-
parts << raw_serialize(doc.root, options) if doc.root
|
|
885
|
+
parts << raw_serialize(doc.root, options) << "\n" if doc.root
|
|
871
886
|
|
|
872
887
|
texts = attachments.get(doc, :document_text)
|
|
873
888
|
texts&.each { |text| parts << XmlEmitter.escape_text(text.content.to_s) }
|
|
@@ -935,6 +950,21 @@ module Moxml
|
|
|
935
950
|
attachments.get(native, :had_source_declaration) ? true : false
|
|
936
951
|
end
|
|
937
952
|
|
|
953
|
+
# Document-level PI pseudo-nodes, materialized once from the C
|
|
954
|
+
# list and cached per document: children and serialization read
|
|
955
|
+
# the same objects, so wrapper mutations round-trip. Build the
|
|
956
|
+
# cache BEFORE appending a PI with add_pi, or the C-side
|
|
957
|
+
# addition would be double-counted.
|
|
958
|
+
def document_pi_nodes(doc)
|
|
959
|
+
attachments.get(doc, :doc_pi_nodes) || begin
|
|
960
|
+
nodes = doc.processing_instructions.map do |(target, data)|
|
|
961
|
+
CustomizedLeptris::DocumentPI.new(target, data, doc)
|
|
962
|
+
end
|
|
963
|
+
attachments.set(doc, :doc_pi_nodes, nodes)
|
|
964
|
+
nodes
|
|
965
|
+
end
|
|
966
|
+
end
|
|
967
|
+
|
|
938
968
|
def assemble_document_children(doc)
|
|
939
969
|
children = []
|
|
940
970
|
|
|
@@ -944,6 +974,15 @@ module Moxml
|
|
|
944
974
|
doctype_wrapper = attachments.get(doc, :doctype)
|
|
945
975
|
children << doctype_wrapper if doctype_wrapper
|
|
946
976
|
|
|
977
|
+
# Document-level PIs live outside the element tree in
|
|
978
|
+
# libleptris (a flat list; its serializer emits them before
|
|
979
|
+
# the root). Listed after the DOCTYPE to match the order
|
|
980
|
+
# serialize_document emits, so children and serialization
|
|
981
|
+
# agree. Epilog anchoring is not representable in the C
|
|
982
|
+
# model — a known divergence from the Nokogiri-shaped
|
|
983
|
+
# contract the other adapters expose.
|
|
984
|
+
children.concat(document_pi_nodes(doc))
|
|
985
|
+
|
|
947
986
|
children << doc.root if doc.root
|
|
948
987
|
|
|
949
988
|
texts = attachments.get(doc, :document_text)
|
|
@@ -966,7 +1005,15 @@ module Moxml
|
|
|
966
1005
|
when ::Leptris::XML::Element
|
|
967
1006
|
doc.root = child
|
|
968
1007
|
when ::Leptris::XML::ProcessingInstruction
|
|
1008
|
+
document_pi_nodes(doc)
|
|
969
1009
|
doc.add_pi(child.target, child.content.to_s)
|
|
1010
|
+
document_pi_nodes(doc) << CustomizedLeptris::DocumentPI.new(
|
|
1011
|
+
child.target, child.content.to_s, doc
|
|
1012
|
+
)
|
|
1013
|
+
when CustomizedLeptris::DocumentPI
|
|
1014
|
+
document_pi_nodes(doc)
|
|
1015
|
+
doc.add_pi(child.target, child.data)
|
|
1016
|
+
document_pi_nodes(doc) << child
|
|
970
1017
|
when ::Leptris::XML::Text
|
|
971
1018
|
texts = attachments.get(doc, :document_text) || []
|
|
972
1019
|
texts << child
|
data/lib/moxml/version.rb
CHANGED
|
@@ -81,6 +81,64 @@ 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("\n")
|
|
116
|
+
from_document = doc.to_xml.sub(%r{\A<\?xml[^>]*\?>\n}, "")
|
|
117
|
+
|
|
118
|
+
expect(from_document).to start_with(from_children)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "matches raw Nokogiri byte-for-byte for pretty-printing (issue #129)" do
|
|
122
|
+
source = %(<root><a/><b>x</b></root>)
|
|
123
|
+
target = Nokogiri::XML(source).to_xml(indent: 2, encoding: "UTF-8")
|
|
124
|
+
output = ctx.parse(source).to_xml(
|
|
125
|
+
indent: 2, declaration: true, expand_empty: false, encoding: "UTF-8",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
expect(output).to eq(target)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "reports the tracked native for a re-added document PI" do
|
|
132
|
+
doc = ctx.parse("<?pi-src orig?><root/>")
|
|
133
|
+
moved = doc.children.to_a[0]
|
|
134
|
+
target_doc = ctx.parse("<other/>")
|
|
135
|
+
target_doc.add_child(moved)
|
|
136
|
+
|
|
137
|
+
expect(target_doc.to_xml).to include("<?pi-src orig?>")
|
|
138
|
+
expect(target_doc.children.to_a.select(&:processing_instruction?).map(&:target)).to include("pi-src")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
84
142
|
describe "entity-marker tracking" do
|
|
85
143
|
let(:ctx) { Moxml.new(:leptris) }
|
|
86
144
|
|
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.7
|
|
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
|