moxml 0.5.100 → 0.5.102
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/dependent-tests.yml +7 -0
- data/.github/workflows/opal.yml +7 -0
- data/.github/workflows/rake.yml +7 -0
- data/.github/workflows/round-trip.yml +7 -0
- data/.github/workflows/windows.yml +7 -0
- data/lib/moxml/adapter/base.rb +28 -2
- data/lib/moxml/adapter/leptris/document_parts.rb +1 -1
- data/lib/moxml/adapter/leptris.rb +131 -12
- data/lib/moxml/adapter/libxml.rb +3 -2
- data/lib/moxml/adapter/nokogiri.rb +4 -0
- data/lib/moxml/cdata.rb +3 -0
- data/lib/moxml/comment.rb +3 -0
- data/lib/moxml/context.rb +12 -0
- data/lib/moxml/document.rb +40 -0
- data/lib/moxml/element.rb +49 -0
- data/lib/moxml/node.rb +219 -23
- data/lib/moxml/node_set.rb +26 -0
- data/lib/moxml/text.rb +3 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xpath/compiler.rb +21 -18
- data/spec/moxml/adapter/leptris_binding_cap_spec.rb +29 -0
- data/spec/moxml/adapter/leptris_xpath_identity_spec.rb +49 -0
- data/spec/moxml/at_search_spec.rb +386 -0
- data/spec/moxml/xpath/union_spec.rb +42 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d182614a13bd3934c6d6dc1bb9b137015dc1daa124aeac24df40246ea3109103
|
|
4
|
+
data.tar.gz: 51d96b5f964b185772d4378913c5e48e4775c41cf5fd1a3e5cf942576fa33183
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4e9ae7331917e4fb295aa541028cb233d889127e4e32982d24ad1d696df668f52d223b70a1c6f2e96360f72138abf36a2eb055f15b9f48c069cb7b421191cfe
|
|
7
|
+
data.tar.gz: '0362854ca321c56e434a0f030232088d200d948dfe231c2d2cf4d2017c3af7316610e3f18ece3ca85a9a9a2c0d98f431c9f3bb465f186fe39ae23b63ea2e9a7b'
|
|
@@ -13,6 +13,13 @@ on:
|
|
|
13
13
|
repository_dispatch:
|
|
14
14
|
types: [ release-passed ]
|
|
15
15
|
|
|
16
|
+
# One live run per workflow+event+ref: a superseded push to the
|
|
17
|
+
# same PR/branch cancels its predecessors instead of queueing
|
|
18
|
+
# behind them (the runner queue otherwise stacks generations).
|
|
19
|
+
concurrency:
|
|
20
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
|
|
16
23
|
jobs:
|
|
17
24
|
rake:
|
|
18
25
|
uses: metanorma/ci/.github/workflows/dependent-rake.yml@main
|
data/.github/workflows/opal.yml
CHANGED
|
@@ -6,6 +6,13 @@ on:
|
|
|
6
6
|
tags: [ v* ]
|
|
7
7
|
pull_request:
|
|
8
8
|
|
|
9
|
+
# One live run per workflow+event+ref: a superseded push to the
|
|
10
|
+
# same PR/branch cancels its predecessors instead of queueing
|
|
11
|
+
# behind them (the runner queue otherwise stacks generations).
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
9
16
|
permissions:
|
|
10
17
|
contents: read
|
|
11
18
|
|
data/.github/workflows/rake.yml
CHANGED
|
@@ -6,6 +6,13 @@ on:
|
|
|
6
6
|
tags: [ v* ]
|
|
7
7
|
pull_request:
|
|
8
8
|
|
|
9
|
+
# One live run per workflow+event+ref: a superseded push to the
|
|
10
|
+
# same PR/branch cancels its predecessors instead of queueing
|
|
11
|
+
# behind them (the runner queue otherwise stacks generations).
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
9
16
|
permissions:
|
|
10
17
|
contents: read
|
|
11
18
|
|
|
@@ -5,6 +5,13 @@ on:
|
|
|
5
5
|
branches: [ main ]
|
|
6
6
|
pull_request:
|
|
7
7
|
|
|
8
|
+
# One live run per workflow+event+ref: a superseded push to the
|
|
9
|
+
# same PR/branch cancels its predecessors instead of queueing
|
|
10
|
+
# behind them (the runner queue otherwise stacks generations).
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
8
15
|
permissions:
|
|
9
16
|
contents: read
|
|
10
17
|
|
|
@@ -6,6 +6,13 @@ on:
|
|
|
6
6
|
pull_request:
|
|
7
7
|
workflow_dispatch:
|
|
8
8
|
|
|
9
|
+
# One live run per workflow+event+ref: a superseded push to the
|
|
10
|
+
# same PR/branch cancels its predecessors instead of queueing
|
|
11
|
+
# behind them (the runner queue otherwise stacks generations).
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
9
16
|
permissions:
|
|
10
17
|
contents: read
|
|
11
18
|
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -47,8 +47,11 @@ module Moxml
|
|
|
47
47
|
|
|
48
48
|
def parse_fragment(xml, _context = nil)
|
|
49
49
|
doc = parse("<m>#{xml}</m>")
|
|
50
|
-
root
|
|
51
|
-
|
|
50
|
+
# doc.root is a wrapper (adapter root overrides answer
|
|
51
|
+
# patched/unpatched natives indifferently); children() and
|
|
52
|
+
# every adapter below expect natives.
|
|
53
|
+
root_node = doc.root
|
|
54
|
+
root_node ? children(root_node.native) : []
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
# Streaming incremental parse (leptris engine): yields each
|
|
@@ -146,6 +149,22 @@ module Moxml
|
|
|
146
149
|
nil
|
|
147
150
|
end
|
|
148
151
|
|
|
152
|
+
# Canonical node identity: adapters carrying more than one
|
|
153
|
+
# Ruby representation over one C node answer the representation
|
|
154
|
+
# the tree walk mints, so every wrap path resolves one wrapper
|
|
155
|
+
# per logical node (moxml#311). Default: the node is canonical.
|
|
156
|
+
def canonical_native(node)
|
|
157
|
+
node
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Element-only children: adapters whose engine carries the
|
|
161
|
+
# face override (leptris C filter, nokogiri native); the
|
|
162
|
+
# default type-filters the native child list so non-element
|
|
163
|
+
# wrappers never mint on element-only traversals.
|
|
164
|
+
def element_children(node)
|
|
165
|
+
children(node).select { |child| node_type(child) == :element }
|
|
166
|
+
end
|
|
167
|
+
|
|
149
168
|
# Prefixed-attribute value fast path: adapters whose engine
|
|
150
169
|
# resolves expanded-name (uri, local) lookups natively answer
|
|
151
170
|
# the value here; others fall back to the resolver's
|
|
@@ -386,6 +405,13 @@ namespace_validation_mode: :strict)
|
|
|
386
405
|
true
|
|
387
406
|
end
|
|
388
407
|
|
|
408
|
+
# Serialization of the node's children (Element#inner_xml).
|
|
409
|
+
# Built over the children/serialize contract; adapters with a
|
|
410
|
+
# native inner-serialization face may override.
|
|
411
|
+
def inner_xml(node)
|
|
412
|
+
children(node).map { |child| serialize(child) }.join
|
|
413
|
+
end
|
|
414
|
+
|
|
389
415
|
# Whether the engine offers a bulk materialization path for
|
|
390
416
|
# Materializer (issue #132). When true, the adapter gets
|
|
391
417
|
# #materialize_fields(native, buffers, &block) — fill the
|
|
@@ -48,7 +48,7 @@ module Moxml
|
|
|
48
48
|
# equal?-based exclusion (canon's document-element skip)
|
|
49
49
|
# silently breaks (issue #219). Mint-on-miss converges
|
|
50
50
|
# both accessors on the same native object.
|
|
51
|
-
children.map! { |child|
|
|
51
|
+
children.map! { |child| native_twin(doc, child) } if NATIVE_READ_LAYER
|
|
52
52
|
children
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -100,6 +100,10 @@ module Moxml
|
|
|
100
100
|
NN_NEXT_SIBLING = NN.instance_method(:next_sibling)
|
|
101
101
|
NN_CONTENT = NN.instance_method(:content)
|
|
102
102
|
NN_CHILDREN = NN.instance_method(:children)
|
|
103
|
+
# The C element filter — captured because ElementBehavior's
|
|
104
|
+
# element_children alias shadows the face on Identity natives
|
|
105
|
+
# (a plain dispatch would recurse into the wrapper method).
|
|
106
|
+
NN_ELEMENT_CHILDREN = NN.instance_method(:element_children)
|
|
103
107
|
NN_NODE_TYPE = NN.instance_method(:node_type)
|
|
104
108
|
NN_DOCUMENT = NN.instance_method(:document)
|
|
105
109
|
NN_ADD_CHILD = NN.instance_method(:add_child)
|
|
@@ -168,6 +172,18 @@ module Moxml
|
|
|
168
172
|
BINDING_OF_CAP = 8_192
|
|
169
173
|
|
|
170
174
|
class << self
|
|
175
|
+
# WeakMap has no #clear — 0.5.100's valve crashed every parse
|
|
176
|
+
# crossing the cap on 3.4+ (NoMethodError). Replace the
|
|
177
|
+
# registry wholesale, type-preserving: the WeakMap flavor
|
|
178
|
+
# stays weak, the strong Hash keeps compare_by_identity.
|
|
179
|
+
def fresh_node_registry
|
|
180
|
+
if Context::WEAK_WRAPPERS
|
|
181
|
+
ObjectSpace::WeakMap.new
|
|
182
|
+
else
|
|
183
|
+
{}.compare_by_identity
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
171
187
|
# native -> bridged binding node. The bridge object is
|
|
172
188
|
# identity-stable for the native's lifetime (the binding's
|
|
173
189
|
# per-document wrap cache), so repeat bridges — the write
|
|
@@ -187,7 +203,7 @@ module Moxml
|
|
|
187
203
|
doc = doc_for(node)
|
|
188
204
|
ptr = ::FFI::Pointer.new(node.address)
|
|
189
205
|
bridged = ::Leptris::XML::Node.wrap(ptr, doc)
|
|
190
|
-
@binding_of
|
|
206
|
+
@binding_of = fresh_node_registry if @binding_of.size >= BINDING_OF_CAP
|
|
191
207
|
@binding_of[node] = bridged unless bridged.nil?
|
|
192
208
|
bridged
|
|
193
209
|
end
|
|
@@ -428,7 +444,7 @@ module Moxml
|
|
|
428
444
|
end
|
|
429
445
|
|
|
430
446
|
def record_native_doc(root_native, doc)
|
|
431
|
-
@native_doc_roots
|
|
447
|
+
@native_doc_roots = fresh_node_registry if @native_doc_roots.size >= NATIVE_DOC_ROOTS_CAP
|
|
432
448
|
@native_doc_roots[root_native] = doc
|
|
433
449
|
end
|
|
434
450
|
|
|
@@ -1385,6 +1401,19 @@ module Moxml
|
|
|
1385
1401
|
end
|
|
1386
1402
|
end
|
|
1387
1403
|
|
|
1404
|
+
# Element-only children through the engine-level filter: the
|
|
1405
|
+
# C face mints element-kind natives directly (klass-children),
|
|
1406
|
+
# so interleaved text/comment nodes cost nothing. Entity
|
|
1407
|
+
# markers are text-kind — the element filter excludes them by
|
|
1408
|
+
# construction, no marker split needed.
|
|
1409
|
+
def element_children(node)
|
|
1410
|
+
if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
|
|
1411
|
+
return NN_ELEMENT_CHILDREN.bind_call(node).to_a
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1414
|
+
to_binding(node).element_children
|
|
1415
|
+
end
|
|
1416
|
+
|
|
1388
1417
|
def children(node, entity_bearing: false)
|
|
1389
1418
|
# NATIVE_READ_LAYER gates the whole keep-path: it consults
|
|
1390
1419
|
# NN_DOCUMENT (defined only with the native layer), and
|
|
@@ -1511,7 +1540,7 @@ module Moxml
|
|
|
1511
1540
|
# accessor converges on one native per node (issue #219).
|
|
1512
1541
|
# Binding natives (Attribute/Attr, synthetic wrappers) and
|
|
1513
1542
|
# docless nodes pass through unchanged.
|
|
1514
|
-
def
|
|
1543
|
+
def native_twin(doc, node)
|
|
1515
1544
|
return node unless node.is_a?(::Leptris::XML::Element)
|
|
1516
1545
|
|
|
1517
1546
|
cache = doc.native_cache
|
|
@@ -1584,10 +1613,25 @@ module Moxml
|
|
|
1584
1613
|
[copied, pointers]
|
|
1585
1614
|
end
|
|
1586
1615
|
|
|
1587
|
-
# The binding reports the root element
|
|
1588
|
-
# moxml contract roots at the
|
|
1616
|
+
# The binding reports the root element and the document-level
|
|
1617
|
+
# parts as parentless; the moxml contract roots them at the
|
|
1618
|
+
# document. DocType carries no #parent at all (plain
|
|
1619
|
+
# c_ptr+document value object) and a parentless PI is a
|
|
1620
|
+
# document-level one by construction — in-tree parts have
|
|
1621
|
+
# element parents.
|
|
1589
1622
|
def root_parent(node)
|
|
1590
|
-
|
|
1623
|
+
return node.document if node.is_a?(::Leptris::XML::DocType)
|
|
1624
|
+
|
|
1625
|
+
parent = node.parent
|
|
1626
|
+
return parent if parent
|
|
1627
|
+
|
|
1628
|
+
doc = node.document
|
|
1629
|
+
return nil unless doc
|
|
1630
|
+
|
|
1631
|
+
return doc if doc.root == node ||
|
|
1632
|
+
node.is_a?(::Leptris::XML::ProcessingInstruction)
|
|
1633
|
+
|
|
1634
|
+
nil
|
|
1591
1635
|
end
|
|
1592
1636
|
|
|
1593
1637
|
def next_sibling(node)
|
|
@@ -1625,7 +1669,7 @@ module Moxml
|
|
|
1625
1669
|
# Through the same address-keyed cache as the children
|
|
1626
1670
|
# path: NativeNode.from alone mints a fresh object per
|
|
1627
1671
|
# call on 1.9.163.x, splitting the wrappers (issue #219).
|
|
1628
|
-
native =
|
|
1672
|
+
native = native_twin(document, r)
|
|
1629
1673
|
record_native_doc(native, document)
|
|
1630
1674
|
return native
|
|
1631
1675
|
end
|
|
@@ -1686,8 +1730,8 @@ module Moxml
|
|
|
1686
1730
|
# convergence (#219) hands every accessor — children
|
|
1687
1731
|
# included — the SAME native, so the wrapper identity
|
|
1688
1732
|
# holds across later traversals.
|
|
1689
|
-
|
|
1690
|
-
|
|
1733
|
+
native_twin(doc,
|
|
1734
|
+
::Leptris::XML::Node.wrap(::FFI::Pointer.new(addr), doc))
|
|
1691
1735
|
end
|
|
1692
1736
|
|
|
1693
1737
|
def attributes(element)
|
|
@@ -1839,6 +1883,7 @@ module Moxml
|
|
|
1839
1883
|
node = to_binding(node)
|
|
1840
1884
|
new_node = to_binding(new_node)
|
|
1841
1885
|
end
|
|
1886
|
+
pin_foreign_owner_document(node, new_node)
|
|
1842
1887
|
# A PI inserted before the root lives at document level in
|
|
1843
1888
|
# libleptris's model, not in the element tree.
|
|
1844
1889
|
if new_node.is_a?(::Leptris::XML::ProcessingInstruction) &&
|
|
@@ -1864,6 +1909,7 @@ module Moxml
|
|
|
1864
1909
|
node = to_binding(node)
|
|
1865
1910
|
new_node = to_binding(new_node)
|
|
1866
1911
|
end
|
|
1912
|
+
pin_foreign_owner_document(node, new_node)
|
|
1867
1913
|
return node.add_next_sibling(new_node) if node.is_a?(::Leptris::XML::Element)
|
|
1868
1914
|
|
|
1869
1915
|
# Non-element receivers (Text/Comment/CDATA): the binding's
|
|
@@ -1935,7 +1981,19 @@ module Moxml
|
|
|
1935
1981
|
node = to_binding(node)
|
|
1936
1982
|
new_node = to_binding(new_node)
|
|
1937
1983
|
end
|
|
1938
|
-
|
|
1984
|
+
if node.is_a?(::Leptris::XML::Element)
|
|
1985
|
+
# NOT Element#replace: it detaches via
|
|
1986
|
+
# leptris_element_remove_child, which leaves the replaced
|
|
1987
|
+
# node's sibling links pointing into the live tree. A later
|
|
1988
|
+
# append of that node splices the stale run under the new
|
|
1989
|
+
# parent, and when the run contains the append target the
|
|
1990
|
+
# tree cycles (standoc term cleanup does
|
|
1991
|
+
# t << n.replace(t2)). Insert + unlink instead: the unlink
|
|
1992
|
+
# path clears the sibling links.
|
|
1993
|
+
node.add_next_sibling(new_node)
|
|
1994
|
+
node.unlink
|
|
1995
|
+
return new_node
|
|
1996
|
+
end
|
|
1939
1997
|
|
|
1940
1998
|
# libleptris only offers element-anchored insertion, so a
|
|
1941
1999
|
# content node (text/comment/CDATA/PI) is replaced by
|
|
@@ -2118,6 +2176,29 @@ module Moxml
|
|
|
2118
2176
|
engine_xpath(node, expression, namespaces)
|
|
2119
2177
|
end
|
|
2120
2178
|
|
|
2179
|
+
# Canonical node identity (moxml#311): the native layer and
|
|
2180
|
+
# the binding layer hold separate Ruby objects over one C
|
|
2181
|
+
# node, and only the native cache carries the tree-walk
|
|
2182
|
+
# identity the @parent_node invalidation chain rides on.
|
|
2183
|
+
# Resolve binding-layer nodes (xpath results, engine results)
|
|
2184
|
+
# to the walked instance when one exists — a memoized
|
|
2185
|
+
# children holder implies a prior walk, so a cache miss means
|
|
2186
|
+
# no holder exists and the binding node is safe as-is.
|
|
2187
|
+
def canonical_native(node)
|
|
2188
|
+
return node unless NATIVE_READ_LAYER
|
|
2189
|
+
return node if node.is_a?(::Leptris::XML::NativeNode)
|
|
2190
|
+
return node unless node.is_a?(::Leptris::XML::Node)
|
|
2191
|
+
|
|
2192
|
+
doc = node.document
|
|
2193
|
+
return node if doc.nil?
|
|
2194
|
+
|
|
2195
|
+
# Only a contract-carrying hit is the walked canonical —
|
|
2196
|
+
# plain NN twins (entity references, #219 miss-mints) have
|
|
2197
|
+
# no Identity class and keep the binding representation.
|
|
2198
|
+
hit = doc.native_cache[node.c_ptr.address]
|
|
2199
|
+
hit.is_a?(::Moxml::Node) ? hit : node
|
|
2200
|
+
end
|
|
2201
|
+
|
|
2121
2202
|
def at_xpath(node, expression, namespaces = {})
|
|
2122
2203
|
native = native_xpath(node, expression, namespaces, first_only: true)
|
|
2123
2204
|
return native unless native.nil?
|
|
@@ -2138,7 +2219,22 @@ module Moxml
|
|
|
2138
2219
|
node = to_binding(node)
|
|
2139
2220
|
end
|
|
2140
2221
|
return nil unless native_context_node?(node)
|
|
2141
|
-
|
|
2222
|
+
# Hash-bound xmlns rides native on probing bindings
|
|
2223
|
+
# (leptris resolves the reserved prefix from the hash);
|
|
2224
|
+
# empty mappings (xmlns="") select no-namespace elements
|
|
2225
|
+
# and stay on the Ruby engine — the C pairs-set rejects
|
|
2226
|
+
# empty URIs. Everything else keeps the expression-keyed
|
|
2227
|
+
# gate.
|
|
2228
|
+
return nil unless (namespaces.is_a?(Hash) &&
|
|
2229
|
+
namespaces["xmlns"].to_s.length.positive? &&
|
|
2230
|
+
xmlns_native?) ||
|
|
2231
|
+
native_expression?(expression)
|
|
2232
|
+
# The leptris engine mishandles namespace-prefixed node
|
|
2233
|
+
# tests: "m:*" matches elements in ANY namespace and
|
|
2234
|
+
# "m:name" matches none, regardless of the binding. Route
|
|
2235
|
+
# namespace-bound queries to the Ruby engine until the
|
|
2236
|
+
# engine grows correct prefix resolution.
|
|
2237
|
+
return nil if namespaces && !namespaces.empty?
|
|
2142
2238
|
# The binding reports the root element parentless while moxml
|
|
2143
2239
|
# roots it at the document, so parent-axis queries from the
|
|
2144
2240
|
# root element keep the Ruby engine.
|
|
@@ -2194,7 +2290,8 @@ module Moxml
|
|
|
2194
2290
|
# and frees in one C dispatch — no NodeSet container, no
|
|
2195
2291
|
# AutoPointer (leptris-ruby TODO.perf/16). The module
|
|
2196
2292
|
# object back means a scalar, whose wrap (and free)
|
|
2197
|
-
# stays with wrap_xpath_first_result.
|
|
2293
|
+
# stays with wrap_xpath_first_result. Identity resolution
|
|
2294
|
+
# happens at the at_xpath boundary (bridge_xpath_node).
|
|
2198
2295
|
return ::Leptris::XML::Searchable.wrap_xpath_first_result(
|
|
2199
2296
|
document, result_ptr
|
|
2200
2297
|
)
|
|
@@ -2273,6 +2370,28 @@ module Moxml
|
|
|
2273
2370
|
# xmlns:name is a nokogiri-compat convention addressing
|
|
2274
2371
|
# elements in the default namespace; only the Ruby engine
|
|
2275
2372
|
# implements it.
|
|
2373
|
+
# One-time capability probe: does the binding's native engine
|
|
2374
|
+
# resolve the reserved xmlns prefix from the namespace hash?
|
|
2375
|
+
# Memoized class-level — the answer never changes for an
|
|
2376
|
+
# install. Only hash-bound xmlns queries may ride native
|
|
2377
|
+
# (the gate below is expression-keyed and cannot see the
|
|
2378
|
+
# call's namespaces); unbound xmlns stays on the Ruby engine,
|
|
2379
|
+
# whose reserved-prefix branch matches default-namespace
|
|
2380
|
+
# elements.
|
|
2381
|
+
def xmlns_native?
|
|
2382
|
+
return @xmlns_native unless @xmlns_native.nil?
|
|
2383
|
+
|
|
2384
|
+
@xmlns_native = begin
|
|
2385
|
+
probe = parse('<r xmlns="urn:p"><a>1</a></r>', {}, nil)
|
|
2386
|
+
raw = to_binding(probe.root.native)
|
|
2387
|
+
res = raw.xpath("./xmlns:a", { "xmlns" => "urn:p" })
|
|
2388
|
+
list = res.respond_to?(:to_a) ? res.to_a : [res].compact
|
|
2389
|
+
list.size == 1 && list.first.text.to_s == "1"
|
|
2390
|
+
rescue StandardError
|
|
2391
|
+
false
|
|
2392
|
+
end
|
|
2393
|
+
end
|
|
2394
|
+
|
|
2276
2395
|
def uses_xmlns_prefix?(ast)
|
|
2277
2396
|
return true if ast.type == :test && ast.value[:namespace] == "xmlns"
|
|
2278
2397
|
|
data/lib/moxml/adapter/libxml.rb
CHANGED
|
@@ -1099,8 +1099,9 @@ module Moxml
|
|
|
1099
1099
|
target_doc = doc || (element.is_a?(::LibXML::XML::Node) ? element.doc : nil)
|
|
1100
1100
|
|
|
1101
1101
|
if target_doc
|
|
1102
|
-
#
|
|
1103
|
-
|
|
1102
|
+
# libxml-ruby 6.0 dropped import's deep flag; the import
|
|
1103
|
+
# walks the subtree either way.
|
|
1104
|
+
imported = target_doc.import(child)
|
|
1104
1105
|
element << imported
|
|
1105
1106
|
else
|
|
1106
1107
|
# No target document - create a deep copy of the node instead
|
|
@@ -318,6 +318,10 @@ module Moxml
|
|
|
318
318
|
node.children
|
|
319
319
|
end
|
|
320
320
|
|
|
321
|
+
def element_children(node)
|
|
322
|
+
node.element_children
|
|
323
|
+
end
|
|
324
|
+
|
|
321
325
|
def adjacent_to_entity_reference?(node)
|
|
322
326
|
node.previous_sibling.is_a?(::Nokogiri::XML::EntityReference) ||
|
|
323
327
|
node.next_sibling.is_a?(::Nokogiri::XML::EntityReference)
|
data/lib/moxml/cdata.rb
CHANGED
data/lib/moxml/comment.rb
CHANGED
data/lib/moxml/context.rb
CHANGED
|
@@ -74,6 +74,18 @@ module Moxml
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# Children-memo generation: bumped by every tree mutation so
|
|
78
|
+
# per-wrapper children memos can self-invalidate even when the
|
|
79
|
+
# same logical node is reachable through multiple wrapper
|
|
80
|
+
# representations (native vs binding duality).
|
|
81
|
+
def children_generation
|
|
82
|
+
@children_generation ||= 0
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def bump_children_generation
|
|
86
|
+
@children_generation = children_generation + 1
|
|
87
|
+
end
|
|
88
|
+
|
|
77
89
|
def namespace_scope_generation
|
|
78
90
|
@namespace_scope_generation
|
|
79
91
|
end
|
data/lib/moxml/document.rb
CHANGED
|
@@ -16,6 +16,38 @@ module Moxml
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def root=(element)
|
|
19
|
+
owner = element.is_a?(Node) && element.document
|
|
20
|
+
# Only a genuinely ATTACHED foreign element adopts: the document
|
|
21
|
+
# face answers arbitrary Document wrappers for FLOATING elements
|
|
22
|
+
# (leptris: a stale mint-time document; ox: an Element), and
|
|
23
|
+
# adopting a floating element splices its (empty) copy as root —
|
|
24
|
+
# the caller keeps appending to the detached original and the
|
|
25
|
+
# tree serializes empty (lutaml-model's builder lost every
|
|
26
|
+
# child this way). Attached means parented, or another
|
|
27
|
+
# document's current root.
|
|
28
|
+
attached = !element.parent.nil? ||
|
|
29
|
+
(owner.is_a?(Document) && owner.root.equal?(element))
|
|
30
|
+
if owner.is_a?(Document) && attached &&
|
|
31
|
+
!owner.equal?(self) && element.context.equal?(context)
|
|
32
|
+
# libleptris refuses a root owned by another document (the
|
|
33
|
+
# engine's set_root ownership check), while Nokogiri adopts.
|
|
34
|
+
# Cross-document CHILD attaches are supported, so build the
|
|
35
|
+
# root here, mirror its declarations and attributes, and
|
|
36
|
+
# splice the foreign subtree child-by-child — each attach
|
|
37
|
+
# rides adapter add_child, which pins the owner document
|
|
38
|
+
# against GC (issue #304).
|
|
39
|
+
new_root = create_element(element.name)
|
|
40
|
+
element.attribute_pairs.each { |k, v| new_root[k] = v }
|
|
41
|
+
element.declared_namespaces.each do |prefix, uri|
|
|
42
|
+
new_root.add_namespace(prefix, uri)
|
|
43
|
+
end
|
|
44
|
+
adapter.set_root(@native, new_root.native)
|
|
45
|
+
new_root.parent_node = self
|
|
46
|
+
element.children.to_a.each { |child| new_root.add_child(child) }
|
|
47
|
+
context.bump_children_generation
|
|
48
|
+
invalidate_children_cache!
|
|
49
|
+
return
|
|
50
|
+
end
|
|
19
51
|
adapter.set_root(@native, element.native)
|
|
20
52
|
element.parent_node = self
|
|
21
53
|
invalidate_children_cache!
|
|
@@ -113,6 +145,14 @@ module Moxml
|
|
|
113
145
|
Wrappers::EntityReference.new(native, context)
|
|
114
146
|
end
|
|
115
147
|
|
|
148
|
+
# Nokogiri-compatible: Document#name returns "document"
|
|
149
|
+
def name
|
|
150
|
+
"document"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Nokogiri-compatible: renaming a document node is a no-op
|
|
154
|
+
def name=(_value); end
|
|
155
|
+
|
|
116
156
|
def add_child(node)
|
|
117
157
|
node = prepare_node(node)
|
|
118
158
|
|
data/lib/moxml/element.rb
CHANGED
|
@@ -159,6 +159,40 @@ module Moxml
|
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
|
|
162
|
+
# Nokogiri-compatible: delete(name) removes the attribute
|
|
163
|
+
def delete(name)
|
|
164
|
+
remove_attribute(name)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Nokogiri-compatible: element-only children. The adapter seam
|
|
168
|
+
# filters at the engine level where the binding carries the face
|
|
169
|
+
# (leptris bulk element children, nokogiri native), so interleaved
|
|
170
|
+
# text/comment nodes never mint wrappers on hot traversals.
|
|
171
|
+
# Memo rides the children generation (#310's mutation clock).
|
|
172
|
+
def elements
|
|
173
|
+
@elements = nil if @elements_gen != context.children_generation
|
|
174
|
+
@elements_gen = context.children_generation
|
|
175
|
+
@elements ||= NodeSet.new(adapter.element_children(@native), context, self)
|
|
176
|
+
end
|
|
177
|
+
alias element_children elements
|
|
178
|
+
|
|
179
|
+
# Nokogiri-compatible: children= replaces the entire child list
|
|
180
|
+
def children=(node_or_nodes)
|
|
181
|
+
natives = case node_or_nodes
|
|
182
|
+
when String
|
|
183
|
+
wrapper = "_moxml_children_#{Process.pid}_#{object_id}"
|
|
184
|
+
context.parse("<#{wrapper}>#{node_or_nodes}</#{wrapper}>")
|
|
185
|
+
.root.children.map(&:native)
|
|
186
|
+
when Moxml::Node then [node_or_nodes.native]
|
|
187
|
+
when Array, NodeSet then node_or_nodes.map(&:native)
|
|
188
|
+
else
|
|
189
|
+
raise ArgumentError, "children= accepts String, Node, or Array"
|
|
190
|
+
end
|
|
191
|
+
adapter.replace_children(@native, natives)
|
|
192
|
+
context.bump_children_generation
|
|
193
|
+
invalidate_children_cache!
|
|
194
|
+
end
|
|
195
|
+
|
|
162
196
|
def remove_attribute(name)
|
|
163
197
|
# Both remove paths end in invalidate_attribute_cache!, which
|
|
164
198
|
# bumps the generation.
|
|
@@ -233,6 +267,19 @@ module Moxml
|
|
|
233
267
|
invalidate_namespace_cache!
|
|
234
268
|
end
|
|
235
269
|
|
|
270
|
+
# Nokogiri-compatible: bind the element to +uri+ as its DEFAULT
|
|
271
|
+
# namespace — the name drops any prefix and the declaration lands
|
|
272
|
+
# on this element. Children keep their own bindings. Skip when an
|
|
273
|
+
# identical default namespace is already in scope: declaring again
|
|
274
|
+
# serializes a duplicate xmlns attribute. (add_namespace is the
|
|
275
|
+
# primitive: namespace=({nil => uri}) declares twice on leptris.)
|
|
276
|
+
def default_namespace=(uri)
|
|
277
|
+
self.name = name.split(":", 2)[-1] if name.to_s.include?(":")
|
|
278
|
+
return if namespace_prefix.to_s.empty? && namespace_uri.to_s == uri.to_s
|
|
279
|
+
|
|
280
|
+
add_namespace(nil, uri.to_s)
|
|
281
|
+
end
|
|
282
|
+
|
|
236
283
|
# All namespaces IN SCOPE for this element — its own
|
|
237
284
|
# declarations plus everything inherited from ancestors —
|
|
238
285
|
# matching the Nokogiri #namespaces contract consumers port
|
|
@@ -296,6 +343,8 @@ module Moxml
|
|
|
296
343
|
invalidate_children_cache!
|
|
297
344
|
end
|
|
298
345
|
|
|
346
|
+
alias content= text=
|
|
347
|
+
|
|
299
348
|
def inner_text
|
|
300
349
|
text = raw_inner_text
|
|
301
350
|
entity_bearing? ? adapter.restore_entities(text) : text
|