moxml 0.5.5 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cc1ee5cd198e8016cd259e62eec132ef73863dbb91125cca55349c8847df01c
4
- data.tar.gz: 9b3db2921b1acaebaaeccfa6dbba82c78184443c6338e79952e334da0a837b0c
3
+ metadata.gz: 7f2b8ff3a745345a5d0462bd246f0ee8b463713fcad2dcf99590813ffd49aa27
4
+ data.tar.gz: 8930bcdaee831095c3a5b576d4f19a36591fd1a3884e0fb1f0aebb1c4c91485f
5
5
  SHA512:
6
- metadata.gz: 4a4ccdd2949f15fedf7bee4bc2c41b9340250ec58d2d4583e7be7a0a14c397749e034f1ec3e15193c7a2ad93d43de7126338fecf2142ad1180ea50b3de25a97a
7
- data.tar.gz: 9e4abee61c92cb0a3390804af398e6baaf593107e8bbc9b33b10d5bc40675f9f0b92ef5ded27f0b8948edd8c930b5188496c4c644529a277d8dd71b49f3bae57
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 }}
@@ -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, and entity references.
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 then node.parent_doc
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)
@@ -863,9 +875,7 @@ module Moxml
863
875
  native = native_doctype_xml(doc)
864
876
  parts << native if native
865
877
 
866
- (doc.processing_instructions || []).each do |(target, data)|
867
- parts << (data.to_s.empty? ? "<?#{target}?>" : "<?#{target} #{data}?>")
868
- end
878
+ document_pi_nodes(doc).each { |pi| parts << pi.to_xml }
869
879
 
870
880
  parts << raw_serialize(doc.root, options) if doc.root
871
881
 
@@ -935,6 +945,21 @@ module Moxml
935
945
  attachments.get(native, :had_source_declaration) ? true : false
936
946
  end
937
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
+
938
963
  def assemble_document_children(doc)
939
964
  children = []
940
965
 
@@ -944,6 +969,15 @@ module Moxml
944
969
  doctype_wrapper = attachments.get(doc, :doctype)
945
970
  children << doctype_wrapper if doctype_wrapper
946
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
+
947
981
  children << doc.root if doc.root
948
982
 
949
983
  texts = attachments.get(doc, :document_text)
@@ -966,7 +1000,15 @@ module Moxml
966
1000
  when ::Leptris::XML::Element
967
1001
  doc.root = child
968
1002
  when ::Leptris::XML::ProcessingInstruction
1003
+ document_pi_nodes(doc)
969
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
970
1012
  when ::Leptris::XML::Text
971
1013
  texts = attachments.get(doc, :document_text) || []
972
1014
  texts << child
data/lib/moxml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.5"
4
+ VERSION = "0.5.6"
5
5
  end
@@ -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.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