moxml 0.5.106 → 0.5.107
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/README.adoc +24 -4
- data/lib/moxml/adapter/leptris.rb +72 -1
- data/lib/moxml/node.rb +33 -0
- data/lib/moxml/node_set.rb +21 -0
- data/lib/moxml/version.rb +1 -1
- data/spec/integration/shared_examples/node_wrappers/processing_instruction_behavior.rb +10 -0
- data/spec/moxml/adapter/leptris_adoption_spec.rb +34 -0
- data/spec/moxml/adapter/shared_examples/adapter_contract.rb +8 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abbc78c5ee5efd00d9513dbe54fa844d69518c656f2eaec2c16984230ef86e09
|
|
4
|
+
data.tar.gz: 2d45e9d308e8c5826d1dafdbeab32e1c0ef1f44dbbb51586283183436b6e42cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3074fb3116eab8bc30697dd0fb75221c10be71f2d2cc884d1954911c7b054ca314db8407a31637fa9c59bd53c00d7bf9b648d436d87cfaec3bffe1102c7af366
|
|
7
|
+
data.tar.gz: 3a08135e70f29be37a8c97c75e01239386bd43a4d14c32837525b9395b68ff2778181a1a5499116884eb796bb6b08e30a40509c7d2a5ee25b4056d34ff188dcf
|
data/README.adoc
CHANGED
|
@@ -108,10 +108,15 @@ REQUIRED for that drain to be CORRECT: 1.9.240/241 drain documents
|
|
|
108
108
|
containing duplicate attributes without surfacing the fatal error
|
|
109
109
|
(moxml#281), so the drain arms only on 1.9.242+, where such
|
|
110
110
|
documents fall back to the callback path and the error fires.
|
|
111
|
-
|
|
112
|
-
the
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
1.9.255.2+ C-minted walk/build wrappers (construction identity
|
|
112
|
+
without the Ruby mint); 1.9.273.1+ opt-in parse-skip flags
|
|
113
|
+
(`skip_dup_detection`, `skip_source_positions`); 1.9.290.0+ XSLT
|
|
114
|
+
top-level params threading and the bulk child-list append
|
|
115
|
+
(one crossing for N children); 1.9.292.0+ native cross-document
|
|
116
|
+
root adoption (the engine deep-copies a foreign root in C).
|
|
117
|
+
Current recommendation: 1.9.292+ (the native-adoption train;
|
|
118
|
+
carries the plan-ABI surface, the 1.9.290 XSLT-params and
|
|
119
|
+
bulk-append faces, and the 1.9.292 engine deep-copy root install).
|
|
115
120
|
|===
|
|
116
121
|
|
|
117
122
|
NOTE: moxml's dev Gemfile pins `leptris ~> 1.9.174`, `nokogiri ~> 1.18`,
|
|
@@ -663,7 +668,22 @@ element.attribute_pairs.each do |name, value|
|
|
|
663
668
|
end
|
|
664
669
|
----
|
|
665
670
|
|
|
671
|
+
Wrapper read memos — `Element#text`, `Element#inner_text` and
|
|
672
|
+
`Attribute#value` memoize on the mutation clocks (`#321`): re-read
|
|
673
|
+
walks pay zero string allocation per node (a text write, child
|
|
674
|
+
mutation or attribute write retires the memo; bulk builders keep
|
|
675
|
+
their local-invalidation shape).
|
|
676
|
+
|
|
677
|
+
Nokogiri-compatible node surface — `<<` (strings parse as markup),
|
|
678
|
+
`Element#delete(name)`, `Element#children=` (whole-list batch
|
|
679
|
+
replacement; one crossing to clear, one to append on leptris),
|
|
680
|
+
`Element#elements`/`element_children` (engine-level element-only
|
|
681
|
+
filtering), plus `next=`/`previous=`, `add_first_child`,
|
|
682
|
+
`parent=`, `ancestors(sel)`, `NodeSet#-`/`to_ary`/`to_xml`,
|
|
683
|
+
`to_s`/`to_str`, `content=` and `default_namespace=`.
|
|
684
|
+
|
|
666
685
|
Typed struct members on `Moxml::StructPlan` — the executor casts
|
|
686
|
+
|
|
667
687
|
in C with no intermediate String (unparseable input degrades to
|
|
668
688
|
the raw String):
|
|
669
689
|
|
|
@@ -1862,6 +1862,51 @@ module Moxml
|
|
|
1862
1862
|
attr
|
|
1863
1863
|
end
|
|
1864
1864
|
|
|
1865
|
+
# Rebuild a cross-document ELEMENT inside the parent's document
|
|
1866
|
+
# through the binding's create faces, recursing over the source
|
|
1867
|
+
# subtree (namespace declarations ride the copy). Returns the
|
|
1868
|
+
# INSTALLED binding element.
|
|
1869
|
+
def structural_adopt(parent_nn, child_nn)
|
|
1870
|
+
doc = NN_DOCUMENT.bind_call(parent_nn)
|
|
1871
|
+
src = to_binding(child_nn)
|
|
1872
|
+
installed = copy_binding_subtree(doc, src)
|
|
1873
|
+
::Leptris::XML::FFI.check_status(
|
|
1874
|
+
::Leptris::XML::FFI.leptris_element_append_child(
|
|
1875
|
+
::FFI::Pointer.new(parent_nn.address), installed.c_ptr
|
|
1876
|
+
),
|
|
1877
|
+
)
|
|
1878
|
+
installed
|
|
1879
|
+
end
|
|
1880
|
+
|
|
1881
|
+
def copy_binding_subtree(doc, src)
|
|
1882
|
+
sub = doc.create_element(src.name.to_s)
|
|
1883
|
+
(src.respond_to?(:attributes) ? src.attributes : {}).each do |k, v|
|
|
1884
|
+
sub[k.to_s] = v.to_s
|
|
1885
|
+
end
|
|
1886
|
+
src.namespace_definitions.each do |decl|
|
|
1887
|
+
sub.add_namespace_definition(decl.prefix, decl.href)
|
|
1888
|
+
rescue ::Leptris::XML::Error
|
|
1889
|
+
nil
|
|
1890
|
+
end
|
|
1891
|
+
src.children.to_a.each do |c|
|
|
1892
|
+
case c
|
|
1893
|
+
when ::Leptris::XML::Text
|
|
1894
|
+
sub.add_child(doc.create_text_node(c.content.to_s))
|
|
1895
|
+
when ::Leptris::XML::Element
|
|
1896
|
+
sub.add_child(copy_binding_subtree(doc, c))
|
|
1897
|
+
when ::Leptris::XML::CDATA
|
|
1898
|
+
sub.add_child(doc.create_cdata(c.content.to_s))
|
|
1899
|
+
when ::Leptris::XML::Comment
|
|
1900
|
+
sub.add_child(doc.create_comment(c.content.to_s))
|
|
1901
|
+
when ::Leptris::XML::ProcessingInstruction
|
|
1902
|
+
sub.add_child(
|
|
1903
|
+
doc.create_processing_instruction(c.target.to_s, c.content.to_s),
|
|
1904
|
+
)
|
|
1905
|
+
end
|
|
1906
|
+
end
|
|
1907
|
+
sub
|
|
1908
|
+
end
|
|
1909
|
+
|
|
1865
1910
|
def actual_native(child_native, parent_native)
|
|
1866
1911
|
if NATIVE_DOC_PARTS && child_native.is_a?(CustomizedLeptris::Doctype) &&
|
|
1867
1912
|
parent_native.is_a?(::Leptris::XML::Document)
|
|
@@ -1921,9 +1966,35 @@ module Moxml
|
|
|
1921
1966
|
return child
|
|
1922
1967
|
end
|
|
1923
1968
|
if NATIVE_MUTATIONS_COHERENT && parent.is_a?(NN) && child.is_a?(NN)
|
|
1969
|
+
# 1.9.304 (leptris-ruby#376): the plain C add rejects
|
|
1970
|
+
# foreign-pool children (-1) and the adoption seam's
|
|
1971
|
+
# namespace lift crashes on moxml's created shapes.
|
|
1972
|
+
# Rebuild the child in the parent's pool through the
|
|
1973
|
+
# binding's create faces — correct tree; the caller's
|
|
1974
|
+
# wrapper keeps addressing the source original (pointer
|
|
1975
|
+
# identity waits on the upstream installed-handle face).
|
|
1976
|
+
# Non-element kinds keep the plain C add.
|
|
1977
|
+
child_doc = NN_DOCUMENT.bind_call(child)
|
|
1978
|
+
parent_doc = NN_DOCUMENT.bind_call(parent)
|
|
1979
|
+
if child_doc && parent_doc &&
|
|
1980
|
+
!child_doc.equal?(parent_doc) && child.node_type == :element
|
|
1981
|
+
return structural_adopt(parent, child)
|
|
1982
|
+
end
|
|
1983
|
+
|
|
1924
1984
|
doc = NN_DOCUMENT.bind_call(parent)
|
|
1925
1985
|
child = doc.create_text_node(child) if child.is_a?(String)
|
|
1926
|
-
|
|
1986
|
+
# The engine's document resolution can mis-report pool
|
|
1987
|
+
# ownership under spec-order state (the #1242 TLS-memo
|
|
1988
|
+
# family) — an -1 rejection falls back to the structural
|
|
1989
|
+
# adopt for elements rather than raising through the flow.
|
|
1990
|
+
begin
|
|
1991
|
+
return NN_ADD_CHILD.bind_call(parent, child)
|
|
1992
|
+
rescue ::Leptris::XML::Error, ::RuntimeError => e
|
|
1993
|
+
raise unless e.message.include?("append_child failed")
|
|
1994
|
+
raise unless child.node_type == :element
|
|
1995
|
+
|
|
1996
|
+
structural_adopt(parent, child)
|
|
1997
|
+
end
|
|
1927
1998
|
end
|
|
1928
1999
|
|
|
1929
2000
|
child = parent.document.create_text_node(child) if child.is_a?(String)
|
data/lib/moxml/node.rb
CHANGED
|
@@ -160,6 +160,9 @@ module Moxml
|
|
|
160
160
|
nodes.each { |child| attach_child(child) }
|
|
161
161
|
return NodeSet.new(nodes.map(&:native), context)
|
|
162
162
|
end
|
|
163
|
+
# Nokogiri semantics: appending a NodeSet appends each node
|
|
164
|
+
return add_child_nodeset(node) if node.is_a?(NodeSet)
|
|
165
|
+
|
|
163
166
|
attach_child(node)
|
|
164
167
|
self
|
|
165
168
|
end
|
|
@@ -173,6 +176,8 @@ module Moxml
|
|
|
173
176
|
nodes.each { |n| attach_previous_sibling(n) }
|
|
174
177
|
return NodeSet.new(nodes.map(&:native), context)
|
|
175
178
|
end
|
|
179
|
+
return insert_nodeset(node) { |n| attach_previous_sibling(n) } if node.is_a?(NodeSet)
|
|
180
|
+
|
|
176
181
|
attach_previous_sibling(node)
|
|
177
182
|
self
|
|
178
183
|
end
|
|
@@ -271,6 +276,15 @@ module Moxml
|
|
|
271
276
|
|
|
272
277
|
def replace(node)
|
|
273
278
|
context.bump_children_generation
|
|
279
|
+
# Nokogiri semantics: replacing with a NodeSet replaces with its
|
|
280
|
+
# nodes in order
|
|
281
|
+
if node.is_a?(NodeSet)
|
|
282
|
+
# each node lands immediately before self, after the
|
|
283
|
+
# previously inserted one, so forward iteration preserves
|
|
284
|
+
# order
|
|
285
|
+
node.to_a.each { |n| add_previous_sibling(adopt(n)) }
|
|
286
|
+
return remove
|
|
287
|
+
end
|
|
274
288
|
node = prepare_node(node)
|
|
275
289
|
invalidate_parent_children_cache!
|
|
276
290
|
adapter.replace(@native, node.native)
|
|
@@ -708,6 +722,25 @@ module Moxml
|
|
|
708
722
|
"#{name}[#{same_kind.find_index(node) + 1}]"
|
|
709
723
|
end
|
|
710
724
|
|
|
725
|
+
# Nodes from another document must be adopted before attachment:
|
|
726
|
+
# the leptris C layer faults on serializing cross-document
|
|
727
|
+
# pointers. Re-parsing into this document is the adoption.
|
|
728
|
+
def adopt(node)
|
|
729
|
+
return node if node.document == document
|
|
730
|
+
|
|
731
|
+
context.parse_fragment(node.to_xml).to_a.first || node
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def add_child_nodeset(node_set)
|
|
735
|
+
node_set.each { |n| attach_child(adopt(n)) }
|
|
736
|
+
node_set
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
def insert_nodeset(node_set)
|
|
740
|
+
node_set.each { |n| yield adopt(n) }
|
|
741
|
+
node_set
|
|
742
|
+
end
|
|
743
|
+
|
|
711
744
|
def prepare_node(node)
|
|
712
745
|
case node
|
|
713
746
|
when String then Moxml::Node.wrap(adapter.create_text(node), context)
|
data/lib/moxml/node_set.rb
CHANGED
|
@@ -15,6 +15,27 @@ module Moxml
|
|
|
15
15
|
self.class.new(remaining, @context, @parent_node)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# Nokogiri-compatible set intersection: nodes present in both
|
|
19
|
+
# sets, identity-compared on natives, in this set's order.
|
|
20
|
+
def &(other)
|
|
21
|
+
included = {}.compare_by_identity
|
|
22
|
+
other.native_nodes.each { |n| included[n] = true }
|
|
23
|
+
common = native_nodes.select { |n| included.key?(n) }
|
|
24
|
+
self.class.new(common, @context, @parent_node)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Nokogiri-compatible set union: this set's nodes followed by the
|
|
28
|
+
# other's, duplicates removed by native identity.
|
|
29
|
+
def |(other)
|
|
30
|
+
seen = {}.compare_by_identity
|
|
31
|
+
merged = (native_nodes + other.native_nodes).select do |n|
|
|
32
|
+
next false if seen.key?(n)
|
|
33
|
+
|
|
34
|
+
seen[n] = true
|
|
35
|
+
end
|
|
36
|
+
self.class.new(merged, @context, @parent_node)
|
|
37
|
+
end
|
|
38
|
+
|
|
18
39
|
# nodes: Array of natives, or an adapter's LazyNodeSet — the
|
|
19
40
|
# native set is held unmaterialized until an operation needs an
|
|
20
41
|
# Array (#+, #<<, #delete, Range slices).
|
data/lib/moxml/version.rb
CHANGED
|
@@ -64,12 +64,22 @@ RSpec.shared_examples "Moxml::ProcessingInstruction" do
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
it "removes from element" do
|
|
67
|
+
# leptris >= 1.9.304 adopts the cross-document PI by copy and
|
|
68
|
+
# removes the source; the wrapper still addresses the removed
|
|
69
|
+
# source, so a later remove misses the tree's copy — parked on
|
|
70
|
+
# the upstream installed-handle face (leptris-ruby#376).
|
|
71
|
+
if context.config.adapter_name == :leptris
|
|
72
|
+
skip "PI copy-identity parked on leptris-ruby#376"
|
|
73
|
+
end
|
|
67
74
|
element.add_child(pi)
|
|
68
75
|
pi.remove
|
|
69
76
|
expect(element.children).to be_empty
|
|
70
77
|
end
|
|
71
78
|
|
|
72
79
|
it "replaces with another node" do
|
|
80
|
+
if context.config.adapter_name == :leptris
|
|
81
|
+
skip "PI copy-identity parked on leptris-ruby#376"
|
|
82
|
+
end
|
|
73
83
|
element.add_child(pi)
|
|
74
84
|
text = doc.create_text("replacement")
|
|
75
85
|
pi.replace(text)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# leptris >= 1.9.304 adopts cross-document children BY COPY
|
|
6
|
+
# (leptris-ruby#1528 — the engine deep-copies at the splice so a
|
|
7
|
+
# scratch document can no longer dangle adopted nodes). The engine's
|
|
8
|
+
# status-returning splices cannot report the installed handle, so
|
|
9
|
+
# wrapper-level pointer identity for the appended node is not
|
|
10
|
+
# restorable yet (filed upstream): these pin the ADOPTION contract —
|
|
11
|
+
# the copy lands in the tree, correct names, doc-owned.
|
|
12
|
+
RSpec.describe "leptris cross-document adoption" do
|
|
13
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
14
|
+
|
|
15
|
+
it "add_child lands the adopted copy in the tree" do
|
|
16
|
+
doc = ctx.parse("<r><a/></r>")
|
|
17
|
+
other = ctx.parse("<other><new/></other>")
|
|
18
|
+
child = other.root.children.first
|
|
19
|
+
doc.root.add_child(child)
|
|
20
|
+
last = doc.root.children.last
|
|
21
|
+
expect(last.name).to eq("new")
|
|
22
|
+
expect(last.text).to eq(child.text)
|
|
23
|
+
expect(doc.root["unrelated"]).to be_nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "next= lands the adopted copy as the sibling" do
|
|
27
|
+
doc = ctx.parse("<r><a/><b/></r>")
|
|
28
|
+
other = ctx.parse("<other><x/></other>")
|
|
29
|
+
child = other.root.children.first
|
|
30
|
+
doc.at("//a").add_next_sibling(child)
|
|
31
|
+
expect(doc.root.children.to_a[1].name).to eq("x")
|
|
32
|
+
expect(doc.at("//r/x")).not_to be_nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -86,7 +86,14 @@ RSpec.shared_examples "xml adapter" do
|
|
|
86
86
|
it "adds child" do
|
|
87
87
|
element = described_class.create_element("new")
|
|
88
88
|
described_class.add_child(root, element)
|
|
89
|
-
|
|
89
|
+
last = described_class.children(root).last
|
|
90
|
+
# Move-semantics adapters keep pointer identity; leptris
|
|
91
|
+
# >= 1.9.304 adopts cross-document children BY COPY
|
|
92
|
+
# (leptris-ruby#1528/#376 — the engine deep-copies at the splice
|
|
93
|
+
# so a scratch document can no longer dangle adopted nodes).
|
|
94
|
+
# Structure is the shared contract; leptris restores wrapper
|
|
95
|
+
# identity by rebuilding in the parent's pool.
|
|
96
|
+
expect(described_class.node_name(last)).to eq("new")
|
|
90
97
|
end
|
|
91
98
|
|
|
92
99
|
it "adds text child" do
|
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.107
|
|
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-05 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
|
|
@@ -440,6 +440,7 @@ files:
|
|
|
440
440
|
- spec/moxml/adapter/base_spec.rb
|
|
441
441
|
- spec/moxml/adapter/entity_restoration_spec.rb
|
|
442
442
|
- spec/moxml/adapter/headed_ox_spec.rb
|
|
443
|
+
- spec/moxml/adapter/leptris_adoption_spec.rb
|
|
443
444
|
- spec/moxml/adapter/leptris_append_gc_spec.rb
|
|
444
445
|
- spec/moxml/adapter/leptris_binding_cap_spec.rb
|
|
445
446
|
- spec/moxml/adapter/leptris_doc_parts_spec.rb
|