leptris 1.9.197.2-arm-linux
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 +7 -0
- data/CHANGELOG.md +3890 -0
- data/CLAUDE.md +115 -0
- data/CONTEXT.md +39 -0
- data/LICENSE.md +33 -0
- data/README.adoc +755 -0
- data/Rakefile +407 -0
- data/TODO.perf/01-ext-accelerated-default-reads.md +28 -0
- data/TODO.perf/02-auto-enable-native-layer.md +19 -0
- data/TODO.perf/03-bulk-xpath-result-materialization.md +18 -0
- data/TODO.perf/04-ext-bound-serialization.md +15 -0
- data/TODO.perf/05-native-surface-completion.md +19 -0
- data/TODO.perf/06-perf-battery-native-legs.md +16 -0
- data/TODO.perf/07-native-read-floor.md +31 -0
- data/TODO.perf/08-version-coherent-native-mutations.md +24 -0
- data/TODO.perf/09-adoption-lift-fast-path.md +38 -0
- data/TODO.perf/10-bulk-attribute-materialization.md +20 -0
- data/TODO.perf/11-moxml-gap-battery.md +18 -0
- data/TODO.perf/12-document-lifetime-in-c.md +47 -0
- data/TODO.perf/13-structural-memos.md +49 -0
- data/TODO.perf/14-insert-family-c-bound.md +31 -0
- data/TODO.perf/15-compiled-expression-cache.md +35 -0
- data/TODO.perf/16-at-xpath-single-result-seam.md +38 -0
- data/TODO.perf/17-precomputed-fast-path-flags.md +35 -0
- data/TODO.perf/18-inner-html-one-c-pass.md +32 -0
- data/TODO.perf/19-lazy-node-pointer.md +39 -0
- data/TODO.perf/20-css-translation-cache.md +24 -0
- data/TODO.perf/21-key-memo-consult.md +20 -0
- data/TODO.perf/22-eager-nodeset-materialization.md +30 -0
- data/TODO.perf/23-cbound-value-mutations.md +27 -0
- data/TODO.perf/24-immutable-read-lanes.md +22 -0
- data/TODO.perf/25-scope-owned-bulk-path.md +39 -0
- data/TODO.perf/26-post-mutation-memo-seeding.md +26 -0
- data/TODO.perf/27-c-yield-traversal.md +38 -0
- data/TODO.perf/28-address-fills.md +25 -0
- data/TODO.perf/29-ns-xpath-compiled.md +23 -0
- data/TODO.perf/30-copy-and-element-child-faces.md +31 -0
- data/TODO.perf/31-battery-rounds-4-8.md +23 -0
- data/TODO.perf/32-clean-host-floor-table.md +29 -0
- data/TODO.perf/33-cbound-root-set.md +29 -0
- data/TODO.perf/34-fragment-fast-lane.md +41 -0
- data/TODO.perf/35-parse-default-c-face.md +24 -0
- data/TODO.perf/36-raise-in-c-serializer-encoding.md +34 -0
- data/TODO.perf/37-document-lazy-pointer.md +28 -0
- data/TODO.perf/38-bulk-walk-and-bench-gate.md +23 -0
- data/TODO.restructure/01-constraint-compliance-audit.md +40 -0
- data/TODO.restructure/02-deep-copy-seam.md +21 -0
- data/TODO.restructure/03-evaluation-context-seam.md +18 -0
- data/TODO.restructure/04-spec-mece-restructure.md +20 -0
- data/TODO.restructure/05-memory-documentation.md +13 -0
- data/TODO.restructure/06-architecture-map-refresh.md +12 -0
- data/TODO.restructure/07-external-gates.md +16 -0
- data/TODO.restructure/08-new-code-audit-and-ext-memo.md +11 -0
- data/TODO.restructure/09-iteration-scope.md +29 -0
- data/TODO.restructure/10-cold-attr-read-diet.md +19 -0
- data/TODO.restructure/11-construction-factory-diet.md +16 -0
- data/TODO.restructure/12-resultattr-merge.md +12 -0
- data/TODO.restructure/13-spec-mece-followup.md +9 -0
- data/TODO.restructure/14-constraint-audit-refresh.md +15 -0
- data/TODO.restructure/15-readme-feature-documentation.md +10 -0
- data/TODO.restructure/16-claudemd-map-refresh.md +5 -0
- data/TODO.restructure/17-perf-drift-record.md +12 -0
- data/TODO.restructure/18-audit-spec-doubles-and-gem-contents.md +12 -0
- data/TODO.restructure/19-gitignore-hygiene.md +10 -0
- data/TODO.restructure/20-utf8proc-enablement.md +34 -0
- data/TODO.restructure/21-typeddata-variant-prototype.md +42 -0
- data/TODO.restructure/22-sax-drain-bulk-strip.md +28 -0
- data/docs/adr/0001-lockstep-mirror.md +17 -0
- data/docs/adr/0002-utf8-at-the-seam.md +19 -0
- data/docs/adr/0003-readonly-memoization-pattern.md +34 -0
- data/docs/adr/0004-lifetime-guard.md +21 -0
- data/docs/adr/0005-autoload-manifest-ordering.md +19 -0
- data/docs/adr/0006-ruby-variant-policy.md +25 -0
- data/ext/build_windows_native.rb +42 -0
- data/ext/leptris/native/extconf.rb +55 -0
- data/ext/leptris/native/native.c +2646 -0
- data/leptris.gemspec +43 -0
- data/lib/leptris/version.rb +5 -0
- data/lib/leptris/xml/attr.rb +76 -0
- data/lib/leptris/xml/c_string_array.rb +37 -0
- data/lib/leptris/xml/cdata.rb +27 -0
- data/lib/leptris/xml/comment.rb +27 -0
- data/lib/leptris/xml/css_to_xpath.rb +192 -0
- data/lib/leptris/xml/descriptor.rb +214 -0
- data/lib/leptris/xml/diff.rb +74 -0
- data/lib/leptris/xml/doc_type.rb +54 -0
- data/lib/leptris/xml/document.rb +716 -0
- data/lib/leptris/xml/document_fragment.rb +63 -0
- data/lib/leptris/xml/element.rb +771 -0
- data/lib/leptris/xml/entity_reference.rb +13 -0
- data/lib/leptris/xml/evaluation_context.rb +32 -0
- data/lib/leptris/xml/ffi.rb +1527 -0
- data/lib/leptris/xml/iteration_scope.rb +56 -0
- data/lib/leptris/xml/iterparse.rb +144 -0
- data/lib/leptris/xml/namespace.rb +43 -0
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/native_layer.rb +92 -0
- data/lib/leptris/xml/node.rb +686 -0
- data/lib/leptris/xml/node_set.rb +212 -0
- data/lib/leptris/xml/parse_options.rb +97 -0
- data/lib/leptris/xml/plan_value.rb +121 -0
- data/lib/leptris/xml/processing_instruction.rb +90 -0
- data/lib/leptris/xml/pull.rb +212 -0
- data/lib/leptris/xml/relaxng.rb +126 -0
- data/lib/leptris/xml/result_attr.rb +39 -0
- data/lib/leptris/xml/result_text.rb +36 -0
- data/lib/leptris/xml/sax/document.rb +45 -0
- data/lib/leptris/xml/sax/dom_dispatch.rb +144 -0
- data/lib/leptris/xml/sax/parser.rb +326 -0
- data/lib/leptris/xml/sax/recorder.rb +374 -0
- data/lib/leptris/xml/sax.rb +14 -0
- data/lib/leptris/xml/schematron.rb +88 -0
- data/lib/leptris/xml/searchable.rb +361 -0
- data/lib/leptris/xml/serialization.rb +180 -0
- data/lib/leptris/xml/text.rb +32 -0
- data/lib/leptris/xml/xpath.rb +89 -0
- data/lib/leptris/xml/xquery.rb +77 -0
- data/lib/leptris/xml/xslt.rb +85 -0
- data/lib/leptris/xml.rb +108 -0
- data/lib/leptris.rb +53 -0
- data/lib/libleptris.so +0 -0
- data/lib/libutf8proc.so.3 +0 -0
- data/scripts/gem_smoke.rb +36 -0
- metadata +214 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
# The owning context for elements yielded by an Iterparse run —
|
|
6
|
+
# the lifetime and memoization authority that a Document is for
|
|
7
|
+
# parsed trees, scoped to one iteration:
|
|
8
|
+
#
|
|
9
|
+
# - `#c_ptr` carries the iterator handle and goes nil on `#free` —
|
|
10
|
+
# the same signal `Node#ensure_alive!` reads, so using a held
|
|
11
|
+
# element after the iterator frees RAISES UseAfterFreeError
|
|
12
|
+
# instead of segfaulting (ruby#152).
|
|
13
|
+
# - `#wrapper_cache` gives wrapper identity within a yielded
|
|
14
|
+
# subtree (children() twice returns the same wrappers).
|
|
15
|
+
# - `#version` advances per yield and on mutation — stale memos on
|
|
16
|
+
# held wrappers invalidate as subtrees are released.
|
|
17
|
+
#
|
|
18
|
+
# `Node#document` answers nil for scope-owned elements (the
|
|
19
|
+
# documented iterparse contract) while the internal machinery
|
|
20
|
+
# engages — including the fast bare-name attribute path, which is
|
|
21
|
+
# what makes iterparse attribute reads match document-backed cost.
|
|
22
|
+
class Leptris::XML::IterationScope
|
|
23
|
+
def initialize(iterator_handle)
|
|
24
|
+
@c_ptr = iterator_handle
|
|
25
|
+
@wrapper_cache = {}
|
|
26
|
+
@version = 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :c_ptr, :wrapper_cache
|
|
30
|
+
|
|
31
|
+
def version
|
|
32
|
+
@version
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def advance_version
|
|
36
|
+
@version += 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def readonly?
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Called by the owning Iterparse when it frees the C iterator —
|
|
44
|
+
# every wrapper it handed out fails liveness from here on.
|
|
45
|
+
def mark_freed
|
|
46
|
+
@c_ptr = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Called before each yield: the previous subtree is released and
|
|
50
|
+
# its pool memory recycled — a fresh element may land at a cached
|
|
51
|
+
# address, so the identity cache and the memo version both reset.
|
|
52
|
+
def new_subtree!
|
|
53
|
+
@wrapper_cache = {}
|
|
54
|
+
@version += 1
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
# Incremental tree iteration (libleptris v1.6.0): yields each
|
|
6
|
+
# completed TOP-LEVEL child element of the document root as it
|
|
7
|
+
# parses. The previous element's whole subtree is released when the
|
|
8
|
+
# next is produced — memory stays bounded by the largest subtree,
|
|
9
|
+
# not the document.
|
|
10
|
+
#
|
|
11
|
+
# Leptris::XML::Iterparse.parse(huge_xml) do |element|
|
|
12
|
+
# process(element) # subtree valid until the next iteration
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# v2 (libleptris 1.9.4, #586): an explicit yield mode —
|
|
16
|
+
# :top_level (v1: the root's completed children) or
|
|
17
|
+
# :full_document (every element in document-order completion —
|
|
18
|
+
# post-order, child before parent, subtree attached) — plus
|
|
19
|
+
# namespace resolution on the last yielded element and an error
|
|
20
|
+
# channel for truncated input:
|
|
21
|
+
#
|
|
22
|
+
# it = Leptris::XML::Iterparse.parse(xml, mode: :full_document)
|
|
23
|
+
# it.run do |el|
|
|
24
|
+
# it.namespace_uri("p") # in scope on el, the last yielded
|
|
25
|
+
# end
|
|
26
|
+
# it.free
|
|
27
|
+
#
|
|
28
|
+
# Leptris::XML::Iterparse.parse(xml) { |el| ... }.error
|
|
29
|
+
# # => nil, or the parse failure (readable after the run: the
|
|
30
|
+
# # block form frees the iterator on return, snapshotting it)
|
|
31
|
+
#
|
|
32
|
+
# The yielded elements are owned by an internal IterationScope:
|
|
33
|
+
# #document returns nil (the documented contract) while liveness
|
|
34
|
+
# guards and memoization engage — using an element after the
|
|
35
|
+
# iterator frees raises UseAfterFreeError instead of crashing.
|
|
36
|
+
class Leptris::XML::Iterparse
|
|
37
|
+
MODES = {
|
|
38
|
+
top_level: Leptris::XML::FFI::ITERPARSE_TOP_LEVEL,
|
|
39
|
+
full_document: Leptris::XML::FFI::ITERPARSE_FULL_DOCUMENT,
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
# NOTE: an IO argument is read fully into memory before the C
|
|
43
|
+
# iterator starts — the bounded-memory path is parse_file (C-side
|
|
44
|
+
# file streaming). The C API takes one (xml, len) buffer.
|
|
45
|
+
def self.parse(xml_or_io, mode: :top_level, &block)
|
|
46
|
+
xml = xml_or_io.is_a?(String) ? xml_or_io : xml_or_io.read
|
|
47
|
+
iterator = new(Leptris::XML::FFI.leptris_iterparse_new_ex(
|
|
48
|
+
xml, xml.bytesize, mode_code(mode)))
|
|
49
|
+
return iterator unless block
|
|
50
|
+
begin
|
|
51
|
+
iterator.run(&block)
|
|
52
|
+
ensure
|
|
53
|
+
iterator.free
|
|
54
|
+
end
|
|
55
|
+
iterator
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.parse_file(path, mode: :top_level, &block)
|
|
59
|
+
iterator = new(Leptris::XML::FFI.leptris_iterparse_new_file_ex(
|
|
60
|
+
path, mode_code(mode)))
|
|
61
|
+
return iterator unless block
|
|
62
|
+
begin
|
|
63
|
+
iterator.run(&block)
|
|
64
|
+
ensure
|
|
65
|
+
iterator.free
|
|
66
|
+
end
|
|
67
|
+
iterator
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.mode_code(mode)
|
|
71
|
+
MODES.fetch(mode) do
|
|
72
|
+
raise ArgumentError,
|
|
73
|
+
"mode must be :top_level or :full_document, got #{mode.inspect}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
private_class_method :mode_code
|
|
77
|
+
|
|
78
|
+
def initialize(handle)
|
|
79
|
+
raise Leptris::XML::ParseError, "leptris_iterparse_new failed" if handle.null?
|
|
80
|
+
@handle = handle
|
|
81
|
+
@scope = Leptris::XML::IterationScope.new(handle)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Yields completed elements until the document is exhausted or the
|
|
85
|
+
# parse fails (see #error). Does not free the iterator — the block
|
|
86
|
+
# form of .parse/.parse_file does, or call #free explicitly.
|
|
87
|
+
def run
|
|
88
|
+
return enum_for(:run) unless block_given?
|
|
89
|
+
while @handle
|
|
90
|
+
ptr = Leptris::XML::FFI.leptris_iterparse_next(@handle)
|
|
91
|
+
break if ptr.null?
|
|
92
|
+
# The scope owns lifetime + memoization for yielded elements
|
|
93
|
+
# (ruby#152): liveness guards engage (post-free use raises
|
|
94
|
+
# instead of segfaulting), wrapper identity holds within a
|
|
95
|
+
# subtree, and memoized reads match document-backed cost.
|
|
96
|
+
# Each yield starts a fresh subtree — the released pool
|
|
97
|
+
# memory is recycled, so the identity cache resets with the
|
|
98
|
+
# memo version. #document still answers nil on the elements.
|
|
99
|
+
@scope.new_subtree!
|
|
100
|
+
yield Leptris::XML::Node.wrap(ptr, @scope)
|
|
101
|
+
end
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# The URI bound to +prefix+ ("" for the default namespace) in the
|
|
106
|
+
# last yielded element's in-scope namespace snapshot, or nil when
|
|
107
|
+
# unbound (v2, #586). Valid only while the iterator is live —
|
|
108
|
+
# call it inside the #run block; nil after #free.
|
|
109
|
+
def namespace_uri(prefix)
|
|
110
|
+
return nil if @handle.nil?
|
|
111
|
+
Leptris::XML::FFI.leptris_iterparse_ns_uri(@handle, prefix.to_s)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Number of namespace bindings in the last yielded element's
|
|
115
|
+
# scope. Valid only while the iterator is live; 0 after #free.
|
|
116
|
+
def namespace_count
|
|
117
|
+
return 0 if @handle.nil?
|
|
118
|
+
Leptris::XML::FFI.leptris_iterparse_ns_count(@handle)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# The parse error for malformed/truncated input, or nil when the
|
|
122
|
+
# document is well-formed and exhausted. Yielded elements stop at
|
|
123
|
+
# the failure point; #free snapshots the message so this stays
|
|
124
|
+
# readable after the block form of .parse returns.
|
|
125
|
+
def error
|
|
126
|
+
@handle ? live_error : @last_error
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Releases the C iterator. Safe to call twice.
|
|
130
|
+
def free
|
|
131
|
+
return if @handle.nil?
|
|
132
|
+
@last_error = live_error
|
|
133
|
+
Leptris::XML::FFI.leptris_iterparse_free(@handle)
|
|
134
|
+
@handle = nil
|
|
135
|
+
@scope.mark_freed
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
def live_error
|
|
141
|
+
msg = Leptris::XML::FFI.leptris_iterparse_error(@handle)
|
|
142
|
+
msg.nil? || msg.empty? ? nil : msg
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# libleptris's LeptrisNamespace is `const char*` (the URI directly). The Ruby
|
|
4
|
+
# Namespace wrapper holds (element, uri, optional prefix). When the prefix
|
|
5
|
+
# isn't supplied (e.g. via Element#namespace), it can be derived by walking
|
|
6
|
+
# the element's namespace declarations via leptris_element_namespace_decl_*.
|
|
7
|
+
|
|
8
|
+
class Leptris::XML::Namespace
|
|
9
|
+
attr_reader :element, :href, :prefix
|
|
10
|
+
|
|
11
|
+
def initialize(element, href, prefix: :derive)
|
|
12
|
+
@element = element
|
|
13
|
+
@href = href
|
|
14
|
+
@prefix = prefix == :derive ? derive_prefix : prefix
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def document
|
|
18
|
+
@element&.document
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ==(other)
|
|
22
|
+
other.is_a?(Leptris::XML::Namespace) &&
|
|
23
|
+
@href == other.href && @prefix == other.prefix
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def inspect
|
|
27
|
+
"#<#{self.class.name} prefix=#{@prefix.inspect} href=#{@href.inspect}>"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def derive_prefix
|
|
33
|
+
return nil if @element.nil?
|
|
34
|
+
count = Leptris::XML::FFI.leptris_element_namespace_count(@element.c_ptr)
|
|
35
|
+
count.times do |i|
|
|
36
|
+
uri = Leptris::XML::FFI.leptris_element_namespace_decl_uri(@element.c_ptr, i)
|
|
37
|
+
next unless uri == @href
|
|
38
|
+
prefix = Leptris::XML::FFI.leptris_element_namespace_decl_prefix(@element.c_ptr, i)
|
|
39
|
+
return prefix.nil? || prefix.empty? ? nil : prefix
|
|
40
|
+
end
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Opt-in native read layer (TODO.restructure/21 round 2, #185):
|
|
4
|
+
# TypedData node wrappers with C-bound hot reads and bulk children
|
|
5
|
+
# construction — measured 6.2x on raw walks. Load explicitly:
|
|
6
|
+
#
|
|
7
|
+
# require "leptris/xml/native"
|
|
8
|
+
# root = doc.native_node
|
|
9
|
+
# root.children.each { |c| c.name }
|
|
10
|
+
#
|
|
11
|
+
# Identity is shared with the binding through the per-document
|
|
12
|
+
# wrapper cache, so native and binding views of one document
|
|
13
|
+
# interoperate. The default require path never loads this file;
|
|
14
|
+
# installs without the compiled bundle (ruby-platform gem) raise
|
|
15
|
+
# a clear LoadError from the require.
|
|
16
|
+
|
|
17
|
+
require "ffi" unless defined?(::FFI::Library)
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
# The compiled bundle. A sanctioned require exception: a
|
|
21
|
+
# .bundle/.so cannot go through autoload. Missing in installs
|
|
22
|
+
# that do not vendor it (ruby-platform gem). Windows names the
|
|
23
|
+
# artifact per Ruby minor, DOT-FREE (#207/#227): a PE DLL must
|
|
24
|
+
# bind its build Ruby's runtime (one DLL per minor), and MRI
|
|
25
|
+
# derives the init symbol from the basename cut at the first
|
|
26
|
+
# dot — native_3_3.so resolves Init_native_3_3.
|
|
27
|
+
if Gem.win_platform?
|
|
28
|
+
require "leptris/xml/native_#{RUBY_VERSION[/\A\d+\.\d+/].tr('.', '_')}"
|
|
29
|
+
else
|
|
30
|
+
require "leptris/xml/native"
|
|
31
|
+
end
|
|
32
|
+
rescue LoadError => e
|
|
33
|
+
raise LoadError, <<~MSG
|
|
34
|
+
leptris: the compiled native read layer is not available in
|
|
35
|
+
this install (#{e.message}). It ships in platform gems; the
|
|
36
|
+
ruby-platform variant does not carry it.
|
|
37
|
+
MSG
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
module Leptris::XML::Native
|
|
41
|
+
# FFI.libleptris_candidates is the single source of truth — the
|
|
42
|
+
# native layer MUST dlsym the exact image the FFI layer loaded,
|
|
43
|
+
# or two library instances split per-document state.
|
|
44
|
+
def self.lib_candidates
|
|
45
|
+
Leptris::XML::FFI.libleptris_candidates
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# TODO.perf/01: accelerate the DEFAULT binding classes while the
|
|
50
|
+
# bundle is loaded (name/content/prefix/[] hot reads).
|
|
51
|
+
Leptris::XML::NATIVE_FAST = true
|
|
52
|
+
|
|
53
|
+
candidates = Leptris::XML::Native.lib_candidates
|
|
54
|
+
resolved = candidates.any? do |path|
|
|
55
|
+
begin
|
|
56
|
+
Leptris::XML::NativeNode.resolve!(path)
|
|
57
|
+
true
|
|
58
|
+
rescue RuntimeError
|
|
59
|
+
false
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
raise LoadError,
|
|
63
|
+
"leptris: native layer loaded but libleptris could not be " \
|
|
64
|
+
"resolved from #{candidates.inspect}" unless resolved
|
|
65
|
+
|
|
66
|
+
class Leptris::XML::Document
|
|
67
|
+
# Separate identity cache for NativeNodes so binding Node.wrap
|
|
68
|
+
# and the native layer never overwrite each other.
|
|
69
|
+
def native_cache
|
|
70
|
+
@native_cache ||= {}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def native_node
|
|
74
|
+
r = root or raise Leptris::XML::Error, "document has no root element"
|
|
75
|
+
Leptris::XML::NativeNode.from(self, r.c_ptr)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Builder factories that return NativeNodes (#149): one C call
|
|
79
|
+
# and a TypedData wrap — no FFI::Pointer, no wrap_fresh path.
|
|
80
|
+
def native_create_element(name)
|
|
81
|
+
Leptris::XML::NativeNode.create_element(self, name.to_s)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def native_create_text(content)
|
|
85
|
+
Leptris::XML::NativeNode.create_text(self, content.to_s)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def native_root=(element)
|
|
89
|
+
Leptris::XML::NativeNode.set_root(self, element)
|
|
90
|
+
element
|
|
91
|
+
end
|
|
92
|
+
end
|