moxml 0.5.35 → 0.5.36
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/docs/_pages/performance.adoc +24 -0
- data/lib/moxml/adapter/base.rb +8 -1
- data/lib/moxml/adapter/leptris/markers.rb +10 -1
- data/lib/moxml/adapter/leptris/serialize.rb +1 -0
- data/lib/moxml/adapter/leptris.rb +263 -3
- data/lib/moxml/adapter/libxml.rb +1 -1
- data/lib/moxml/adapter/nokogiri.rb +1 -1
- data/lib/moxml/adapter/oga.rb +1 -1
- data/lib/moxml/adapter/ox.rb +1 -1
- data/lib/moxml/adapter/rexml.rb +1 -1
- data/lib/moxml/c14n.rb +1 -1
- data/lib/moxml/node.rb +25 -4
- data/lib/moxml/version.rb +1 -1
- data/spec/integration/shared_examples/node_wrappers/node_set_behavior.rb +1 -1
- data/spec/moxml/adapter/shared_examples/adapter_contract.rb +4 -6
- 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: 9d5b785cac911c3289013e8ad7f3a41a0608498b6389098a80b58a6693f1a063
|
|
4
|
+
data.tar.gz: 73f7938818ebcc02059f1396d776e9db477f924ceefcce3680b14a2c3c26aca7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 00ac1feeeb5e1f4e407d4ccdb3afb29580f85300c1daca678de547079db791488d7d792d15b95381ae3488fe6e1f9e73f782376af6a09078fd4101f523218e93
|
|
7
|
+
data.tar.gz: be6be6adfe05ad94ecc36b1dfea8f295683478243d46301f261a6a9caecb09ac59b836342f79e124f75855c4864757a341687ef51377c9aaa252d7316fb5f141
|
|
@@ -253,6 +253,30 @@ read cache materializes lazily). The remaining cold-walk gap is
|
|
|
253
253
|
binding-side (its per-node Ruby wrapper construction) — tracked
|
|
254
254
|
upstream as the TypedData work.
|
|
255
255
|
|
|
256
|
+
=== Native read layer (leptris >= 1.9.162.6)
|
|
257
|
+
|
|
258
|
+
The leptris adapter adopts the binding's opt-in native read layer
|
|
259
|
+
(leptris-ruby#185): TypedData node wrappers with C-bound hot reads
|
|
260
|
+
and bulk children construction — one wrapper-cache round-trip for a
|
|
261
|
+
whole child list, zero per-node FFI. Element natives minted from
|
|
262
|
+
`#root` and `children` are native-layer nodes; the C tree is shared
|
|
263
|
+
with the binding, so writes made through the bridged binding nodes
|
|
264
|
+
(a `to_binding` wrap over the same pointer) are visible to native
|
|
265
|
+
reads. Engines without the layer, or ruby-platform installs without
|
|
266
|
+
the compiled bundle, stay on the binding path unchanged.
|
|
267
|
+
|
|
268
|
+
| path | binding path | native layer |
|
|
269
|
+
| Consumer pipeline (parse + typed walk) | 6112µs (0.50x nokogiri) | **3380µs (1.00-1.04x)** |
|
|
270
|
+
| Pipeline allocations | 13564 | **9813 (0.69x nokogiri)** |
|
|
271
|
+
| Cold walk (parse + mint) | 738µs | **509µs** |
|
|
272
|
+
| Raw walk (binding vs native, upstream's fixture) | 64.3µs/doc | 22.8µs/doc |
|
|
273
|
+
|
|
274
|
+
Entity-marker documents fall back to the binding children path (the
|
|
275
|
+
marker split materializes TextSegments); the wrapper's
|
|
276
|
+
`entity_bearing?` memo decides, so the check costs no per-call
|
|
277
|
+
climb. `Node#==` and the adapter protocol's `same_node?` compare
|
|
278
|
+
C-node addresses — two wrapper classes over one node compare equal.
|
|
279
|
+
|
|
256
280
|
=== Prefer bulk paths for per-node conversion
|
|
257
281
|
|
|
258
282
|
Per-node Ruby iteration pays the wrapper tax per element. When the
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -116,6 +116,13 @@ module Moxml
|
|
|
116
116
|
)
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
# Protocol-level native equality; engines with more than one
|
|
120
|
+
# wrapper class over one C node override (leptris native
|
|
121
|
+
# read layer). Shared adapter examples compare through this.
|
|
122
|
+
def same_node?(one, other)
|
|
123
|
+
one == other
|
|
124
|
+
end
|
|
125
|
+
|
|
119
126
|
# Backends without a native subtree digest answer nil —
|
|
120
127
|
# the wrapper contract Node#digest gates on (issue #173).
|
|
121
128
|
def digest(*)
|
|
@@ -272,7 +279,7 @@ namespace_validation_mode: :strict)
|
|
|
272
279
|
# Marker-tracking adapters override this so the post-serialize
|
|
273
280
|
# restore can skip its full-output scans on marker-free
|
|
274
281
|
# documents; the default stays conservative.
|
|
275
|
-
def entity_bearing?(_native)
|
|
282
|
+
def entity_bearing?(_native, _doc = nil)
|
|
276
283
|
true
|
|
277
284
|
end
|
|
278
285
|
|
|
@@ -8,7 +8,16 @@ module Moxml
|
|
|
8
8
|
# the ER builder path flips it, and the split/restore scans
|
|
9
9
|
# consult it. Customized natives only exist as split products
|
|
10
10
|
# of marker-bearing text, so they always report true.
|
|
11
|
-
|
|
11
|
+
# doc is the binding document when the wrapper could resolve
|
|
12
|
+
# it through its parent chain; the C climb is the fallback.
|
|
13
|
+
def entity_bearing?(native, doc = nil)
|
|
14
|
+
if NATIVE_READ_LAYER && native.is_a?(::Leptris::XML::NativeNode)
|
|
15
|
+
doc ||= doc_for(native)
|
|
16
|
+
return false if doc.nil?
|
|
17
|
+
|
|
18
|
+
return attachments.get(doc, :entity_markers) != false
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
case native
|
|
13
22
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
14
23
|
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment,
|
|
@@ -30,6 +30,7 @@ module Moxml
|
|
|
30
30
|
EMPTY_ELEMENT_RE = %r{<([A-Za-z_][\w.:-]*+)((?:"[^"]*+"|'[^']*+'|[^<>"'/]++)*+)/>}
|
|
31
31
|
|
|
32
32
|
def serialize(node, options = {})
|
|
33
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
33
34
|
# Entity restoration belongs to the wrapper layer
|
|
34
35
|
# (Node#to_xml runs adapter.restore_entities for every
|
|
35
36
|
# adapter); doing it here scanned the output a second time.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
3
5
|
return if RUBY_ENGINE == "opal"
|
|
4
6
|
|
|
5
7
|
require "stringio"
|
|
@@ -40,6 +42,87 @@ module Moxml
|
|
|
40
42
|
DIGEST_SUPPORTED =
|
|
41
43
|
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.99")
|
|
42
44
|
|
|
45
|
+
# Opt-in native read layer (leptris-ruby#185, bindings >= 1.9.162.6):
|
|
46
|
+
# TypedData node wrappers with C-bound hot reads and bulk
|
|
47
|
+
# children construction. Minted from #root downward; the C
|
|
48
|
+
# tree is shared with the binding, so writes made through the
|
|
49
|
+
# bridged binding nodes are visible to native reads. Installs
|
|
50
|
+
# without the compiled bundle (ruby-platform gem) simply stay
|
|
51
|
+
# on the binding path.
|
|
52
|
+
begin
|
|
53
|
+
require "leptris/xml/native_layer"
|
|
54
|
+
rescue LoadError, StandardError
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
NATIVE_READ_LAYER =
|
|
58
|
+
defined?(::Leptris::XML::NativeNode) &&
|
|
59
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.162.6")
|
|
60
|
+
|
|
61
|
+
# The binding's scratch-buffer growth truncates bulk children
|
|
62
|
+
# at 512 on 1.9.163.x (leptris-ruby#202) — the engine copies
|
|
63
|
+
# any buffer size correctly and its count query is exact, so
|
|
64
|
+
# moxml fetches with its own grow-to-fit scratch (correct on
|
|
65
|
+
# every binding version; the binding's own Node#children is
|
|
66
|
+
# not trusted for large lists).
|
|
67
|
+
|
|
68
|
+
if NATIVE_READ_LAYER
|
|
69
|
+
# root NativeNode -> binding document (recorded at #root mint;
|
|
70
|
+
# NativeNode exposes no document accessor).
|
|
71
|
+
@native_doc_roots = ObjectSpace::WeakMap.new
|
|
72
|
+
|
|
73
|
+
class << self
|
|
74
|
+
def record_native_doc(root_native, doc)
|
|
75
|
+
@native_doc_roots[root_native] = doc
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Protocol-level node equality: one engine can hand out
|
|
79
|
+
# two wrapper classes over the same C node (native layer +
|
|
80
|
+
# binding), so raw == is not identity across the seam.
|
|
81
|
+
def same_node?(one, other)
|
|
82
|
+
return true if one.equal?(other)
|
|
83
|
+
|
|
84
|
+
if NATIVE_READ_LAYER
|
|
85
|
+
native_one = one.is_a?(::Leptris::XML::NativeNode)
|
|
86
|
+
native_other = other.is_a?(::Leptris::XML::NativeNode)
|
|
87
|
+
if native_one || native_other
|
|
88
|
+
address_one = native_one ? one.address : one.c_ptr.address
|
|
89
|
+
address_other = native_other ? other.address : other.c_ptr.address
|
|
90
|
+
return address_one == address_other
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
one == other
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Binding node for any native: identity for binding nodes,
|
|
98
|
+
# a Node.wrap over the shared C pointer for NativeNodes.
|
|
99
|
+
def to_binding(node)
|
|
100
|
+
return node unless NATIVE_READ_LAYER &&
|
|
101
|
+
node.is_a?(::Leptris::XML::NativeNode)
|
|
102
|
+
|
|
103
|
+
doc = doc_for(node)
|
|
104
|
+
ptr = ::FFI::Pointer.new(node.address)
|
|
105
|
+
return ::Leptris::XML::Node.wrap(ptr, doc) if doc
|
|
106
|
+
|
|
107
|
+
::Leptris::XML::Node.wrap(ptr, nil)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Binding document for a NativeNode subtree: climb parents
|
|
111
|
+
# to the root (C-bound reads) and look the doc up in the
|
|
112
|
+
# root registry. Detached subtrees answer nil.
|
|
113
|
+
def doc_for(node)
|
|
114
|
+
current = node
|
|
115
|
+
current = current.parent while current&.parent
|
|
116
|
+
@native_doc_roots[current]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Binding node for any native: identity for binding nodes, a
|
|
122
|
+
# Node.wrap over the shared C pointer for NativeNodes (used by
|
|
123
|
+
# the write/serialize/namespace paths the native layer does
|
|
124
|
+
# not carry).
|
|
125
|
+
|
|
43
126
|
# Native C14N delegation probe: the engine's C14N must
|
|
44
127
|
# byte-match the Ruby reference (ported from canon) on a
|
|
45
128
|
# namespace-sorting + attributes + comments shape before
|
|
@@ -191,7 +274,14 @@ module Moxml
|
|
|
191
274
|
# document-plus-attachment probe here cost more than the
|
|
192
275
|
# read itself).
|
|
193
276
|
def bare_attr_value(element, name)
|
|
194
|
-
element[name.to_s]
|
|
277
|
+
value = element[name.to_s]
|
|
278
|
+
if NATIVE_READ_LAYER &&
|
|
279
|
+
element.is_a?(::Leptris::XML::NativeNode) &&
|
|
280
|
+
value.is_a?(String)
|
|
281
|
+
return value.dup.force_encoding(Encoding::UTF_8)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
value
|
|
195
285
|
end
|
|
196
286
|
|
|
197
287
|
def native_identity_stable?
|
|
@@ -428,10 +518,12 @@ module Moxml
|
|
|
428
518
|
end
|
|
429
519
|
|
|
430
520
|
def create_native_namespace(element, prefix, uri)
|
|
521
|
+
element = to_binding(element) if NATIVE_READ_LAYER
|
|
431
522
|
element.add_namespace_definition(prefix, uri)
|
|
432
523
|
end
|
|
433
524
|
|
|
434
525
|
def set_namespace(node, namespace)
|
|
526
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
435
527
|
return set_attribute_namespace(node, namespace) if node.is_a?(::Leptris::XML::Attr)
|
|
436
528
|
|
|
437
529
|
element = node
|
|
@@ -498,6 +590,8 @@ module Moxml
|
|
|
498
590
|
end
|
|
499
591
|
|
|
500
592
|
def namespace(node)
|
|
593
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
594
|
+
|
|
501
595
|
# The binding resolves Attr#namespace to the declaration but
|
|
502
596
|
# without the prefix; when the qualified name carries one,
|
|
503
597
|
# prefer the in-scope declaration that binds it so wrappers
|
|
@@ -549,6 +643,16 @@ module Moxml
|
|
|
549
643
|
end
|
|
550
644
|
|
|
551
645
|
def node_type(node)
|
|
646
|
+
if NATIVE_READ_LAYER &&
|
|
647
|
+
node.is_a?(::Leptris::XML::NativeNode)
|
|
648
|
+
type = node.node_type
|
|
649
|
+
# The native layer spells PIs :pi; the wrapper contract
|
|
650
|
+
# (node_type_map) says :processing_instruction.
|
|
651
|
+
return :processing_instruction if type == :pi
|
|
652
|
+
|
|
653
|
+
return type
|
|
654
|
+
end
|
|
655
|
+
|
|
552
656
|
# Frequency-ordered: elements and text dominate every real
|
|
553
657
|
# document, and Node.wrap dispatches here once per cold
|
|
554
658
|
# wrap. CDATA must precede Text (CDATA < Text in the
|
|
@@ -574,6 +678,9 @@ module Moxml
|
|
|
574
678
|
# lightweight Attr triples answer nil.
|
|
575
679
|
def digest(node, drop_ws_text: false)
|
|
576
680
|
return nil unless DIGEST_SUPPORTED
|
|
681
|
+
if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
|
|
682
|
+
return to_binding(node).digest(drop_ws: drop_ws_text)
|
|
683
|
+
end
|
|
577
684
|
return nil unless node.is_a?(::Leptris::XML::Node) &&
|
|
578
685
|
!node.is_a?(::Leptris::XML::ResultAttr)
|
|
579
686
|
|
|
@@ -584,10 +691,19 @@ module Moxml
|
|
|
584
691
|
return node.root_name if node.is_a?(::Leptris::XML::DocType)
|
|
585
692
|
return node.target if node.is_a?(CustomizedLeptris::DocumentPI)
|
|
586
693
|
|
|
694
|
+
if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
|
|
695
|
+
# Native strings arrive ASCII-8BIT; PIs expose no name
|
|
696
|
+
# through the native layer.
|
|
697
|
+
return to_binding(node).target.to_s if node.node_type == :pi
|
|
698
|
+
|
|
699
|
+
return node.name.dup.force_encoding(Encoding::UTF_8)
|
|
700
|
+
end
|
|
701
|
+
|
|
587
702
|
node.name.to_s.dup.force_encoding("UTF-8")
|
|
588
703
|
end
|
|
589
704
|
|
|
590
705
|
def set_node_name(node, name)
|
|
706
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
591
707
|
case node
|
|
592
708
|
when ::Leptris::XML::ProcessingInstruction, CustomizedLeptris::DocumentPI then node.target = name
|
|
593
709
|
else node.name = name
|
|
@@ -595,6 +711,7 @@ module Moxml
|
|
|
595
711
|
end
|
|
596
712
|
|
|
597
713
|
def duplicate_node(node)
|
|
714
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
598
715
|
case node
|
|
599
716
|
when CustomizedLeptris::Declaration
|
|
600
717
|
CustomizedLeptris::Declaration.new(node.version, node.encoding, node.standalone)
|
|
@@ -609,12 +726,38 @@ module Moxml
|
|
|
609
726
|
end
|
|
610
727
|
end
|
|
611
728
|
|
|
612
|
-
def children(node)
|
|
729
|
+
def children(node, entity_bearing: false)
|
|
730
|
+
if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
|
|
731
|
+
# Bulk C children through moxml's own scratch (see
|
|
732
|
+
# NATIVE_READ_LAYER note above). Entity-marker documents
|
|
733
|
+
# bridge each child for the marker split — the
|
|
734
|
+
# TextSegment reconstruction reads binding text nodes;
|
|
735
|
+
# the wrapper's memo supplies the flag, so deriving it
|
|
736
|
+
# here costs no C parent climb per call.
|
|
737
|
+
natives = bulk_native_children(node)
|
|
738
|
+
return natives unless entity_bearing
|
|
739
|
+
|
|
740
|
+
# Entity-marker documents: bridge for the marker split
|
|
741
|
+
# (TextSegment reconstruction reads binding text nodes).
|
|
742
|
+
doc = doc_for(node)
|
|
743
|
+
bound_children = natives.map { |child| to_binding(child) }
|
|
744
|
+
if doc && attachments.get(doc, :entity_markers) == false
|
|
745
|
+
return bound_children
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
return split_entity_markers(bound_children, to_binding(node))
|
|
749
|
+
end
|
|
613
750
|
# Frequency-ordered: elements dominate every walk and paid
|
|
614
751
|
# ten failed compares to reach the else arm.
|
|
615
752
|
case node
|
|
616
753
|
when ::Leptris::XML::Element
|
|
617
754
|
natives = node.children.to_a
|
|
755
|
+
if NATIVE_READ_LAYER && natives.size == 512
|
|
756
|
+
# Binding scratch truncation (leptris-ruby#202): a
|
|
757
|
+
# full batch is ambiguous — refetch through moxml's
|
|
758
|
+
# own buffers to be sure.
|
|
759
|
+
natives = bulk_binding_children(node)
|
|
760
|
+
end
|
|
618
761
|
# Parse records whether the preprocessed source held any
|
|
619
762
|
# entity markers, and the ER builder path flips the flag
|
|
620
763
|
# when it mints one. A false flag lets traversal skip the
|
|
@@ -643,6 +786,16 @@ module Moxml
|
|
|
643
786
|
end
|
|
644
787
|
|
|
645
788
|
def parent(node)
|
|
789
|
+
if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
|
|
790
|
+
parent = node.parent
|
|
791
|
+
return parent if parent
|
|
792
|
+
|
|
793
|
+
doc = doc_for(node)
|
|
794
|
+
return doc if doc&.root && doc.root.c_ptr.address == node.address
|
|
795
|
+
|
|
796
|
+
return nil
|
|
797
|
+
end
|
|
798
|
+
|
|
646
799
|
# Frequency-ordered: elements dominate navigation and paid
|
|
647
800
|
# three failed compares to reach the else arm.
|
|
648
801
|
case node
|
|
@@ -657,6 +810,66 @@ module Moxml
|
|
|
657
810
|
end
|
|
658
811
|
end
|
|
659
812
|
|
|
813
|
+
# The binding-node twin of bulk_native_children (identity via
|
|
814
|
+
# the binding's wrap cache).
|
|
815
|
+
def bulk_binding_children(node)
|
|
816
|
+
copied, pointers = bulk_child_buffers(node.c_ptr)
|
|
817
|
+
return node.children.to_a if copied.zero?
|
|
818
|
+
|
|
819
|
+
doc = node.document
|
|
820
|
+
Array.new(copied) do |i|
|
|
821
|
+
::Leptris::XML::Node.wrap(pointers.get_pointer(i * 8), doc)
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
# Bulk child fetch with moxml's own grow-to-fit thread-local
|
|
826
|
+
# scratch: count first (exact), size the buffers to it, wrap
|
|
827
|
+
# each pointer as a native-layer node. Correct on every
|
|
828
|
+
# binding version — the binding's own scratch truncates at
|
|
829
|
+
# 512 on 1.9.163.x (leptris-ruby#202).
|
|
830
|
+
def bulk_native_children(node)
|
|
831
|
+
copied, pointers = bulk_child_buffers(::FFI::Pointer.new(node.address))
|
|
832
|
+
return [] if copied.zero?
|
|
833
|
+
|
|
834
|
+
doc = doc_for(node)
|
|
835
|
+
return to_binding(node).children.to_a if doc.nil?
|
|
836
|
+
|
|
837
|
+
# Share the layer's per-document identity cache (keyed by
|
|
838
|
+
# node address): NativeNode.from alone mints fresh objects
|
|
839
|
+
# per call on 1.9.163.x, which would fork moxml wrappers.
|
|
840
|
+
cache = doc.native_cache
|
|
841
|
+
Array.new(copied) do |i|
|
|
842
|
+
child_ptr = pointers.get_pointer(i * 8)
|
|
843
|
+
cache[child_ptr.address] ||=
|
|
844
|
+
::Leptris::XML::NativeNode.from(doc, child_ptr)
|
|
845
|
+
end
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
# Shared count-then-copy fetch over moxml's grow-to-fit
|
|
849
|
+
# thread-local scratch — the layer above the binding's
|
|
850
|
+
# (buggy on 1.9.163.x) own buffers. Returns [copied, pointers].
|
|
851
|
+
def bulk_child_buffers(ptr)
|
|
852
|
+
total = ::Leptris::XML::FFI.leptris_node_children_ex(ptr, nil, nil, 0)
|
|
853
|
+
return [0, nil] if total.zero?
|
|
854
|
+
|
|
855
|
+
scratch = (Thread.current[:moxml_leptris_children] ||= {})
|
|
856
|
+
pointers = scratch[:pointers]
|
|
857
|
+
if pointers.nil? || pointers.size / 8 < total
|
|
858
|
+
pointers&.free
|
|
859
|
+
pointers = scratch[:pointers] =
|
|
860
|
+
::FFI::MemoryPointer.new(:pointer, total)
|
|
861
|
+
end
|
|
862
|
+
kinds = scratch[:kinds]
|
|
863
|
+
if kinds.nil? || kinds.size / 4 < total
|
|
864
|
+
kinds&.free
|
|
865
|
+
kinds = scratch[:kinds] = ::FFI::MemoryPointer.new(:int, total)
|
|
866
|
+
end
|
|
867
|
+
copied = ::Leptris::XML::FFI.leptris_node_children_ex(
|
|
868
|
+
ptr, pointers, kinds, total
|
|
869
|
+
)
|
|
870
|
+
[copied, pointers]
|
|
871
|
+
end
|
|
872
|
+
|
|
660
873
|
# The binding reports the root element as parentless; the
|
|
661
874
|
# moxml contract roots at the document.
|
|
662
875
|
def root_parent(node)
|
|
@@ -664,14 +877,25 @@ module Moxml
|
|
|
664
877
|
end
|
|
665
878
|
|
|
666
879
|
def next_sibling(node)
|
|
880
|
+
return node.next_sibling if NATIVE_READ_LAYER &&
|
|
881
|
+
node.is_a?(::Leptris::XML::NativeNode)
|
|
882
|
+
|
|
667
883
|
node.next_sibling if node.is_a?(::Leptris::XML::Node)
|
|
668
884
|
end
|
|
669
885
|
|
|
670
886
|
def previous_sibling(node)
|
|
887
|
+
# Bridged, not the native sibling walk: the native layer
|
|
888
|
+
# exposes next_sibling only, and its children batch
|
|
889
|
+
# truncates at 512 — the predecessor beyond that would be
|
|
890
|
+
# wrong.
|
|
891
|
+
node = to_binding(node)
|
|
671
892
|
node.previous_sibling if node.is_a?(::Leptris::XML::Node)
|
|
672
893
|
end
|
|
673
894
|
|
|
674
895
|
def document(node)
|
|
896
|
+
return doc_for(node) if NATIVE_READ_LAYER &&
|
|
897
|
+
node.is_a?(::Leptris::XML::NativeNode)
|
|
898
|
+
|
|
675
899
|
case node
|
|
676
900
|
when ::Leptris::XML::Document then node
|
|
677
901
|
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype then node.parent_doc
|
|
@@ -680,7 +904,15 @@ module Moxml
|
|
|
680
904
|
end
|
|
681
905
|
|
|
682
906
|
def root(document)
|
|
683
|
-
document.root
|
|
907
|
+
r = document.root
|
|
908
|
+
return nil if r.nil?
|
|
909
|
+
|
|
910
|
+
if NATIVE_READ_LAYER
|
|
911
|
+
native = document.native_node
|
|
912
|
+
record_native_doc(native, document)
|
|
913
|
+
return native
|
|
914
|
+
end
|
|
915
|
+
r
|
|
684
916
|
end
|
|
685
917
|
|
|
686
918
|
def line_number(node)
|
|
@@ -691,6 +923,7 @@ module Moxml
|
|
|
691
923
|
end
|
|
692
924
|
|
|
693
925
|
def attributes(element)
|
|
926
|
+
element = to_binding(element)
|
|
694
927
|
element.attribute_nodes
|
|
695
928
|
end
|
|
696
929
|
|
|
@@ -707,6 +940,7 @@ module Moxml
|
|
|
707
940
|
end
|
|
708
941
|
|
|
709
942
|
def set_attribute(element, name, value)
|
|
943
|
+
element = to_binding(element) if NATIVE_READ_LAYER
|
|
710
944
|
element[name.to_s] = value.to_s
|
|
711
945
|
end
|
|
712
946
|
|
|
@@ -726,14 +960,17 @@ module Moxml
|
|
|
726
960
|
end
|
|
727
961
|
|
|
728
962
|
def get_attribute(element, name)
|
|
963
|
+
element = to_binding(element)
|
|
729
964
|
element.attribute_nodes.find { |attr| attr.name == name.to_s }
|
|
730
965
|
end
|
|
731
966
|
|
|
732
967
|
def get_attribute_value(element, name)
|
|
968
|
+
element = to_binding(element)
|
|
733
969
|
element[name.to_s]
|
|
734
970
|
end
|
|
735
971
|
|
|
736
972
|
def remove_attribute(element, name)
|
|
973
|
+
element = to_binding(element)
|
|
737
974
|
element.remove_attribute(name.to_s)
|
|
738
975
|
end
|
|
739
976
|
|
|
@@ -743,6 +980,10 @@ module Moxml
|
|
|
743
980
|
end
|
|
744
981
|
|
|
745
982
|
def add_child(parent, child)
|
|
983
|
+
if NATIVE_READ_LAYER
|
|
984
|
+
parent = to_binding(parent)
|
|
985
|
+
child = to_binding(child)
|
|
986
|
+
end
|
|
746
987
|
case parent
|
|
747
988
|
when ::Leptris::XML::Document then add_document_child(parent, child)
|
|
748
989
|
else
|
|
@@ -759,6 +1000,7 @@ module Moxml
|
|
|
759
1000
|
end
|
|
760
1001
|
|
|
761
1002
|
def add_previous_sibling(node, new_node)
|
|
1003
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
762
1004
|
# A PI inserted before the root lives at document level in
|
|
763
1005
|
# libleptris's model, not in the element tree.
|
|
764
1006
|
if new_node.is_a?(::Leptris::XML::ProcessingInstruction) &&
|
|
@@ -770,10 +1012,12 @@ module Moxml
|
|
|
770
1012
|
end
|
|
771
1013
|
|
|
772
1014
|
def add_next_sibling(node, new_node)
|
|
1015
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
773
1016
|
node.add_next_sibling(new_node)
|
|
774
1017
|
end
|
|
775
1018
|
|
|
776
1019
|
def remove(node)
|
|
1020
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
777
1021
|
case node
|
|
778
1022
|
when CustomizedLeptris::Declaration
|
|
779
1023
|
remove_declaration(node.parent_doc) if node.parent_doc
|
|
@@ -793,6 +1037,10 @@ module Moxml
|
|
|
793
1037
|
end
|
|
794
1038
|
|
|
795
1039
|
def replace(node, new_node)
|
|
1040
|
+
if NATIVE_READ_LAYER
|
|
1041
|
+
node = to_binding(node)
|
|
1042
|
+
new_node = to_binding(new_node)
|
|
1043
|
+
end
|
|
796
1044
|
return node.replace(new_node) if node.is_a?(::Leptris::XML::Element)
|
|
797
1045
|
|
|
798
1046
|
# libleptris only offers element-anchored insertion, so a
|
|
@@ -817,10 +1065,18 @@ module Moxml
|
|
|
817
1065
|
end
|
|
818
1066
|
|
|
819
1067
|
def replace_children(node, new_children)
|
|
1068
|
+
if NATIVE_READ_LAYER
|
|
1069
|
+
node = to_binding(node)
|
|
1070
|
+
new_children = new_children.map { |child| to_binding(child) }
|
|
1071
|
+
end
|
|
820
1072
|
node.children = new_children
|
|
821
1073
|
end
|
|
822
1074
|
|
|
823
1075
|
def text_content(node)
|
|
1076
|
+
if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
|
|
1077
|
+
return node.content.dup.force_encoding(Encoding::UTF_8)
|
|
1078
|
+
end
|
|
1079
|
+
|
|
824
1080
|
# Frequency-ordered: elements dominate reads; they paid
|
|
825
1081
|
# three failed compares to reach the else arm. The
|
|
826
1082
|
# duplicated branch bodies are the point.
|
|
@@ -837,6 +1093,7 @@ module Moxml
|
|
|
837
1093
|
end
|
|
838
1094
|
|
|
839
1095
|
def inner_text(node)
|
|
1096
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
840
1097
|
# moxml semantic: direct text children only — no descendant
|
|
841
1098
|
# text (that is #text), no comments. Entity references
|
|
842
1099
|
# contribute their serialized form.
|
|
@@ -851,6 +1108,7 @@ module Moxml
|
|
|
851
1108
|
end
|
|
852
1109
|
|
|
853
1110
|
def set_text_content(node, content)
|
|
1111
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
854
1112
|
case node
|
|
855
1113
|
when ::Leptris::XML::Document
|
|
856
1114
|
node.root&.content = content.to_s
|
|
@@ -899,6 +1157,7 @@ module Moxml
|
|
|
899
1157
|
end
|
|
900
1158
|
|
|
901
1159
|
def namespace_definitions(element)
|
|
1160
|
+
element = to_binding(element) if NATIVE_READ_LAYER
|
|
902
1161
|
element.namespace_definitions
|
|
903
1162
|
end
|
|
904
1163
|
|
|
@@ -929,6 +1188,7 @@ module Moxml
|
|
|
929
1188
|
NATIVE_GATE_CACHE = XPath::Cache.new(100)
|
|
930
1189
|
|
|
931
1190
|
def xpath(node, expression, namespaces = {})
|
|
1191
|
+
node = to_binding(node) if NATIVE_READ_LAYER
|
|
932
1192
|
native = native_xpath(node, expression, namespaces)
|
|
933
1193
|
return native unless native.nil?
|
|
934
1194
|
|
data/lib/moxml/adapter/libxml.rb
CHANGED
data/lib/moxml/adapter/oga.rb
CHANGED
data/lib/moxml/adapter/ox.rb
CHANGED
data/lib/moxml/adapter/rexml.rb
CHANGED
data/lib/moxml/c14n.rb
CHANGED
|
@@ -66,7 +66,7 @@ module Moxml
|
|
|
66
66
|
return nil unless adapter == Moxml::Adapter::Leptris &&
|
|
67
67
|
Moxml::Adapter::Leptris.native_c14n_byte_safe?
|
|
68
68
|
|
|
69
|
-
node_or_xml.native.canonicalize(
|
|
69
|
+
Moxml::Adapter::Leptris.to_binding(node_or_xml.native).canonicalize(
|
|
70
70
|
::Leptris::XML::FFI::C14N_1_0, nil,
|
|
71
71
|
mode: ::Leptris::XML::FFI::C14N_MODE_CANONICAL
|
|
72
72
|
)
|
data/lib/moxml/node.rb
CHANGED
|
@@ -54,7 +54,10 @@ module Moxml
|
|
|
54
54
|
|
|
55
55
|
def children
|
|
56
56
|
@children ||= begin
|
|
57
|
-
|
|
57
|
+
# The wrapper's entity memo decides the marker split; the
|
|
58
|
+
# adapter would otherwise re-derive it per call (a C parent
|
|
59
|
+
# climb on the native layer).
|
|
60
|
+
natives = adapter.children(@native, entity_bearing: entity_bearing?)
|
|
58
61
|
natives = natives.map { adapter.patch_node(_1, @native) } if adapter.patches_children?
|
|
59
62
|
NodeSet.new(natives, context, self)
|
|
60
63
|
end
|
|
@@ -150,10 +153,24 @@ module Moxml
|
|
|
150
153
|
@entity_bearing
|
|
151
154
|
else
|
|
152
155
|
@entity_bearing_gen = gen
|
|
153
|
-
@entity_bearing = adapter.entity_bearing?(@native)
|
|
156
|
+
@entity_bearing = adapter.entity_bearing?(@native, document_native_for_markers)
|
|
154
157
|
end
|
|
155
158
|
end
|
|
156
159
|
|
|
160
|
+
# Binding document through the wrapper parent chain — pure Ruby
|
|
161
|
+
# attr reads; the native-layer fallback climbs C parents per
|
|
162
|
+
# call (the doc_for walk was the largest wrapper-layer cost on
|
|
163
|
+
# the consumer pipeline after the native adoption).
|
|
164
|
+
def document_native_for_markers
|
|
165
|
+
node = self
|
|
166
|
+
while node
|
|
167
|
+
return node.native if node.document?
|
|
168
|
+
|
|
169
|
+
node = node.parent_node
|
|
170
|
+
end
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
157
174
|
def xpath(expression, namespaces = {})
|
|
158
175
|
result = adapter.xpath(@native, expression, namespaces)
|
|
159
176
|
# Adapter contract: Array<native> | LazyNodeSet | scalar.
|
|
@@ -384,7 +401,11 @@ module Moxml
|
|
|
384
401
|
end
|
|
385
402
|
|
|
386
403
|
def ==(other)
|
|
387
|
-
|
|
404
|
+
# Native equality goes through the adapter: engines with a
|
|
405
|
+
# native read layer hand out two wrapper classes over one C
|
|
406
|
+
# node, and raw native == is false across that seam.
|
|
407
|
+
self.class == other.class &&
|
|
408
|
+
adapter.same_node?(@native, other.native)
|
|
388
409
|
end
|
|
389
410
|
|
|
390
411
|
TYPES.each do |node_type|
|
|
@@ -447,7 +468,7 @@ module Moxml
|
|
|
447
468
|
# Internal: Set the parent node for cache invalidation tracking.
|
|
448
469
|
# Called by NodeSet, Document, Element when establishing parent-child
|
|
449
470
|
# relationships. Public to allow cross-class usage within Moxml internals.
|
|
450
|
-
|
|
471
|
+
attr_accessor :parent_node
|
|
451
472
|
|
|
452
473
|
def adapter
|
|
453
474
|
# A context's adapter object is fixed for its lifetime; the
|
data/lib/moxml/version.rb
CHANGED
|
@@ -43,7 +43,7 @@ RSpec.shared_examples "Moxml::NodeSet" do
|
|
|
43
43
|
it "compares nodes" do
|
|
44
44
|
xpath_results = doc.xpath("//child")
|
|
45
45
|
element_children = doc.root.children.grep(Moxml::Element)
|
|
46
|
-
expect(xpath_results.
|
|
46
|
+
expect(xpath_results.to_a).to eq(element_children)
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -79,22 +79,20 @@ RSpec.shared_examples "xml adapter" do
|
|
|
79
79
|
first = children[0]
|
|
80
80
|
second = children[1]
|
|
81
81
|
|
|
82
|
-
expect(described_class.next_sibling(first)).to
|
|
83
|
-
expect(described_class.previous_sibling(second)).to
|
|
82
|
+
expect(described_class.same_node?(described_class.next_sibling(first), second)).to be(true)
|
|
83
|
+
expect(described_class.same_node?(described_class.previous_sibling(second), first)).to be(true)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
it "adds child" do
|
|
87
87
|
element = described_class.create_element("new")
|
|
88
88
|
described_class.add_child(root, element)
|
|
89
|
-
expect(described_class.children(root).last).to
|
|
89
|
+
expect(described_class.same_node?(described_class.children(root).last, element)).to be(true)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
it "adds text child" do
|
|
93
93
|
described_class.add_child(root, "text")
|
|
94
|
-
# a workaround for Rexml until we convert tests to work with Moxml wrappers
|
|
95
94
|
last_child = described_class.children(root).last
|
|
96
|
-
|
|
97
|
-
expect(last_text).to eq("text")
|
|
95
|
+
expect(described_class.text_content(last_child)).to eq("text")
|
|
98
96
|
end
|
|
99
97
|
end
|
|
100
98
|
|