moxml 0.5.109 → 0.5.111
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/lib/moxml/adapter/leptris.rb +165 -9
- data/lib/moxml/element.rb +2 -2
- data/lib/moxml/version.rb +1 -1
- data/spec/integration/shared_examples/node_wrappers/processing_instruction_behavior.rb +0 -10
- data/spec/moxml/adapter/leptris_adoption_spec.rb +76 -7
- data/spec/moxml/element_attribute_pairs_spec.rb +19 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24c16db3331672007afc4d08d042c3c44b52cfd00b3c20a026d88646afc842a7
|
|
4
|
+
data.tar.gz: 412edab11fe3895ef32a0f0520fb819fd2b5ea01b4436f08d715c0b38b132d1f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc6537c232494b1b33ec4f4f821e392ca38e8f274e01d0973f5725449b6688c5ba5ec598b50ef74dbb3a9d0b26fce15f411bde9d93d02744b0d663482e603d05
|
|
7
|
+
data.tar.gz: '019eb6bacde42ea30d76d6207da5f95d5c05e907c6f144e7f4c21cf92790bc150780f9289ac88fdc5ac94223f862ba198096bd040c90d26f146f54e52c6dc797'
|
|
@@ -146,6 +146,21 @@ module Moxml
|
|
|
146
146
|
NATIVE_SAX_RECORDS =
|
|
147
147
|
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.242")
|
|
148
148
|
|
|
149
|
+
# Document#absorb (engine #1548): one call transfers a source
|
|
150
|
+
# document's pool ownership into the destination, so
|
|
151
|
+
# cross-document attaches of its nodes move by reference (O(1),
|
|
152
|
+
# no deep copy) instead of rebuilding or copying at every seam.
|
|
153
|
+
# Floor 1.9.311.3, not .0: the .0 finalizer double-freed pools
|
|
154
|
+
# whose absorbed sources survived to process exit, and its
|
|
155
|
+
# splice paths dropped absorbed nodes (both fixed in .3 —
|
|
156
|
+
# leptris-ruby#386). The method_defined probe pairs with the
|
|
157
|
+
# version gate (misrelease races — the NATIVE_PLAN_STRUCTS
|
|
158
|
+
# lesson).
|
|
159
|
+
NATIVE_DOC_ABSORB =
|
|
160
|
+
defined?(::Leptris::XML::Document) &&
|
|
161
|
+
::Leptris::XML::Document.method_defined?(:absorb) &&
|
|
162
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.311.3")
|
|
163
|
+
|
|
149
164
|
# XSLT 1.0/2.0/3.0 through the engine's compiled-stylesheet
|
|
150
165
|
# faces (Moxml::XSLT contract). Top-level parameters are not
|
|
151
166
|
# threaded through the faces yet (leptris-ruby#360) — non-empty
|
|
@@ -1921,13 +1936,17 @@ module Moxml
|
|
|
1921
1936
|
# collected its arena dies and the subtree vanishes (empty
|
|
1922
1937
|
# output, nameless element pairs — silent, GC-dependent). Pin
|
|
1923
1938
|
# the owning document on the target's document so the arena
|
|
1924
|
-
# outlives every attached subtree.
|
|
1939
|
+
# outlives every attached subtree. Absorb (1.9.311) is NOT
|
|
1940
|
+
# taken here: the binding's splice paths are unreliable for
|
|
1941
|
+
# absorbed sources (transitively-stale handles drop nodes), so
|
|
1942
|
+
# only the element moves that route through the raw engine add
|
|
1943
|
+
# (add_child / sibling inserts below) absorb first.
|
|
1925
1944
|
def pin_foreign_owner_document(parent, child)
|
|
1926
1945
|
return unless child.is_a?(NN) || child.is_a?(::Leptris::XML::Node)
|
|
1927
1946
|
|
|
1928
1947
|
# Two document fetches + identity compare on the same-document
|
|
1929
|
-
# hot path (every builder add_child); the
|
|
1930
|
-
#
|
|
1948
|
+
# hot path (every builder add_child); the bridge and the
|
|
1949
|
+
# pin only pay when the child actually comes from another
|
|
1931
1950
|
# document.
|
|
1932
1951
|
child_doc = child.is_a?(NN) ? NN_DOCUMENT.bind_call(child) : child.document
|
|
1933
1952
|
return if child_doc.nil?
|
|
@@ -1947,7 +1966,103 @@ module Moxml
|
|
|
1947
1966
|
attachments.set(parent_doc, :adopted_docs, docs)
|
|
1948
1967
|
end
|
|
1949
1968
|
|
|
1969
|
+
# True when new_node's owning document (or the absorber its
|
|
1970
|
+
# pool moved into) was absorbed into node's document: the pool
|
|
1971
|
+
# is already the destination's, so splices must not take the
|
|
1972
|
+
# adopt/copy paths. The engine re-parents pool ownership but
|
|
1973
|
+
# NOT the node→document handles (they keep answering the
|
|
1974
|
+
# original parse document), so the absorbed-sources set is the
|
|
1975
|
+
# only authority. The binding's splice paths also drop
|
|
1976
|
+
# absorbed-source nodes upstream (their absorbed_into? check
|
|
1977
|
+
# is not transitive) — route absorbed splices through the raw
|
|
1978
|
+
# engine faces.
|
|
1979
|
+
def source_absorbed_into?(node, new_node)
|
|
1980
|
+
return false unless NATIVE_DOC_ABSORB
|
|
1981
|
+
|
|
1982
|
+
node_doc = node.is_a?(NN) ? NN_DOCUMENT.bind_call(node) : node.document
|
|
1983
|
+
new_doc = new_node.is_a?(NN) ? NN_DOCUMENT.bind_call(new_node) : new_node.document
|
|
1984
|
+
return false if node_doc.nil? || new_doc.nil? || node_doc.equal?(new_doc)
|
|
1985
|
+
|
|
1986
|
+
owner = new_doc.is_a?(NN) ? to_binding(new_doc) : new_doc
|
|
1987
|
+
dest = node_doc.is_a?(NN) ? to_binding(node_doc) : node_doc
|
|
1988
|
+
attachments.get(dest, :absorbed_sources)&.include?(owner) || false
|
|
1989
|
+
end
|
|
1990
|
+
|
|
1991
|
+
# 1.9.311 (engine #1548): move semantics for doomed splice
|
|
1992
|
+
# sources. Absorbing the child's document into the target
|
|
1993
|
+
# transfers pool ownership, so every later attach of its nodes
|
|
1994
|
+
# moves by reference instead of copying (the #376 structural
|
|
1995
|
+
# rebuild and its pointer-identity loss disappear; the engine
|
|
1996
|
+
# re-parents node→document so the same-doc gates resolve).
|
|
1997
|
+
# Absorb exactly once per (dest, owner): re-absorbing is an
|
|
1998
|
+
# engine error. On success the source's free becomes
|
|
1999
|
+
# handle-only — the reverse attachment pins the new pool owner
|
|
2000
|
+
# on the source, so a living source can never outlive the
|
|
2001
|
+
# memory its reads still touch.
|
|
2002
|
+
def absorb_source_document(dest, owner)
|
|
2003
|
+
return nil unless NATIVE_DOC_ABSORB
|
|
2004
|
+
return :direct if attachments.get(dest, :absorbed_sources)&.include?(owner)
|
|
2005
|
+
|
|
2006
|
+
# The child's document handle keeps answering its original
|
|
2007
|
+
# parse document even after that document's pool was
|
|
2008
|
+
# absorbed upward — the pool follows the absorber. On a
|
|
2009
|
+
# rejected absorb (already owned elsewhere), chase the
|
|
2010
|
+
# absorber chain and absorb the current pool owner. Returns
|
|
2011
|
+
# :direct on a first-attempt absorb, :chased when the pool
|
|
2012
|
+
# had to be taken from an upstream absorber (the binding's
|
|
2013
|
+
# splice paths drop such nodes — their absorbed_into? checks
|
|
2014
|
+
# are not transitive — so only raw engine faces may follow a
|
|
2015
|
+
# chase), nil when absorb is unavailable/refused.
|
|
2016
|
+
status = :direct
|
|
2017
|
+
candidate = owner
|
|
2018
|
+
while candidate
|
|
2019
|
+
begin
|
|
2020
|
+
dest.absorb(candidate)
|
|
2021
|
+
rescue ::Leptris::XML::Error, ::RuntimeError
|
|
2022
|
+
status = :chased
|
|
2023
|
+
candidate = attachments.get(candidate, :absorbed_by)
|
|
2024
|
+
next
|
|
2025
|
+
end
|
|
2026
|
+
sources = attachments.get(dest, :absorbed_sources) || []
|
|
2027
|
+
sources << candidate
|
|
2028
|
+
sub = attachments.get(candidate, :absorbed_sources)
|
|
2029
|
+
sources.concat(sub) if sub
|
|
2030
|
+
sources << owner unless sources.include?(owner)
|
|
2031
|
+
attachments.set(dest, :absorbed_sources, sources)
|
|
2032
|
+
# Un-pin: a pinned absorbed owner lives until exit and
|
|
2033
|
+
# its finalizer double-frees the transferred pool
|
|
2034
|
+
# (leptris-ruby#386).
|
|
2035
|
+
docs = attachments.get(dest, :adopted_docs)
|
|
2036
|
+
if docs
|
|
2037
|
+
docs.delete(owner)
|
|
2038
|
+
attachments.set(dest, :adopted_docs, docs)
|
|
2039
|
+
end
|
|
2040
|
+
attachments.set(candidate, :absorbed_by, dest)
|
|
2041
|
+
attachments.set(owner, :absorbed_by, dest) unless owner.equal?(candidate)
|
|
2042
|
+
return status
|
|
2043
|
+
end
|
|
2044
|
+
nil
|
|
2045
|
+
end
|
|
2046
|
+
|
|
1950
2047
|
def add_child(parent, child)
|
|
2048
|
+
# 1.9.311: binding-family children (created PIs, texts...) —
|
|
2049
|
+
# a DIRECT absorb makes the binding add below move the node
|
|
2050
|
+
# by reference. A source already owned elsewhere would need
|
|
2051
|
+
# a chase, and the binding's splice drops chased sources
|
|
2052
|
+
# upstream (its absorbed_into? checks are not transitive) —
|
|
2053
|
+
# those keep the pin + adoption copy. Entity references
|
|
2054
|
+
# keep the marker flow below.
|
|
2055
|
+
if NATIVE_DOC_ABSORB && !child.is_a?(NN) &&
|
|
2056
|
+
child.is_a?(::Leptris::XML::Node) &&
|
|
2057
|
+
!child.is_a?(CustomizedLeptris::EntityReference)
|
|
2058
|
+
child_doc = child.document
|
|
2059
|
+
parent_doc = parent.is_a?(NN) ? NN_DOCUMENT.bind_call(parent) : parent.document
|
|
2060
|
+
if child_doc && parent_doc && !child_doc.equal?(parent_doc)
|
|
2061
|
+
owner = to_binding(child_doc)
|
|
2062
|
+
absorb_source_document(to_binding(parent_doc), owner) if
|
|
2063
|
+
attachments.get(owner, :absorbed_by).nil?
|
|
2064
|
+
end
|
|
2065
|
+
end
|
|
1951
2066
|
if NATIVE_READ_LAYER && !(NATIVE_MUTATIONS_COHERENT &&
|
|
1952
2067
|
parent.is_a?(::Leptris::XML::NativeNode) &&
|
|
1953
2068
|
child.is_a?(::Leptris::XML::NativeNode))
|
|
@@ -1973,12 +2088,19 @@ module Moxml
|
|
|
1973
2088
|
# binding's create faces — correct tree; the caller's
|
|
1974
2089
|
# wrapper keeps addressing the source original (pointer
|
|
1975
2090
|
# identity waits on the upstream installed-handle face).
|
|
1976
|
-
#
|
|
2091
|
+
# 1.9.311 (engine #1548): absorb the source document
|
|
2092
|
+
# first and the plain add MOVES the element by reference
|
|
2093
|
+
# (O(1), no copy, no live-set doubling) — the rebuild
|
|
2094
|
+
# only fires when absorb is refused. Non-element kinds
|
|
2095
|
+
# keep the pin + plain C add: the binding's splices drop
|
|
2096
|
+
# absorbed-source nodes upstream.
|
|
1977
2097
|
child_doc = NN_DOCUMENT.bind_call(child)
|
|
1978
2098
|
parent_doc = NN_DOCUMENT.bind_call(parent)
|
|
1979
2099
|
if child_doc && parent_doc &&
|
|
1980
2100
|
!child_doc.equal?(parent_doc) && child.node_type == :element
|
|
1981
|
-
|
|
2101
|
+
absorb_source_document(to_binding(parent_doc), to_binding(child_doc))
|
|
2102
|
+
return structural_adopt(parent, child) unless
|
|
2103
|
+
source_absorbed_into?(parent, child)
|
|
1982
2104
|
end
|
|
1983
2105
|
|
|
1984
2106
|
doc = NN_DOCUMENT.bind_call(parent)
|
|
@@ -2015,7 +2137,18 @@ module Moxml
|
|
|
2015
2137
|
node.document.add_pi(new_node.target, new_node.content.to_s)
|
|
2016
2138
|
return new_node
|
|
2017
2139
|
end
|
|
2018
|
-
|
|
2140
|
+
# 1.9.311: an absorbed element source moves through the raw
|
|
2141
|
+
# engine faces — the binding's sibling insert drops it.
|
|
2142
|
+
if new_node.is_a?(::Leptris::XML::Element) &&
|
|
2143
|
+
!new_node.is_a?(::Leptris::XML::NativeNode)
|
|
2144
|
+
new_doc = new_node.document
|
|
2145
|
+
node_doc = node.document
|
|
2146
|
+
if new_doc && node_doc && !new_doc.equal?(node_doc)
|
|
2147
|
+
absorb_source_document(node_doc, new_doc)
|
|
2148
|
+
end
|
|
2149
|
+
end
|
|
2150
|
+
return node.add_previous_sibling(new_node) if node.is_a?(::Leptris::XML::Element) &&
|
|
2151
|
+
!source_absorbed_into?(node, new_node)
|
|
2019
2152
|
|
|
2020
2153
|
# Non-element receivers: raw-FFI anchor (issue #245, same
|
|
2021
2154
|
# shape as add_next_sibling).
|
|
@@ -2034,7 +2167,18 @@ module Moxml
|
|
|
2034
2167
|
new_node = to_binding(new_node)
|
|
2035
2168
|
end
|
|
2036
2169
|
pin_foreign_owner_document(node, new_node)
|
|
2037
|
-
|
|
2170
|
+
# 1.9.311: an absorbed element source moves through the raw
|
|
2171
|
+
# engine faces — the binding's sibling insert drops it.
|
|
2172
|
+
if new_node.is_a?(::Leptris::XML::Element) &&
|
|
2173
|
+
!new_node.is_a?(::Leptris::XML::NativeNode)
|
|
2174
|
+
new_doc = new_node.document
|
|
2175
|
+
node_doc = node.document
|
|
2176
|
+
if new_doc && node_doc && !new_doc.equal?(node_doc)
|
|
2177
|
+
absorb_source_document(node_doc, new_doc)
|
|
2178
|
+
end
|
|
2179
|
+
end
|
|
2180
|
+
return node.add_next_sibling(new_node) if node.is_a?(::Leptris::XML::Element) &&
|
|
2181
|
+
!source_absorbed_into?(node, new_node)
|
|
2038
2182
|
|
|
2039
2183
|
# Non-element receivers (Text/Comment/CDATA): the binding's
|
|
2040
2184
|
# method is Element-only, but the C insert anchors on any
|
|
@@ -2132,6 +2276,10 @@ module Moxml
|
|
|
2132
2276
|
# content node (text/comment/CDATA/PI) is replaced by
|
|
2133
2277
|
# unlinking and re-inserting: after an element sibling when
|
|
2134
2278
|
# one exists, else appended (end-of-list) to the parent.
|
|
2279
|
+
# Raw engine faces, not the binding add: a parent resolved
|
|
2280
|
+
# through an absorbed source's stale document handle sends
|
|
2281
|
+
# same-pool adds down the adoption path, which drops
|
|
2282
|
+
# non-element kinds (leptris-ruby#376 family).
|
|
2135
2283
|
parent = node.parent
|
|
2136
2284
|
unless parent
|
|
2137
2285
|
raise Moxml::DocumentStructureError.new(
|
|
@@ -2142,9 +2290,17 @@ module Moxml
|
|
|
2142
2290
|
prev = node.previous_sibling
|
|
2143
2291
|
node.unlink
|
|
2144
2292
|
if prev.is_a?(::Leptris::XML::Element)
|
|
2145
|
-
|
|
2293
|
+
::Leptris::XML::FFI.check_status(
|
|
2294
|
+
::Leptris::XML::FFI.leptris_element_insert_after(
|
|
2295
|
+
prev.c_ptr, new_node.c_ptr
|
|
2296
|
+
),
|
|
2297
|
+
)
|
|
2146
2298
|
else
|
|
2147
|
-
|
|
2299
|
+
::Leptris::XML::FFI.check_status(
|
|
2300
|
+
::Leptris::XML::FFI.leptris_element_append_child(
|
|
2301
|
+
parent.c_ptr, new_node.c_ptr
|
|
2302
|
+
),
|
|
2303
|
+
)
|
|
2148
2304
|
end
|
|
2149
2305
|
new_node
|
|
2150
2306
|
end
|
data/lib/moxml/element.rb
CHANGED
|
@@ -127,8 +127,8 @@ module Moxml
|
|
|
127
127
|
# writes bump it (see invalidate_attribute_value_cache!).
|
|
128
128
|
def attribute_pairs
|
|
129
129
|
generation = attribute_value_generation
|
|
130
|
-
@
|
|
131
|
-
@
|
|
130
|
+
@attribute_pairs = nil if @attribute_pairs_gen != generation
|
|
131
|
+
@attribute_pairs_gen = generation
|
|
132
132
|
@attribute_pairs ||= adapter.attribute_pairs(@native)
|
|
133
133
|
end
|
|
134
134
|
|
data/lib/moxml/version.rb
CHANGED
|
@@ -64,22 +64,12 @@ 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
|
|
74
67
|
element.add_child(pi)
|
|
75
68
|
pi.remove
|
|
76
69
|
expect(element.children).to be_empty
|
|
77
70
|
end
|
|
78
71
|
|
|
79
72
|
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
|
|
83
73
|
element.add_child(pi)
|
|
84
74
|
text = doc.create_text("replacement")
|
|
85
75
|
pi.replace(text)
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require "spec_helper"
|
|
4
4
|
|
|
5
|
-
# leptris >= 1.9.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# the copy lands in the tree, correct names, doc-owned.
|
|
5
|
+
# Cross-document adoption contract. leptris >= 1.9.311 absorbs the
|
|
6
|
+
# source document's pool into the destination (engine #1548) so
|
|
7
|
+
# attaches move by reference — wrapper identity survives; older
|
|
8
|
+
# bindings deep-copy at the splice (leptris-ruby#1528) and only the
|
|
9
|
+
# ADOPTION shape is pinned (the copy lands in the tree, correct
|
|
10
|
+
# names, doc-owned).
|
|
12
11
|
RSpec.describe "leptris cross-document adoption" do
|
|
13
12
|
let(:ctx) { Moxml.new(:leptris) }
|
|
14
13
|
|
|
@@ -31,4 +30,74 @@ RSpec.describe "leptris cross-document adoption" do
|
|
|
31
30
|
expect(doc.root.children.to_a[1].name).to eq("x")
|
|
32
31
|
expect(doc.at("//r/x")).not_to be_nil
|
|
33
32
|
end
|
|
33
|
+
|
|
34
|
+
context "when the binding offers Document#absorb (>= 1.9.311)" do
|
|
35
|
+
before do
|
|
36
|
+
unless Moxml::Adapter::Leptris::NATIVE_DOC_ABSORB
|
|
37
|
+
skip "binding #{Leptris::VERSION} has no absorb face"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "moves the child by reference — the wrapper addresses the attached node" do
|
|
42
|
+
doc = ctx.parse("<r><a/></r>")
|
|
43
|
+
other = ctx.parse("<other><new/></other>")
|
|
44
|
+
child = other.root.children.first
|
|
45
|
+
doc.root.add_child(child)
|
|
46
|
+
expect(doc.root.children.last).to eq(child)
|
|
47
|
+
expect(doc.root.to_xml).not_to include("<other")
|
|
48
|
+
child["probe"] = "1"
|
|
49
|
+
expect(doc.at("//r/new")["probe"]).to eq("1")
|
|
50
|
+
expect(child.parent.name).to eq("r")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "keeps the pre-311 adoption behavior for non-element kinds" do
|
|
54
|
+
# Absorb is scoped to element moves: the binding's splice paths
|
|
55
|
+
# drop absorbed-source nodes upstream (their absorbed_into?
|
|
56
|
+
# check is not transitive), so binding-family children keep
|
|
57
|
+
# the 1.9.304 adoption copy.
|
|
58
|
+
doc = ctx.parse("<r/>")
|
|
59
|
+
other = ctx.parse("<other/>")
|
|
60
|
+
pi = other.create_processing_instruction("xml-stylesheet", 'href="s.xsl"')
|
|
61
|
+
other.root.add_child(pi)
|
|
62
|
+
doc.root.add_child(pi)
|
|
63
|
+
expect(doc.root.to_xml).to include("<?xml-stylesheet")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "keeps the tree alive after the source document is collected" do
|
|
67
|
+
doc = ctx.parse("<r/>")
|
|
68
|
+
child = nil
|
|
69
|
+
3.times do
|
|
70
|
+
other = ctx.parse("<other><deep><leaf v='1'>t</leaf></deep></other>")
|
|
71
|
+
child = other.root.children.first
|
|
72
|
+
doc.root.add_child(child)
|
|
73
|
+
nil
|
|
74
|
+
GC.start
|
|
75
|
+
end
|
|
76
|
+
expect(doc.at("//r/deep/leaf")["v"]).to eq("1")
|
|
77
|
+
expect(doc.at("//r/deep/leaf").text).to eq("t")
|
|
78
|
+
expect(child.name).to eq("deep")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "keeps a living source readable after its pool is absorbed" do
|
|
82
|
+
doc = ctx.parse("<r/>")
|
|
83
|
+
other = ctx.parse("<other><x>1</x></other>")
|
|
84
|
+
doc.root.add_child(other.root.children.first)
|
|
85
|
+
expect(other.root.name).to eq("other")
|
|
86
|
+
expect(other.at("//other")).not_to be_nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "moves elements whose pool was absorbed through an intermediate document" do
|
|
90
|
+
# scratch → mid (first splice) → doc: the chase takes the pool
|
|
91
|
+
# from mid; the raw engine add then moves by pool identity.
|
|
92
|
+
doc = ctx.parse("<r/>")
|
|
93
|
+
mid = ctx.parse("<mid/>")
|
|
94
|
+
scratch = ctx.parse("<scratch><deep v='1'>t</deep></scratch>")
|
|
95
|
+
mid.root.add_child(scratch.root.children.first)
|
|
96
|
+
expect(mid.root.to_xml).to include("<deep")
|
|
97
|
+
doc.root.add_child(mid.root.children.first)
|
|
98
|
+
expect(doc.at("//r/deep")["v"]).to eq("1")
|
|
99
|
+
expect(doc.root.to_xml).not_to include("<mid>")
|
|
100
|
+
expect(mid.root.to_xml).not_to include("<deep")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
34
103
|
end
|
|
@@ -39,4 +39,23 @@ RSpec.describe Moxml::Element do
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
|
+
|
|
43
|
+
context "when memoized" do
|
|
44
|
+
it "returns the same object on repeated reads" do
|
|
45
|
+
with_adapter(:leptris) do |ctx|
|
|
46
|
+
root = ctx.parse(xml).root
|
|
47
|
+
expect(root.attribute_pairs).to equal(root.attribute_pairs)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "refreshes after a value write" do
|
|
52
|
+
with_adapter(:leptris) do |ctx|
|
|
53
|
+
root = ctx.parse(xml).root
|
|
54
|
+
before = root.attribute_pairs
|
|
55
|
+
root["id"] = "r9"
|
|
56
|
+
expect(root.attribute_pairs).not_to equal(before)
|
|
57
|
+
expect(root.attribute_pairs).to include(["id", "r9"])
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
42
61
|
end
|