moxml 0.5.9 → 0.5.10

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: 011d9a8566b2697d8c01a17a1a4f702ace315361dae3b246f8ed5ad5fb79b3e8
4
- data.tar.gz: 286839c9aca15e4027d4b01bcdfb432c2036f10234fb408cf10f88e5dcbeac0d
3
+ metadata.gz: 0307f3c9e6060b1ca344c69633788790dc899121f213be74870be3cd2c2600ac
4
+ data.tar.gz: 22135905e79bcb1099a04809bedb69c734b1893ee4fa4749761427641bfcf9b2
5
5
  SHA512:
6
- metadata.gz: f53698ba5eee597fb6bda3355c1f7dfe0a33442cd3e67a6aedc73a155a23e80f049f2fc63aed6871b6cb2e85723719d7e7f318cc140ef178e5dde4bcd493a095
7
- data.tar.gz: 44d1afe3880ebc55dd214a0a7f2b48c69151504b6c5a16646afd88cf4a885184132db9a590552c2531bfda8ea84a92e8fac50a7cfae75379e215582acf0f5bac
6
+ metadata.gz: 50413c5cddd8680fc5c6cf4bd67dcf75e54e0cbf202b45f1ff5b985648ff948286b92c0a4efc9ef725d9fd0af677e0239eefb7fec37f27e43ece9795240a5db7
7
+ data.tar.gz: 696949b3fb51358b4841e2a2fdeb68824659a7ffefdbb6ac4a2006758006d649ddb64127728cd2b5b653cc54dd888e14566d012d5d6b6c382ce012f3b0ea0112
@@ -19,6 +19,11 @@ module Moxml
19
19
  # programmatic DOCTYPE, so those live in CustomizedLeptris value
20
20
  # objects stored through NativeAttachment.
21
21
  class Leptris < Base
22
+ # libleptris 1.9.7 (leptris-ruby 1.9.26): Document#node exposes
23
+ # the libxml2-model document node — prolog/epilog parts in
24
+ # document order. Older bindings keep the flat pre-root PI path.
25
+ DOC_NODE_SUPPORTED = ::Leptris::XML::Document.method_defined?(:node)
26
+
22
27
  class << self
23
28
  def attachments
24
29
  @attachments ||= Moxml::NativeAttachment.new
@@ -283,6 +288,12 @@ module Moxml
283
288
  qname: node.name,
284
289
  prefix: node.prefix,
285
290
  namespace_uri: ns&.href,
291
+ # Own declarations only — same shape as the generic
292
+ # path's Element#declared_namespaces (issue #138).
293
+ namespaces: begin
294
+ decls = node.namespace_definitions.map { |d| [d.prefix, d.href] }
295
+ decls.empty? ? Materializer::EMPTY_ATTRIBUTES : decls
296
+ end,
286
297
  attributes: attributes,
287
298
  text: nil,
288
299
  depth: material_depth(node, depth_memo),
@@ -295,6 +306,7 @@ module Moxml
295
306
  qname: nil,
296
307
  prefix: nil,
297
308
  namespace_uri: nil,
309
+ namespaces: Materializer::EMPTY_ATTRIBUTES,
298
310
  attributes: Materializer::EMPTY_ATTRIBUTES,
299
311
  text: text,
300
312
  depth: material_depth(node, depth_memo),
@@ -971,9 +983,22 @@ module Moxml
971
983
  native = native_doctype_xml(doc)
972
984
  parts << native << "\n" if native
973
985
 
974
- document_pi_nodes(doc).each { |pi| parts << pi.to_xml << "\n" }
986
+ if DOC_NODE_SUPPORTED
987
+ # The libxml2-model document node: prolog PIs/comments,
988
+ # the root, epilog PIs/comments — in document order, so
989
+ # epilog parts serialize after the root (issue #130).
990
+ doc_children = document_node_children(doc)
991
+ if doc_children
992
+ doc_children.each { |child| parts << raw_serialize(child, options) << "\n" }
993
+ else
994
+ document_pi_nodes(doc).each { |pi| parts << pi.to_xml << "\n" }
995
+ parts << raw_serialize(doc.root, options) << "\n" if doc.root
996
+ end
997
+ else
998
+ document_pi_nodes(doc).each { |pi| parts << pi.to_xml << "\n" }
975
999
 
976
- parts << raw_serialize(doc.root, options) << "\n" if doc.root
1000
+ parts << raw_serialize(doc.root, options) << "\n" if doc.root
1001
+ end
977
1002
 
978
1003
  texts = attachments.get(doc, :document_text)
979
1004
  texts&.each { |text| parts << XmlEmitter.escape_text(text.content.to_s) }
@@ -1046,6 +1071,20 @@ module Moxml
1046
1071
  # the same objects, so wrapper mutations round-trip. Build the
1047
1072
  # cache BEFORE appending a PI with add_pi, or the C-side
1048
1073
  # addition would be double-counted.
1074
+ # The document node's children, or nil when the node does not
1075
+ # reflect reality: parsed documents always list the root
1076
+ # element among their children, but programmatically built
1077
+ # ones do not (binding gap) — those keep the legacy parts
1078
+ # path.
1079
+ def document_node_children(doc)
1080
+ doc_children = doc.children.to_a
1081
+ has_root = doc_children.any?(::Leptris::XML::Element)
1082
+ return doc_children if has_root
1083
+ return doc_children if doc.root.nil?
1084
+
1085
+ nil
1086
+ end
1087
+
1049
1088
  def document_pi_nodes(doc)
1050
1089
  attachments.get(doc, :doc_pi_nodes) || begin
1051
1090
  nodes = doc.processing_instructions.map do |(target, data)|
@@ -1065,16 +1104,29 @@ module Moxml
1065
1104
  doctype_wrapper = attachments.get(doc, :doctype)
1066
1105
  children << doctype_wrapper if doctype_wrapper
1067
1106
 
1068
- # Document-level PIs live outside the element tree in
1069
- # libleptris (a flat list; its serializer emits them before
1070
- # the root). Listed after the DOCTYPE to match the order
1071
- # serialize_document emits, so children and serialization
1072
- # agree. Epilog anchoring is not representable in the C
1073
- # model a known divergence from the Nokogiri-shaped
1074
- # contract the other adapters expose.
1075
- children.concat(document_pi_nodes(doc))
1107
+ if DOC_NODE_SUPPORTED
1108
+ # The libxml2-model document node lists prolog PIs/comments,
1109
+ # the root, and epilog PIs/comments in document order
1110
+ # the Nokogiri-shaped contract, epilog anchoring included
1111
+ # (issue #130). Built (programmatic) documents are not yet
1112
+ # fully reflected by the node (binding gap: an attached
1113
+ # root does not appear); document_node_children answers
1114
+ # nil there for the legacy parts path.
1115
+ doc_children = document_node_children(doc)
1116
+ if doc_children
1117
+ children.concat(doc_children)
1118
+ else
1119
+ children.concat(document_pi_nodes(doc))
1120
+ children << doc.root if doc.root
1121
+ end
1122
+ else
1123
+ # Legacy path: document-level PIs live outside the element
1124
+ # tree in a flat pre-root list (libleptris < 1.9.7 C
1125
+ # model); epilog anchoring is not representable there.
1126
+ children.concat(document_pi_nodes(doc))
1076
1127
 
1077
- children << doc.root if doc.root
1128
+ children << doc.root if doc.root
1129
+ end
1078
1130
 
1079
1131
  texts = attachments.get(doc, :document_text)
1080
1132
  children.concat(texts) if texts
data/lib/moxml/element.rb CHANGED
@@ -119,6 +119,16 @@ module Moxml
119
119
  end
120
120
  alias namespace_definitions namespaces
121
121
 
122
+ # The element's OWN namespace declarations as [prefix, uri]
123
+ # pairs (nil prefix = default namespace) — not the inherited
124
+ # scope. The shape materialize records carry (issue #138).
125
+ def declared_namespaces
126
+ adapter.namespace_definitions(@native).map do |ns|
127
+ wrapper = Namespace.new(ns, context)
128
+ [wrapper.prefix, wrapper.uri]
129
+ end
130
+ end
131
+
122
132
  # Returns all namespaces in scope for this element,
123
133
  # including those inherited from ancestor elements.
124
134
  def in_scope_namespaces
@@ -69,6 +69,10 @@ module Moxml
69
69
  qname: element.name,
70
70
  prefix: element.namespace_prefix,
71
71
  namespace_uri: ns&.uri,
72
+ # The element's OWN namespace declarations ([prefix, uri]
73
+ # pairs; nil prefix = default), not the in-scope set — enough
74
+ # for a consumer to rebuild scope while walking (issue #138).
75
+ namespaces: element.declared_namespaces,
72
76
  attributes: attributes,
73
77
  text: nil,
74
78
  depth: depth,
@@ -81,6 +85,7 @@ module Moxml
81
85
  qname: nil,
82
86
  prefix: nil,
83
87
  namespace_uri: nil,
88
+ namespaces: EMPTY_ATTRIBUTES,
84
89
  attributes: EMPTY_ATTRIBUTES,
85
90
  text: text,
86
91
  depth: depth,
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.9"
4
+ VERSION = "0.5.10"
5
5
  end
@@ -85,37 +85,51 @@ RSpec.describe Moxml::Adapter::Leptris do
85
85
  let(:ctx) { Moxml.new(:leptris) }
86
86
 
87
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?>')
88
+ doc = ctx.parse('<?xml version="1.0"?><?pi-prolog before?><root/><?pi-epilog after?><!-- tail -->')
89
89
  kids = doc.children.to_a
90
90
 
91
91
  expect(kids.map(&:class)).to eq(
92
- [Moxml::ProcessingInstruction, Moxml::ProcessingInstruction, Moxml::Element],
92
+ [Moxml::ProcessingInstruction, Moxml::Element,
93
+ Moxml::ProcessingInstruction, Moxml::Comment],
93
94
  )
94
95
  expect(kids.select(&:processing_instruction?).map(&:target)).to eq(%w[pi-prolog pi-epilog])
95
96
  expect(kids[0].content).to eq("before")
97
+ expect(doc.to_xml.index("pi-epilog")).to be > doc.to_xml.index("</root>")
98
+ expect(doc.to_xml.index("<!-- tail -->")).to be > doc.to_xml.index("</root>")
96
99
  end
97
100
 
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
+ it "round-trips tree-level PI mutations through serialization" do
102
+ doc = ctx.parse("<root><a><?pi-original x?></a></root>")
103
+ pi = doc.at_xpath("//a").children.to_a.find(&:processing_instruction?)
101
104
  pi.target = "renamed"
102
105
  pi.content = "changed"
103
106
  expect(doc.to_xml).to include("<?renamed changed?>")
107
+ end
104
108
 
109
+ it "adds document PIs and lists them as children" do
110
+ doc = ctx.parse("<root/>")
105
111
  doc.add_child(doc.create_processing_instruction("added", "now"))
106
112
  expect(doc.to_xml).to include("<?added now?>")
107
- expect(doc.children.to_a.select(&:processing_instruction?).map(&:target)).to eq(%w[renamed added])
113
+ expect(doc.children.to_a.select(&:processing_instruction?).map(&:target)).to eq(%w[added])
114
+ end
115
+
116
+ it "reports document-level PI mutation as unsupported on the native path" do
117
+ # libleptris 1.9.7 exposes document PIs read-only: target=/data=
118
+ # and unlink are rejected for nodes outside the element tree.
119
+ doc = ctx.parse("<?pi x?><root/>")
120
+ pi = doc.children.to_a[0]
121
+
122
+ expect { pi.target = "renamed" }.to raise_error(Leptris::XML::Error)
108
123
  end
109
124
 
110
125
  it "serializes children and document output in agreement" do
111
126
  # libleptris stores document PIs as one flat pre-root list (no
112
127
  # epilog anchoring); children and to_xml must at least agree.
113
128
  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")
129
+ parts = doc.children.to_a.map { |c| "#{c.to_xml}\n" }.join
116
130
  from_document = doc.to_xml.sub(%r{\A<\?xml[^>]*\?>\n}, "")
117
131
 
118
- expect(from_document).to start_with(from_children)
132
+ expect(from_document).to eq(parts)
119
133
  end
120
134
 
121
135
  it "matches raw Nokogiri byte-for-byte for pretty-printing (issue #129)" do
@@ -36,6 +36,10 @@ RSpec.describe Moxml::Materializer do
36
36
  expect(book[:namespace_uri]).to eq("urn:c")
37
37
  expect(book[:text]).to be_nil
38
38
 
39
+ catalog = records.last
40
+ expect(catalog[:namespaces]).to eq([[nil, "urn:c"], ["p", "urn:p"]])
41
+ expect(book[:namespaces]).to eq([])
42
+
39
43
  title_text = records.find { |r| r[:kind] == :text && r[:text] == "T" }
40
44
  expect(title_text[:attributes]).to eq([])
41
45
  end
@@ -77,6 +81,6 @@ RSpec.describe Moxml::Materializer do
77
81
 
78
82
  # Records themselves allocate (hash + FFI strings); the wrapper
79
83
  # tree does not: far fewer allocations than 2 wrappers per node.
80
- expect(allocated).to be < node_count * 20
84
+ expect(allocated).to be < node_count * 24
81
85
  end
82
86
  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.9
4
+ version: 0.5.10
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-08-27 00:00:00.000000000 Z
11
+ date: 2026-08-28 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