leptris 1.9.199.0-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 +2528 -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,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module Leptris
|
|
6
|
+
module XML
|
|
7
|
+
module SAX
|
|
8
|
+
autoload :Document, "leptris/xml/sax/document"
|
|
9
|
+
autoload :Parser, "leptris/xml/sax/parser"
|
|
10
|
+
autoload :Recorder, "leptris/xml/sax/recorder"
|
|
11
|
+
autoload :DomDispatch, "leptris/xml/sax/dom_dispatch"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module Leptris::XML::Schematron
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def parse(schema_xml, phase: nil)
|
|
9
|
+
Schema.parse(schema_xml, phase: phase)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parse_file(path, phase: nil)
|
|
13
|
+
Schema.parse_file(path, phase: phase)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# A compiled Schematron schema (libleptris >= 1.9.126): parse
|
|
17
|
+
# once — optionally selecting a PHASE, which activates only its
|
|
18
|
+
# patterns — then validate any number of documents. #validate
|
|
19
|
+
# returns the SVRL report as a queryable Document; #valid?
|
|
20
|
+
# answers the boolean.
|
|
21
|
+
#
|
|
22
|
+
# sch = Leptris::XML::Schematron.parse(schema_xml)
|
|
23
|
+
# sch.valid?(doc) # => true/false
|
|
24
|
+
# svrl = sch.validate(doc) # => Document (svrl:schema root)
|
|
25
|
+
# sch = Leptris::XML::Schematron.parse(schema_xml, phase: "Basic")
|
|
26
|
+
#
|
|
27
|
+
class Schema
|
|
28
|
+
class Handle < ::FFI::AutoPointer
|
|
29
|
+
def self.release(ptr)
|
|
30
|
+
Leptris::XML::FFI.leptris_schematron_free(ptr)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.parse(schema_xml, phase: nil)
|
|
35
|
+
xml = schema_xml.to_s
|
|
36
|
+
raw =
|
|
37
|
+
if phase
|
|
38
|
+
Leptris::XML::FFI.leptris_schematron_parse_phase(
|
|
39
|
+
xml, xml.bytesize, phase.to_s, nil)
|
|
40
|
+
else
|
|
41
|
+
Leptris::XML::FFI.leptris_schematron_parse(xml, xml.bytesize, nil)
|
|
42
|
+
end
|
|
43
|
+
if raw.null?
|
|
44
|
+
raise Leptris::XML::Error,
|
|
45
|
+
"schema parse failed: #{Leptris::XML::FFI.leptris_last_error}"
|
|
46
|
+
end
|
|
47
|
+
new(Handle.new(raw))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.parse_file(path, phase: nil)
|
|
51
|
+
raw =
|
|
52
|
+
if phase
|
|
53
|
+
xml = File.read(path)
|
|
54
|
+
Leptris::XML::FFI.leptris_schematron_parse_phase(
|
|
55
|
+
xml, xml.bytesize, phase.to_s, nil)
|
|
56
|
+
else
|
|
57
|
+
Leptris::XML::FFI.leptris_schematron_parse_file(path.to_s, nil)
|
|
58
|
+
end
|
|
59
|
+
if raw.null?
|
|
60
|
+
raise Leptris::XML::Error,
|
|
61
|
+
"schema parse failed: #{Leptris::XML::FFI.leptris_last_error}"
|
|
62
|
+
end
|
|
63
|
+
new(Handle.new(raw))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def initialize(handle)
|
|
67
|
+
@handle = handle
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The SVRL report as a Document (svrl:schema root — query
|
|
71
|
+
# failed-assert / successful-report elements like any other
|
|
72
|
+
# tree), or the schema-level error text.
|
|
73
|
+
def validate(document)
|
|
74
|
+
report = Leptris::XML::FFI.leptris_schematron_validate(
|
|
75
|
+
@handle, document.c_ptr)
|
|
76
|
+
if report.null?
|
|
77
|
+
Leptris::XML::FFI.leptris_schematron_error(@handle)
|
|
78
|
+
else
|
|
79
|
+
Leptris::XML::Document.wrap(report)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def valid?(document)
|
|
84
|
+
Leptris::XML::FFI.leptris_schematron_valid(
|
|
85
|
+
@handle, document.c_ptr) != 0
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Leptris::XML::Searchable
|
|
4
|
+
# Values that make a :version key the version channel (#183,
|
|
5
|
+
# mirroring leptris-py#105); anything else is a namespace prefix.
|
|
6
|
+
VERSION_SELECTORS = ["1.0", "3.1", :xpath10, :xpath31].freeze
|
|
7
|
+
|
|
8
|
+
# Compiled-expression cache (TODO.perf/15): the engine caches
|
|
9
|
+
# compiled strings internally, but the string path still pays
|
|
10
|
+
# lookup + hashing per call — a direct handle eval measured 34%
|
|
11
|
+
# under it on repeat expressions. Bounded LRU keyed on the
|
|
12
|
+
# expression string; only successful compiles cache; GVL makes
|
|
13
|
+
# the Hash operations safe. Version-pinned and namespace-bound
|
|
14
|
+
# evaluations keep their dedicated string entries.
|
|
15
|
+
COMPILED_CACHE_LIMIT = 64
|
|
16
|
+
|
|
17
|
+
def self.compiled_expression(expr)
|
|
18
|
+
cache = (@compiled_expressions ||= {})
|
|
19
|
+
if (hit = cache[expr])
|
|
20
|
+
cache.delete(expr)
|
|
21
|
+
cache[expr] = hit # LRU refresh
|
|
22
|
+
return hit
|
|
23
|
+
end
|
|
24
|
+
compiled = Leptris::XML::XPath.compile(expr)
|
|
25
|
+
cache.shift while cache.size >= COMPILED_CACHE_LIMIT
|
|
26
|
+
cache[expr] = compiled
|
|
27
|
+
compiled
|
|
28
|
+
rescue Leptris::XML::XPathError
|
|
29
|
+
nil # fall back to the string path — its error surface is the contract
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# CSS translation cache (TODO.perf/20): the selector -> XPath
|
|
33
|
+
# translation is a pure function of (selector, prefix); without
|
|
34
|
+
# it every css call re-ran the regex machinery before the
|
|
35
|
+
# compiled-expression cache could hit. Same bounded-LRU shape.
|
|
36
|
+
def self.css_expression(selector, prefix)
|
|
37
|
+
cache = (@css_expressions ||= {})
|
|
38
|
+
key = "#{prefix}#{selector}"
|
|
39
|
+
if (hit = cache[key])
|
|
40
|
+
cache.delete(key)
|
|
41
|
+
cache[key] = hit # LRU refresh
|
|
42
|
+
return hit
|
|
43
|
+
end
|
|
44
|
+
expr = Leptris::XML::CssToXPath.convert(selector, prefix: prefix)
|
|
45
|
+
cache.shift while cache.size >= COMPILED_CACHE_LIMIT
|
|
46
|
+
cache[key] = expr
|
|
47
|
+
expr
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def xpath(*paths)
|
|
51
|
+
handler, ns, version = parse_search_args(paths)
|
|
52
|
+
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
53
|
+
# Single-String shape (the common call): join would mint a
|
|
54
|
+
# fresh String per call even for one path.
|
|
55
|
+
expr = paths.one? && paths.first.is_a?(String) ? paths.first
|
|
56
|
+
: paths.join(" | ")
|
|
57
|
+
|
|
58
|
+
# TODO.perf/37: the document's Pointer is lazy (materializes
|
|
59
|
+
# once per document lifetime, not per parse); the ffi gem
|
|
60
|
+
# does NOT convert Integers for typedef'd pointer params, so
|
|
61
|
+
# the Pointer object is still what crosses here.
|
|
62
|
+
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
63
|
+
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
64
|
+
|
|
65
|
+
result_ptr =
|
|
66
|
+
if version
|
|
67
|
+
if ns && !ns.empty?
|
|
68
|
+
raise ArgumentError,
|
|
69
|
+
"version: cannot be combined with namespace bindings " \
|
|
70
|
+
"(the versioned engine entry takes no ns set yet)"
|
|
71
|
+
end
|
|
72
|
+
Leptris::XML::FFI.leptris_xpath_eval_versioned(
|
|
73
|
+
doc_ptr, context_ptr, expr,
|
|
74
|
+
Leptris::XML::Searchable.xpath_version_code(version), nil)
|
|
75
|
+
elsif ns && !ns.empty?
|
|
76
|
+
# TODO.perf/29: the compiled handle is ns-independent —
|
|
77
|
+
# ride the cache and the cached ns set instead of the
|
|
78
|
+
# engine's string parse per call.
|
|
79
|
+
if (compiled = Leptris::XML::Searchable.compiled_expression(expr))
|
|
80
|
+
Leptris::XML::FFI.with_ns_set(ns) do |set|
|
|
81
|
+
compiled.eval_ns_ptrs(doc_ptr, context_ptr, set)
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
85
|
+
end
|
|
86
|
+
elsif (compiled = Leptris::XML::Searchable.compiled_expression(expr))
|
|
87
|
+
compiled.eval_ptrs(doc_ptr, context_ptr)
|
|
88
|
+
else
|
|
89
|
+
Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
|
|
90
|
+
end
|
|
91
|
+
if result_ptr.null?
|
|
92
|
+
raise Leptris::XML::XPathError,
|
|
93
|
+
Leptris::XML::FFI.leptris_last_error.to_s
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
Leptris::XML::Searchable.wrap_xpath_result(document, result_ptr)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def at_xpath(*paths)
|
|
100
|
+
handler, ns, version = parse_search_args(paths)
|
|
101
|
+
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
102
|
+
# Single-String shape (the common call): join would mint a
|
|
103
|
+
# fresh String per call even for one path.
|
|
104
|
+
expr = paths.one? && paths.first.is_a?(String) ? paths.first
|
|
105
|
+
: paths.join(" | ")
|
|
106
|
+
|
|
107
|
+
# TODO.perf/37: the document's Pointer is lazy (materializes
|
|
108
|
+
# once per document lifetime, not per parse); the ffi gem
|
|
109
|
+
# does NOT convert Integers for typedef'd pointer params, so
|
|
110
|
+
# the Pointer object is still what crosses here.
|
|
111
|
+
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
112
|
+
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
113
|
+
|
|
114
|
+
result_ptr =
|
|
115
|
+
if version
|
|
116
|
+
if ns && !ns.empty?
|
|
117
|
+
raise ArgumentError,
|
|
118
|
+
"version: cannot be combined with namespace bindings " \
|
|
119
|
+
"(the versioned engine entry takes no ns set yet)"
|
|
120
|
+
end
|
|
121
|
+
Leptris::XML::FFI.leptris_xpath_eval_versioned(
|
|
122
|
+
doc_ptr, context_ptr, expr,
|
|
123
|
+
Leptris::XML::Searchable.xpath_version_code(version), nil)
|
|
124
|
+
elsif ns && !ns.empty?
|
|
125
|
+
# TODO.perf/29: the compiled handle is ns-independent —
|
|
126
|
+
# ride the cache and the cached ns set instead of the
|
|
127
|
+
# engine's string parse per call.
|
|
128
|
+
if (compiled = Leptris::XML::Searchable.compiled_expression(expr))
|
|
129
|
+
Leptris::XML::FFI.with_ns_set(ns) do |set|
|
|
130
|
+
compiled.eval_ns_ptrs(doc_ptr, context_ptr, set)
|
|
131
|
+
end
|
|
132
|
+
else
|
|
133
|
+
xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
134
|
+
end
|
|
135
|
+
elsif (compiled = Leptris::XML::Searchable.compiled_expression(expr))
|
|
136
|
+
compiled.eval_ptrs(doc_ptr, context_ptr)
|
|
137
|
+
else
|
|
138
|
+
Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
|
|
139
|
+
end
|
|
140
|
+
if result_ptr.null?
|
|
141
|
+
raise Leptris::XML::XPathError,
|
|
142
|
+
Leptris::XML::FFI.leptris_last_error.to_s
|
|
143
|
+
end
|
|
144
|
+
Leptris::XML::Searchable.wrap_xpath_first_result(document, result_ptr)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def search(*args)
|
|
148
|
+
paths = args.first.is_a?(Array) ? args.first : [args.first]
|
|
149
|
+
paths.map(&:to_s).all? { |p| looks_like_xpath?(p) } ? xpath(*paths) : css(*paths)
|
|
150
|
+
end
|
|
151
|
+
alias_method :/, :search
|
|
152
|
+
|
|
153
|
+
# Single-node seam (round XIV): dispatch on syntax like #search,
|
|
154
|
+
# then call the at_* fast paths directly — no NodeSet container,
|
|
155
|
+
# no result handle, one fewer dispatch than search().first.
|
|
156
|
+
def at(*args)
|
|
157
|
+
paths = args.first.is_a?(Array) ? args.first : [args.first]
|
|
158
|
+
if paths.map(&:to_s).all? { |p| looks_like_xpath?(p) }
|
|
159
|
+
at_xpath(*paths)
|
|
160
|
+
else
|
|
161
|
+
at_css(*args)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
alias_method :%, :at
|
|
165
|
+
|
|
166
|
+
def css(*args)
|
|
167
|
+
handler, ns, version = parse_search_args(args)
|
|
168
|
+
raise ArgumentError, "namespace bindings not supported in css" if ns && !ns.empty?
|
|
169
|
+
raise ArgumentError, "version: is XPath-only" if version
|
|
170
|
+
raise ArgumentError, "custom CSS handlers not supported" if handler
|
|
171
|
+
# Nokogiri semantics: css is receiver-relative — absolute "//"
|
|
172
|
+
# from a Document, descendant ".//" from elements and fragments.
|
|
173
|
+
prefix = is_a?(Leptris::XML::Document) ? "//" : ".//"
|
|
174
|
+
expr = args.map { |r| Leptris::XML::Searchable.css_expression(r, prefix) }
|
|
175
|
+
.join(" | ")
|
|
176
|
+
xpath(expr)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def at_css(*args)
|
|
180
|
+
handler, ns, version = parse_search_args(args)
|
|
181
|
+
raise ArgumentError, "namespace bindings not supported in css" if ns && !ns.empty?
|
|
182
|
+
raise ArgumentError, "version: is XPath-only" if version
|
|
183
|
+
raise ArgumentError, "custom CSS handlers not supported" if handler
|
|
184
|
+
prefix = is_a?(Leptris::XML::Document) ? "//" : ".//"
|
|
185
|
+
expr = args.map { |r| Leptris::XML::Searchable.css_expression(r, prefix) }
|
|
186
|
+
.join(" | ")
|
|
187
|
+
at_xpath(expr)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Version-pinned evaluation (libleptris lane 15,
|
|
191
|
+
# leptris_xpath_eval_versioned): :xpath10 keeps the strict XPath
|
|
192
|
+
# 1.0 surface — 3.x syntax (arrow, bang, lookup, let, inline
|
|
193
|
+
# functions, string templates) raises; :xpath31 evaluates the
|
|
194
|
+
# full grammar (identical to #xpath's default entry). A separate
|
|
195
|
+
# method by design: #xpath's trailing-hash argument is the
|
|
196
|
+
# namespace-binding channel, and a declared keyword would
|
|
197
|
+
# capture it.
|
|
198
|
+
def xpath_versioned(expression, version)
|
|
199
|
+
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
200
|
+
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
201
|
+
result_ptr = Leptris::XML::FFI.leptris_xpath_eval_versioned(
|
|
202
|
+
doc_ptr, context_ptr, expression,
|
|
203
|
+
Leptris::XML::Searchable.xpath_version_code(version), nil)
|
|
204
|
+
if result_ptr.null?
|
|
205
|
+
raise Leptris::XML::XPathError,
|
|
206
|
+
Leptris::XML::FFI.leptris_last_error.to_s
|
|
207
|
+
end
|
|
208
|
+
Leptris::XML::Searchable.wrap_xpath_result(document, result_ptr)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
protected
|
|
212
|
+
|
|
213
|
+
# Evaluates against a caller-owned namespace binding set; the
|
|
214
|
+
# build/teardown lifecycle lives at the FFI seam (FFI.with_ns_set).
|
|
215
|
+
def xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
216
|
+
Leptris::XML::FFI.with_ns_set(ns) do |set|
|
|
217
|
+
Leptris::XML::FFI.leptris_xpath_eval_ns(doc_ptr, context_ptr, expr, set)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# Version pin for xpath_versioned: :xpath10
|
|
223
|
+
# keeps the strict XPath 1.0 surface (3.x syntax — arrow, bang,
|
|
224
|
+
# lookup, let, inline functions — raises); :xpath31 evaluates the
|
|
225
|
+
# full grammar (identical to the default entry).
|
|
226
|
+
def self.xpath_version_code(version)
|
|
227
|
+
case version
|
|
228
|
+
when :xpath10, "1.0" then Leptris::XML::FFI::XPATH_10
|
|
229
|
+
when :xpath31, "3.1" then Leptris::XML::FFI::XPATH_31
|
|
230
|
+
else
|
|
231
|
+
raise ArgumentError,
|
|
232
|
+
"version must be :xpath10 or :xpath31, got #{version.inspect}"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Trailing-argument contract (#183): a String-keyed hash is the
|
|
237
|
+
# namespace-binding channel — as is a Symbol-keyed hash (the
|
|
238
|
+
# legacy prefix vocabulary, {x: "urn:x"}); a :version key whose
|
|
239
|
+
# VALUE is a version selector is the version-selection channel
|
|
240
|
+
# (xpath("//b", version: "1.0"), mirroring leptris-py#105). A
|
|
241
|
+
# DECLARED keyword cannot carry this: the moment #xpath declares
|
|
242
|
+
# one, Ruby converts the trailing namespace hash into keywords
|
|
243
|
+
# (unknown keyword: "p") — so both channels stay positional and
|
|
244
|
+
# dispatch on key/value here.
|
|
245
|
+
def parse_search_args(args)
|
|
246
|
+
handler = args.find { |a| !a.is_a?(String) && !a.is_a?(Hash) && !a.is_a?(Symbol) }
|
|
247
|
+
args = args - [handler] if handler
|
|
248
|
+
hashes = []
|
|
249
|
+
while args.last.is_a?(Hash) || args.last.nil?
|
|
250
|
+
hashes << args.pop
|
|
251
|
+
break if args.empty?
|
|
252
|
+
end
|
|
253
|
+
ns, vars = hashes.reverse
|
|
254
|
+
unless vars.nil? || vars.empty?
|
|
255
|
+
raise ArgumentError, "XPath variable bindings are not supported"
|
|
256
|
+
end
|
|
257
|
+
# Value-based disambiguation is load-bearing: Symbol keys are
|
|
258
|
+
# also the namespace vocabulary, and bare keywords FUSE into a
|
|
259
|
+
# preceding String-keyed hash at the call site ({"p" => uri,
|
|
260
|
+
# version: "1.0"} arrives as ONE hash). A :version key with any
|
|
261
|
+
# other value stays a prefix binding.
|
|
262
|
+
version = nil
|
|
263
|
+
if ns.is_a?(Hash)
|
|
264
|
+
v = ns[:version]
|
|
265
|
+
if VERSION_SELECTORS.include?(v)
|
|
266
|
+
version = ns.delete(:version)
|
|
267
|
+
ns = nil if ns.empty?
|
|
268
|
+
elsif v.is_a?(String) && v.match?(/\A\d+\.\d+\z/)
|
|
269
|
+
# A dotted-number :version value is version INTENT — raise
|
|
270
|
+
# with the valid values rather than silently binding a
|
|
271
|
+
# prefix literally. Genuine prefix bindings carry URIs.
|
|
272
|
+
raise ArgumentError,
|
|
273
|
+
"unknown XPath version #{v.inspect} " \
|
|
274
|
+
"(valid: \"1.0\"/:xpath10, \"3.1\"/:xpath31)"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
[handler, ns, version]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def looks_like_xpath?(str)
|
|
281
|
+
%r{\A(\./|/|\.\.|\.)}.match?(str)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Single-node seam beside wrap_xpath_result: a nodeset answers
|
|
285
|
+
# via result_get_node(0) + wrap + free — no NodeSet container, no
|
|
286
|
+
# AutoPointer, one fewer FFI than xpath().first; scalars keep the
|
|
287
|
+
# full-wrapper semantics.
|
|
288
|
+
def self.wrap_xpath_first_result(document, result_ptr)
|
|
289
|
+
# C seam (TODO.perf/16): nodesets materialize entry 0, free
|
|
290
|
+
# the handle, and return in one dispatch. The module object
|
|
291
|
+
# back means non-nodeset — the scalar path below owns it.
|
|
292
|
+
if defined?(Leptris::XML::NATIVE_FAST)
|
|
293
|
+
first = Leptris::XML::Native.at_xpath_first(
|
|
294
|
+
document, result_ptr.address)
|
|
295
|
+
return first unless first.equal?(Leptris::XML::Native)
|
|
296
|
+
end
|
|
297
|
+
type = Leptris::XML::FFI.leptris_xpath_result_type(result_ptr)
|
|
298
|
+
if type == Leptris::XML::FFI::XPATH_NODESET
|
|
299
|
+
ptr = Leptris::XML::FFI.leptris_xpath_result_get_node(result_ptr, 0)
|
|
300
|
+
if ptr.null?
|
|
301
|
+
node = nil
|
|
302
|
+
else
|
|
303
|
+
kind = Leptris::XML::FFI.leptris_xpath_result_node_kind(result_ptr, 0)
|
|
304
|
+
node = if kind == Leptris::XML::FFI::XPATH_NODE_ATTRIBUTE
|
|
305
|
+
Leptris::XML::ResultAttr.new(
|
|
306
|
+
ptr, document,
|
|
307
|
+
Leptris::XML::FFI.leptris_xpath_result_node_name(result_ptr, 0),
|
|
308
|
+
Leptris::XML::FFI.leptris_xpath_result_node_value(result_ptr, 0))
|
|
309
|
+
else
|
|
310
|
+
Leptris::XML::Node.wrap(ptr, document)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
|
|
314
|
+
node
|
|
315
|
+
else
|
|
316
|
+
wrap_xpath_result(document, result_ptr)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Shared by Searchable#xpath and Leptris::XML::XPath (compiled
|
|
321
|
+
# expressions): wraps a raw XPathResult pointer into the
|
|
322
|
+
# Ruby-typed result and frees the C handle.
|
|
323
|
+
def self.wrap_xpath_result(document, result_ptr)
|
|
324
|
+
type = Leptris::XML::FFI.leptris_xpath_result_type(result_ptr)
|
|
325
|
+
case type
|
|
326
|
+
when Leptris::XML::FFI::XPATH_NODESET
|
|
327
|
+
# TODO.perf/22: materialize AND free in C — no Ruby
|
|
328
|
+
# AutoPointer + finalizer per xpath call. Qnil means an
|
|
329
|
+
# exotic kind needs the Ruby fallback per entry; the lazy
|
|
330
|
+
# path below keeps the handle.
|
|
331
|
+
if defined?(Leptris::XML::NATIVE_FAST)
|
|
332
|
+
nodes = Leptris::XML::Native.materialize_xpath(
|
|
333
|
+
document, result_ptr.address)
|
|
334
|
+
return Leptris::XML::NodeSet.new(document, nodes) if nodes
|
|
335
|
+
end
|
|
336
|
+
Leptris::XML::NodeSet.from_result(document, result_ptr)
|
|
337
|
+
when Leptris::XML::FFI::XPATH_BOOLEAN
|
|
338
|
+
v = Leptris::XML::FFI.leptris_xpath_result_boolean(result_ptr) != 0
|
|
339
|
+
Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
|
|
340
|
+
v
|
|
341
|
+
when Leptris::XML::FFI::XPATH_NUMBER
|
|
342
|
+
v = Leptris::XML::FFI.leptris_xpath_result_number(result_ptr)
|
|
343
|
+
Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
|
|
344
|
+
v
|
|
345
|
+
when Leptris::XML::FFI::XPATH_STRING
|
|
346
|
+
str_ptr = Leptris::XML::FFI.leptris_xpath_result_string(result_ptr)
|
|
347
|
+
v = Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
348
|
+
Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
|
|
349
|
+
v
|
|
350
|
+
when Leptris::XML::FFI::XPATH_FUNCTION
|
|
351
|
+
Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
|
|
352
|
+
raise Leptris::XML::XPathError,
|
|
353
|
+
'XPath function items cannot cross the FFI boundary yet ' \
|
|
354
|
+
'(libleptris TODO 07; call them inside the expression, ' \
|
|
355
|
+
'e.g. for-each((1,2), function($n){$n+1}))'
|
|
356
|
+
else
|
|
357
|
+
Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
|
|
358
|
+
raise Leptris::XML::XPathError, "unknown xpath result type #{type}"
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Owns the SerializeOptions lifecycle and the owned-string read for
|
|
4
|
+
# document and subtree serialization/canonicalization. Document and
|
|
5
|
+
# Element expose one-line methods over this module — the options
|
|
6
|
+
# struct, the encoding anchor, the C call, and the read-and-free all
|
|
7
|
+
# live here.
|
|
8
|
+
module Leptris::XML::Serialization
|
|
9
|
+
# The default options struct (indent 0, declaration on, no encoding
|
|
10
|
+
# override) is constant: build it once and reuse its pointer on the
|
|
11
|
+
# fast path instead of allocating a fresh struct per call.
|
|
12
|
+
DEFAULT_OPTIONS, _default_anchor = begin
|
|
13
|
+
opts = Leptris::XML::FFI::SerializeOptions.new
|
|
14
|
+
opts[:indent] = 0
|
|
15
|
+
opts[:xml_declaration] = 1
|
|
16
|
+
opts[:encoding] = nil
|
|
17
|
+
[opts, nil]
|
|
18
|
+
end
|
|
19
|
+
private_constant :DEFAULT_OPTIONS
|
|
20
|
+
|
|
21
|
+
# Bound serializer entries, hoisted: Element#to_xml and the
|
|
22
|
+
# inner_html per-child loop passed FFI.method(:...) — one Method
|
|
23
|
+
# allocation per serialized child.
|
|
24
|
+
ELEMENT_SERIALIZE_INTO =
|
|
25
|
+
Leptris::XML::FFI.method(:leptris_element_serialize_into)
|
|
26
|
+
DOCUMENT_SERIALIZE_INTO =
|
|
27
|
+
Leptris::XML::FFI.method(:leptris_document_serialize_into)
|
|
28
|
+
# Public within the library: Element#to_xml and Document#to_xml
|
|
29
|
+
# pass the hoisted bound methods straight through.
|
|
30
|
+
|
|
31
|
+
# +ffi_function+ is a bound _serialize_into function. The buffer
|
|
32
|
+
# cycle lives at the FFI seam (FFI.serialize_into_string); this
|
|
33
|
+
# module owns only options selection and construction.
|
|
34
|
+
def self.to_xml(ffi_function, c_ptr, indent: 0, no_decl: false, encoding: nil)
|
|
35
|
+
# TODO.perf/04+36: the whole sized-buffer cycle in the ext —
|
|
36
|
+
# one C dispatch, no options marshaling, no Ruby buffer
|
|
37
|
+
# management; encoding rides the face (nil -> no override).
|
|
38
|
+
if defined?(Leptris::XML::NATIVE_FAST)
|
|
39
|
+
decl = !no_decl
|
|
40
|
+
return Leptris::XML::Native.fast_element_xml(
|
|
41
|
+
c_ptr.address, indent.to_i, decl, encoding) if
|
|
42
|
+
ffi_function == ELEMENT_SERIALIZE_INTO
|
|
43
|
+
return Leptris::XML::Native.fast_document_xml(
|
|
44
|
+
c_ptr.address, indent.to_i, decl, encoding) if
|
|
45
|
+
ffi_function == DOCUMENT_SERIALIZE_INTO
|
|
46
|
+
end
|
|
47
|
+
opts =
|
|
48
|
+
if indent.to_i.zero? && !no_decl && encoding.nil?
|
|
49
|
+
DEFAULT_OPTIONS
|
|
50
|
+
else
|
|
51
|
+
opts, _encoding_anchor = build_options(
|
|
52
|
+
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
53
|
+
opts
|
|
54
|
+
end
|
|
55
|
+
Leptris::XML::FFI.serialize_into_string(ffi_function, c_ptr, opts.pointer)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Default-options element serialization for inner_html's per-child
|
|
59
|
+
# loop: no kwargs, no options rebuild, no method allocation —
|
|
60
|
+
# one FFI fast-path dispatch per child.
|
|
61
|
+
def self.element_xml_default(c_ptr)
|
|
62
|
+
return Leptris::XML::Native.fast_element_xml(c_ptr.address, 0, true,
|
|
63
|
+
nil) if
|
|
64
|
+
defined?(Leptris::XML::NATIVE_FAST)
|
|
65
|
+
Leptris::XML::FFI.serialize_into_string(
|
|
66
|
+
ELEMENT_SERIALIZE_INTO, c_ptr, DEFAULT_OPTIONS.pointer)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Element serialization with expand_empty (libleptris 1.9.95,
|
|
70
|
+
# #882): empty elements emit <a></a> instead of <a/> — the
|
|
71
|
+
# inverse of libxml2's XML_SAVE_NO_EMPTY, through the element
|
|
72
|
+
# ext entry (the sized form keeps older ext allocations safe).
|
|
73
|
+
EXPAND_EMPTY_EXT = begin
|
|
74
|
+
ext = Leptris::XML::FFI::SerializeExtStruct.new
|
|
75
|
+
ext[:indent_text] = 0
|
|
76
|
+
ext[:indent_unit] = nil
|
|
77
|
+
ext[:expand_empty] = 1
|
|
78
|
+
ext
|
|
79
|
+
end
|
|
80
|
+
private_constant :EXPAND_EMPTY_EXT
|
|
81
|
+
|
|
82
|
+
def self.element_xml_expand_empty(c_ptr)
|
|
83
|
+
str_ptr = Leptris::XML::FFI.leptris_element_serialize_ext_sized(
|
|
84
|
+
c_ptr, DEFAULT_OPTIONS.pointer,
|
|
85
|
+
EXPAND_EMPTY_EXT.pointer, EXPAND_EMPTY_EXT.size)
|
|
86
|
+
Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Display-form document serialization (libleptris 1.9.9, #129):
|
|
90
|
+
# the ext struct's indent_text hands ALL whitespace to the
|
|
91
|
+
# formatter — text and mixed content indent too. Display-oriented:
|
|
92
|
+
# the output is NOT guaranteed to round-trip byte-exactly.
|
|
93
|
+
INDENT_TEXT_EXT = begin
|
|
94
|
+
ext = Leptris::XML::FFI::SerializeExtStruct.new
|
|
95
|
+
ext[:indent_text] = 1
|
|
96
|
+
ext
|
|
97
|
+
end
|
|
98
|
+
private_constant :INDENT_TEXT_EXT
|
|
99
|
+
|
|
100
|
+
def self.to_xml_display(c_ptr, indent: 0, no_decl: false, encoding: nil)
|
|
101
|
+
opts, encoding_anchor = build_options(
|
|
102
|
+
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
103
|
+
str_ptr = Leptris::XML::FFI.leptris_document_serialize_ext_sized(
|
|
104
|
+
c_ptr, opts.pointer, INDENT_TEXT_EXT.pointer, INDENT_TEXT_EXT.size)
|
|
105
|
+
Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Indent-unit serialization (libleptris 1.9.22, #633 —
|
|
109
|
+
# leptris-ruby#109): Nokogiri's indent_text semantics — the unit
|
|
110
|
+
# string replaces the default spaces, repeated `indent` times per
|
|
111
|
+
# depth level, standard layout otherwise. The engine emits ONE
|
|
112
|
+
# copy of the unit string per level (options->indent is ignored
|
|
113
|
+
# when a unit is set), so the binding multiplies unit x indent to
|
|
114
|
+
# reach Nokogiri's repeat count.
|
|
115
|
+
def self.to_xml_indent_unit(c_ptr, unit, indent: 0, no_decl: false,
|
|
116
|
+
encoding: nil)
|
|
117
|
+
opts, encoding_anchor = build_options(
|
|
118
|
+
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
119
|
+
ext = Leptris::XML::FFI::SerializeExtStruct.new
|
|
120
|
+
unit_anchor = ::FFI::MemoryPointer.from_string(
|
|
121
|
+
indent.positive? ? unit.to_s * indent : unit.to_s)
|
|
122
|
+
ext[:indent_unit] = unit_anchor
|
|
123
|
+
str_ptr = Leptris::XML::FFI.leptris_document_serialize_ext_sized(
|
|
124
|
+
c_ptr, opts.pointer, ext.pointer, ext.size)
|
|
125
|
+
Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# +ffi_function+ is a bound FFI function taking
|
|
129
|
+
# (c_ptr, version, mode, ns_ptr, flags): leptris_c14n_canonicalize_ex
|
|
130
|
+
# or leptris_c14n_canonicalize_subtree_ex.
|
|
131
|
+
def self.canonicalize(ffi_function, c_ptr, version:, mode:,
|
|
132
|
+
inclusive_namespaces: nil, with_comments: false)
|
|
133
|
+
ns_ptr, _ns_anchor = Leptris::XML::CStringArray.to_c(inclusive_namespaces)
|
|
134
|
+
str_ptr = ffi_function.call(c_ptr, version, mode, ns_ptr, with_comments ? 1 : 0)
|
|
135
|
+
Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# XML-escapes text content for inner_html: & first, then < >
|
|
139
|
+
# and CR (libxml2 emits 
 for a bare carriage return).
|
|
140
|
+
ESCAPE_TEXT = {
|
|
141
|
+
"&" => "&", "<" => "<", ">" => ">", "\r" => "
",
|
|
142
|
+
}.freeze
|
|
143
|
+
private_constant :ESCAPE_TEXT
|
|
144
|
+
ESCAPE_TEXT_RE = /[&<>\r]/.freeze
|
|
145
|
+
private_constant :ESCAPE_TEXT_RE
|
|
146
|
+
|
|
147
|
+
def self.escape_text(string)
|
|
148
|
+
string.gsub(ESCAPE_TEXT_RE, ESCAPE_TEXT)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Element-face indent unit (leptris-ruby#109 residual 2): no
|
|
152
|
+
# element-level ext-serialize entry exists yet, so the unit path
|
|
153
|
+
# copies the subtree into a fresh document via Document.copy_of
|
|
154
|
+
# (the single copy seam) and serializes that document without a
|
|
155
|
+
# declaration — identical output to the element serializer for
|
|
156
|
+
# the standard layout, with the unit and every child kind intact.
|
|
157
|
+
def self.to_xml_element_unit(element, unit, indent: 0)
|
|
158
|
+
document = Leptris::XML::Document.copy_of(element).document
|
|
159
|
+
begin
|
|
160
|
+
to_xml_indent_unit(document.c_ptr, unit,
|
|
161
|
+
indent: indent, no_decl: true)
|
|
162
|
+
ensure
|
|
163
|
+
document.free
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Builds a SerializeOptions struct. Returns [opts, encoding_anchor];
|
|
168
|
+
# the anchor must stay referenced while opts is in an FFI call.
|
|
169
|
+
def self.build_options(indent: 0, no_decl: false, encoding: nil)
|
|
170
|
+
opts = Leptris::XML::FFI::SerializeOptions.new
|
|
171
|
+
opts[:indent] = indent.to_i
|
|
172
|
+
opts[:xml_declaration] = no_decl ? 0 : 1
|
|
173
|
+
encoding_anchor = nil
|
|
174
|
+
if encoding
|
|
175
|
+
encoding_anchor = ::FFI::MemoryPointer.from_string(encoding.to_s)
|
|
176
|
+
opts[:encoding] = encoding_anchor
|
|
177
|
+
end
|
|
178
|
+
[opts, encoding_anchor]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Leptris::XML::Text < Leptris::XML::Node
|
|
4
|
+
def name; "text"; end
|
|
5
|
+
|
|
6
|
+
def content
|
|
7
|
+
return @content if memo_hit?(@content_version)
|
|
8
|
+
ensure_alive!
|
|
9
|
+
result = if @addr_reads_fast
|
|
10
|
+
Leptris::XML::Native.fast_text_content(@c_address)
|
|
11
|
+
else
|
|
12
|
+
Leptris::XML::FFI.leptris_text_node_get_content(c_ptr)
|
|
13
|
+
end
|
|
14
|
+
if @document
|
|
15
|
+
@content = result
|
|
16
|
+
@content_version = @document.version
|
|
17
|
+
end
|
|
18
|
+
result
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def content=(new_content)
|
|
22
|
+
if @native_fast
|
|
23
|
+
Leptris::XML::Native.set_binding_text(
|
|
24
|
+
@document, @c_address, new_content.to_s)
|
|
25
|
+
return new_content
|
|
26
|
+
end
|
|
27
|
+
ensure_writable!
|
|
28
|
+
Leptris::XML::FFI.check_status(
|
|
29
|
+
Leptris::XML::FFI.leptris_text_node_set_content(c_ptr, new_content.to_s))
|
|
30
|
+
new_content
|
|
31
|
+
end
|
|
32
|
+
end
|