leptris 1.9.54 → 1.9.56
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/CHANGELOG.md +87 -0
- data/README.adoc +48 -7
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +1 -1
- data/lib/leptris/xml/element.rb +3 -2
- data/lib/leptris/xml/ffi.rb +42 -5
- data/lib/leptris/xml/serialization.rb +18 -0
- data/lib/leptris/xml/xquery.rb +69 -0
- data/lib/leptris/xml.rb +11 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e63d8af535ae0bfc72372d3ed8add688494d9a05b40175b038c7d83c4cdff15
|
|
4
|
+
data.tar.gz: 110517ea3d517fb76f2f1b6adb623f6c50fbe731fd41ec821efb21e54de458d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 14054ad4ad00851c244ffc8c9493df25c6a300bfd96f4eb9b827001e75aa5e35e06e3dcf310efc08c4fe61bc3f4030121fa5d3127cc9ce2213a367546dd2ca46
|
|
7
|
+
data.tar.gz: a301bc6f767c7b8c67ea4c06c15b99cbe2fbec50d848eefb4c743d03aa0e1fa64d2eb49083a046c7859c6506f4ad5c7cde5dcc65779161ceff35821b29e45708
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,93 @@ All notable changes to Leptris will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.56] - 2026-09-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Lockstep with libleptris 1.9.66** (1.9.51 → 1.9.66 — sixteen
|
|
13
|
+
releases, the XPath 3.1 value-level + XQuery wave). Four new C
|
|
14
|
+
symbols attached (audit 259/259):
|
|
15
|
+
- **`Leptris::XML::XQuery`** (1.9.64-1.9.66): compile-once /
|
|
16
|
+
evaluate-many XQuery 1.0 core — prolog (`declare variable` /
|
|
17
|
+
`namespace` / `function local:*`), nested `for` with `at`
|
|
18
|
+
positions, `let`, `where`, stable multi-key `order by`, direct
|
|
19
|
+
and computed constructors with attribute value templates, and
|
|
20
|
+
plain XPath expression bodies. Results reuse the XPath result
|
|
21
|
+
handle; constructor results arrive as readable strings, FLWOR
|
|
22
|
+
sequences through the sequence channel. Twelve specs.
|
|
23
|
+
- **`Leptris::XML.buffer_has_nonstandard_entity?`** (1.9.62,
|
|
24
|
+
closes leptris-ruby#124 / upstream #745): the one-pass C
|
|
25
|
+
entity pre-scan — measured 7.5 µs vs 24 µs for the Ruby regex
|
|
26
|
+
it replaces on a 13 KB document (+18% vs +59% over parse).
|
|
27
|
+
- **XPath 3.1 value-level surface** (1.9.51-1.9.63), all
|
|
28
|
+
resolving standalone from `#xpath` and specced: map/array
|
|
29
|
+
constructors and accessors (`?key`, `map:get`, `array:get`,
|
|
30
|
+
sizes), `parse-json`, `json-to-xml` / `xml-to-json`,
|
|
31
|
+
`serialize(…, map { 'method': 'json' })`, postfix `?lookup`,
|
|
32
|
+
inline function items and dynamic calls, `function-lookup`,
|
|
33
|
+
`fold-left`, `for-each`, named function references.
|
|
34
|
+
- 1.9.61 (upstream #739/#744): `xs:boolean('0')` casts to
|
|
35
|
+
false, `xs:` string lexicals validated.
|
|
36
|
+
- **README**: XQuery section, value-level examples in the
|
|
37
|
+
standalone-subset block, the migration list drops "No XQuery".
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **XQuery v1 grammar gaps found on first contact, filed as
|
|
42
|
+
leptris/leptris#790** (pending specs against it): constructor as
|
|
43
|
+
the FLWOR return clause fails to parse; `where`/`at` clauses
|
|
44
|
+
inside a function-argument FLWOR fail to parse (bare for-return
|
|
45
|
+
works); `'nope' cast as xs:integer` NaNs instead of erroring, so
|
|
46
|
+
`try/catch` never reaches its catch arm.
|
|
47
|
+
|
|
48
|
+
## [1.9.55] - 2026-09-02
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **Lockstep with libleptris 1.9.50**: XPath 2.0 type operators —
|
|
53
|
+
`instance of`, `castable as`, `cast as`, `treat as` with
|
|
54
|
+
SequenceType v1 (`node()`, `item()`, occurrence `*`/`+`) —
|
|
55
|
+
resolving standalone from `#xpath`, specced against the Saxon
|
|
56
|
+
ground truth including the falsifiable negative
|
|
57
|
+
(`'x' castable as xs:integer` = false, `1.9 cast as xs:integer`
|
|
58
|
+
= 1). No new C surface (audit 255/255).
|
|
59
|
+
- **README**: the standalone-subset example block grows the type
|
|
60
|
+
operators; the #683 gap list drops sequence types (still out:
|
|
61
|
+
value comparators, quantifiers, `intersect`/`except`, string
|
|
62
|
+
templates, XQuery).
|
|
63
|
+
|
|
64
|
+
### Improved
|
|
65
|
+
|
|
66
|
+
- **Serialize + inner_html, one dispatch instead of two**: the
|
|
67
|
+
`_serialize_into` contract is snprintf-like (the return is
|
|
68
|
+
always the bytes needed incl. NUL; the copy happens only when
|
|
69
|
+
the buffer fits), so `serialize_into_string` now fills the
|
|
70
|
+
grow-only byte scratch opportunistically and probes the size
|
|
71
|
+
only when a child outgrows it — previously every call paid a
|
|
72
|
+
size-probe dispatch before the fill. The bound serializer
|
|
73
|
+
methods are hoisted to constants (`Element#to_xml` and
|
|
74
|
+
`Document#to_xml` allocated a Method object per call), and
|
|
75
|
+
`inner_html` serializes element children through a dedicated
|
|
76
|
+
default-options fast path (no kwargs, no options rebuild).
|
|
77
|
+
Measured on a 1.0 MB / 42k-element document: document serialize
|
|
78
|
+
1.1x → **3.2x faster** than Nokogiri 1.19.4; `inner_html` 0.8x →
|
|
79
|
+
**4.4x faster** (4.2 ms → 1.1 ms per 200 elements). Parse (11x),
|
|
80
|
+
attr reads (9x), text aggregation (11x), and element_children
|
|
81
|
+
walks (parity) all hold.
|
|
82
|
+
|
|
83
|
+
### Fixed
|
|
84
|
+
|
|
85
|
+
- **#120 closed with a guard**: text ampersands escape on
|
|
86
|
+
serialize — byte-parity specs (parse-and-serialize round-trip
|
|
87
|
+
plus a `content=` write path) now run on every CI leg including
|
|
88
|
+
both Linuxes, so a platform-build serializer divergence fails CI
|
|
89
|
+
instead of shipping. The 1.9.50 Linux build was superseded per
|
|
90
|
+
the verification on the issue.
|
|
91
|
+
- **#124 filed upstream** (leptris/leptris#745): the C-side
|
|
92
|
+
non-standard-entity pre-scan entry — the moxml +61% parse → ~+2%
|
|
93
|
+
case. The binding attaches and wraps it the day it lands.
|
|
94
|
+
|
|
8
95
|
## [1.9.54] - 2026-09-02
|
|
9
96
|
|
|
10
97
|
### Changed
|
data/README.adoc
CHANGED
|
@@ -9,9 +9,9 @@ A Nokogiri-compatible Ruby binding for
|
|
|
9
9
|
https://github.com/leptris/leptris[libleptris], a pure-C99 XML 1.0 parser
|
|
10
10
|
with full https://www.w3.org/TR/1999/REC-xpath-19991116/[XPath 1.0],
|
|
11
11
|
XML Namespaces 1.0, SAX, and C14N (1.0 / 1.1 / Exclusive), plus
|
|
12
|
-
XSLT 1.0–3.0 transforms
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
XSLT 1.0–3.0 transforms, an XQuery 1.0 core face, and a growing
|
|
13
|
+
XPath 2/3.1 expression subset available standalone from `#xpath`
|
|
14
|
+
itself.
|
|
15
15
|
|
|
16
16
|
The C DOM is the single source of truth — Ruby objects are thin FFI
|
|
17
17
|
handles over the C pointers, so every Ruby method maps to one FFI call.
|
|
@@ -239,6 +239,14 @@ doc.xpath("count(1 to 4)") # => 4.0
|
|
|
239
239
|
doc.xpath("//book => count()") # 3.1 arrow
|
|
240
240
|
doc.xpath("//title ! string(.)") # 3.1 simple map
|
|
241
241
|
doc.xpath("'v' || count(//book)") # 3.1 concat
|
|
242
|
+
doc.xpath("'42' castable as xs:integer") # 2.0 type ops
|
|
243
|
+
doc.xpath("1.9 cast as xs:integer") # => 1.0 (truncates)
|
|
244
|
+
doc.xpath("//title instance of node()+") # 2.0 sequence types
|
|
245
|
+
doc.xpath("map { 'b': 'beta' }?b") # 3.1 maps
|
|
246
|
+
doc.xpath("[10, 20, 30]?2") # 3.1 arrays
|
|
247
|
+
doc.xpath(%q{serialize(parse-json('{"b":"beta"}'), map { 'method': 'json' })})
|
|
248
|
+
doc.xpath("function($x) { $x + 1 }(41)") # 3.1 function items
|
|
249
|
+
doc.xpath("fold-left(1 to 4, 0, function($a, $b) { $a + $b })")
|
|
242
250
|
----
|
|
243
251
|
|
|
244
252
|
Two caveats: `for … return` sequences come back as opaque result
|
|
@@ -247,8 +255,8 @@ nodes — consume them through an aggregate (`count(...)`,
|
|
|
247
255
|
items; and XQuery-only syntax (`order by`, `try/catch`, map
|
|
248
256
|
constructors) raises `XPathError` rather than evaluating silently
|
|
249
257
|
wrong. The remaining grammar gaps — value comparators (`eq`),
|
|
250
|
-
quantifiers,
|
|
251
|
-
|
|
258
|
+
quantifiers, `intersect`/`except`, string templates — and XQuery
|
|
259
|
+
itself are
|
|
252
260
|
https://github.com/leptris/leptris/issues/683[tracked upstream].
|
|
253
261
|
|
|
254
262
|
=== Supported CSS selectors
|
|
@@ -379,6 +387,39 @@ dispositions drop unmatched subtrees in the built-in initial
|
|
|
379
387
|
descent (https://github.com/leptris/leptris/issues/705[#705]) —
|
|
380
388
|
the other four dispositions are spec-correct. XQuery has no
|
|
381
389
|
entry point yet.
|
|
390
|
+
== XQuery
|
|
391
|
+
|
|
392
|
+
XQuery 1.0 core through `Leptris::XML::XQuery` (compile once,
|
|
393
|
+
evaluate many):
|
|
394
|
+
|
|
395
|
+
[source,ruby]
|
|
396
|
+
----
|
|
397
|
+
query = Leptris::XML::XQuery.parse(<<~XQ)
|
|
398
|
+
declare variable $min := 3;
|
|
399
|
+
for $i in //item
|
|
400
|
+
where number($i/@qty) > $min
|
|
401
|
+
order by $i/@qty descending
|
|
402
|
+
return <big>{$i/name/text()}</big>
|
|
403
|
+
XQ
|
|
404
|
+
query.eval(doc) # plain expressions keep their XPath result type;
|
|
405
|
+
# FLWOR results arrive as the sequence channel —
|
|
406
|
+
# read them through an aggregate until the engine
|
|
407
|
+
# materializes readable sequence items
|
|
408
|
+
----
|
|
409
|
+
|
|
410
|
+
Supported: the prolog (`declare variable` / `declare namespace` /
|
|
411
|
+
`declare function local:*`), nested `for` with `at` positions,
|
|
412
|
+
`let`, `where`, stable multi-key `order by`, direct and computed
|
|
413
|
+
constructors with attribute value templates, and plain XPath
|
|
414
|
+
expression bodies. Known grammar gaps are
|
|
415
|
+
https://github.com/leptris/leptris/issues/790[tracked upstream].
|
|
416
|
+
|
|
417
|
+
`Leptris::XML.buffer_has_nonstandard_entity?(string)` is a
|
|
418
|
+
one-pass C pre-scan (for adapter layers): true when the buffer
|
|
419
|
+
contains a named entity outside the five predefined ones (or
|
|
420
|
+
numeric) — ~3x faster than the equivalent Ruby regex and no
|
|
421
|
+
false positives on bare `&`.
|
|
422
|
+
|
|
382
423
|
`Document#save(path, **opts)` :: serialize to a file.
|
|
383
424
|
`Document#canonicalize(version, inclusive_ns, with_comments:, exclusive:, mode:)` (alias `#c14n`) :: canonical XML.
|
|
384
425
|
`Element#canonicalize(...)` :: subtree canonicalization.
|
|
@@ -545,8 +586,8 @@ Notable differences:
|
|
|
545
586
|
past Document disposal will see the error; silence-replace-UAF patterns
|
|
546
587
|
from Nokogiri do not apply.
|
|
547
588
|
* No `Nokogiri::HTML` or `Nokogiri::CSS` parser. Leptris is XML-only.
|
|
548
|
-
* No
|
|
549
|
-
|
|
589
|
+
* No RelaxNG / DTD validation, no schema caching (XSLT 1.0–3.0
|
|
590
|
+
and an XQuery 1.0 core are supported — see above).
|
|
550
591
|
* No built-in JRuby / TruffleRuby support — only CRuby via `ffi`.
|
|
551
592
|
|
|
552
593
|
== Performance
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.66"
|
|
12
12
|
|
|
13
13
|
CMAKE_FLAGS = %w[
|
|
14
14
|
-DCMAKE_BUILD_TYPE=Release
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -285,7 +285,7 @@ class Leptris::XML::Document
|
|
|
285
285
|
@c_ptr, indent: indent, no_decl: no_decl, encoding: encoding)
|
|
286
286
|
end
|
|
287
287
|
Leptris::XML::Serialization.to_xml(
|
|
288
|
-
Leptris::XML::
|
|
288
|
+
Leptris::XML::Serialization::DOCUMENT_SERIALIZE_INTO, @c_ptr,
|
|
289
289
|
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
290
290
|
end
|
|
291
291
|
alias_method :to_s, :to_xml
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -349,7 +349,8 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
349
349
|
def inner_html
|
|
350
350
|
children.map do |child|
|
|
351
351
|
case child
|
|
352
|
-
when Leptris::XML::Element
|
|
352
|
+
when Leptris::XML::Element
|
|
353
|
+
Leptris::XML::Serialization.element_xml_default(child.c_ptr)
|
|
353
354
|
when Leptris::XML::CDATA # before Text: CDATA subclasses it
|
|
354
355
|
"<![CDATA[#{child.content}]]>"
|
|
355
356
|
when Leptris::XML::Text
|
|
@@ -377,7 +378,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
377
378
|
return Leptris::XML::Serialization.to_xml_element_unit(
|
|
378
379
|
self, indent_text, indent: indent) if indent_text.is_a?(String)
|
|
379
380
|
Leptris::XML::Serialization.to_xml(
|
|
380
|
-
Leptris::XML::
|
|
381
|
+
Leptris::XML::Serialization::ELEMENT_SERIALIZE_INTO, @c_ptr,
|
|
381
382
|
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
382
383
|
end
|
|
383
384
|
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -43,6 +43,7 @@ module Leptris
|
|
|
43
43
|
typedef :pointer, :leptris_xpath_ns_set
|
|
44
44
|
typedef :pointer, :leptris_xpath_compiled
|
|
45
45
|
typedef :pointer, :leptris_xslt
|
|
46
|
+
typedef :pointer, :leptris_xquery
|
|
46
47
|
typedef :pointer, :leptris_sax_recorder
|
|
47
48
|
typedef :pointer, :leptris_pull_parser
|
|
48
49
|
typedef :pointer, :leptris_iterparse
|
|
@@ -671,6 +672,25 @@ module Leptris
|
|
|
671
672
|
attach_function :leptris_document_save_file,
|
|
672
673
|
[:leptris_document, :string, :pointer], :leptris_status
|
|
673
674
|
# XSLT 1.0 engine (libleptris 1.9.1): compile once, apply many.
|
|
675
|
+
# XQuery 1.0 core (libleptris 1.9.64-1.9.66): orchestration
|
|
676
|
+
# over the XPath engine — prolog (declare variable/namespace/
|
|
677
|
+
# function local:*), FLWOR, computed and direct constructors.
|
|
678
|
+
# Results reuse the XPath result handle (free with
|
|
679
|
+
# leptris_xpath_result_free); context_node NULL = document.
|
|
680
|
+
attach_function :leptris_xquery_parse,
|
|
681
|
+
[:string, :size_t], :leptris_xquery
|
|
682
|
+
attach_function :leptris_xquery_eval,
|
|
683
|
+
[:leptris_xquery, :leptris_document, :leptris_element],
|
|
684
|
+
:leptris_xpath_result
|
|
685
|
+
attach_function :leptris_xquery_free, [:leptris_xquery], :void
|
|
686
|
+
|
|
687
|
+
# One-pass entity pre-scan (libleptris 1.9.62, leptris#745):
|
|
688
|
+
# 1 when the buffer holds a named entity that is not one of
|
|
689
|
+
# the five predefined XML entities (nor numeric) — the moxml
|
|
690
|
+
# pre-scan at C speed instead of a Ruby regex.
|
|
691
|
+
attach_function :leptris_str_has_nonstandard_entity,
|
|
692
|
+
[:string, :size_t], :int
|
|
693
|
+
|
|
674
694
|
attach_function :leptris_xslt_parse,
|
|
675
695
|
[:string, :size_t], :leptris_xslt
|
|
676
696
|
attach_function :leptris_xslt_parse_file,
|
|
@@ -894,12 +914,29 @@ module Leptris
|
|
|
894
914
|
# leptris_element_serialize_into. The buffer holds the
|
|
895
915
|
# serialization + NUL and XML output is NUL-free, so the
|
|
896
916
|
# bounded read is exact.
|
|
917
|
+
# snprintf contract (leptris.h #535): the return is ALWAYS the
|
|
918
|
+
# bytes needed incl. the NUL; the copy happens only when the
|
|
919
|
+
# buffer fits. So the size probe is paid only when the child
|
|
920
|
+
# outgrows the grow-only scratch — one dispatch per call on
|
|
921
|
+
# the fast path instead of two (inner_html serializes each
|
|
922
|
+
# child; the probe was most of the per-child FFI cost).
|
|
923
|
+
SERIALIZE_INITIAL = 256
|
|
924
|
+
private_constant :SERIALIZE_INITIAL
|
|
925
|
+
|
|
897
926
|
def self.serialize_into_string(ffi_function, c_ptr, options)
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
buffer
|
|
901
|
-
|
|
902
|
-
|
|
927
|
+
scratch = (Thread.current[:leptris_scratch] ||= {})
|
|
928
|
+
buffer = scratch[:bytes]
|
|
929
|
+
if buffer.nil?
|
|
930
|
+
buffer = scratch[:bytes] =
|
|
931
|
+
::FFI::MemoryPointer.new(:char, SERIALIZE_INITIAL)
|
|
932
|
+
end
|
|
933
|
+
needed = ffi_function.call(c_ptr, buffer, buffer.size, nil, options)
|
|
934
|
+
if needed > buffer.size
|
|
935
|
+
buffer = scratch_bytes(needed)
|
|
936
|
+
needed = ffi_function.call(c_ptr, buffer, buffer.size, nil, options)
|
|
937
|
+
end
|
|
938
|
+
return "" if needed <= 1
|
|
939
|
+
buffer.read_string(needed - 1).force_encoding(Encoding::UTF_8)
|
|
903
940
|
end
|
|
904
941
|
|
|
905
942
|
# Byte scratch for serialize-into cycles: the size+fill pair
|
|
@@ -18,6 +18,16 @@ module Leptris::XML::Serialization
|
|
|
18
18
|
end
|
|
19
19
|
private_constant :DEFAULT_OPTIONS
|
|
20
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
|
+
|
|
21
31
|
# +ffi_function+ is a bound _serialize_into function. The buffer
|
|
22
32
|
# cycle lives at the FFI seam (FFI.serialize_into_string); this
|
|
23
33
|
# module owns only options selection and construction.
|
|
@@ -33,6 +43,14 @@ module Leptris::XML::Serialization
|
|
|
33
43
|
Leptris::XML::FFI.serialize_into_string(ffi_function, c_ptr, opts.pointer)
|
|
34
44
|
end
|
|
35
45
|
|
|
46
|
+
# Default-options element serialization for inner_html's per-child
|
|
47
|
+
# loop: no kwargs, no options rebuild, no method allocation —
|
|
48
|
+
# one FFI fast-path dispatch per child.
|
|
49
|
+
def self.element_xml_default(c_ptr)
|
|
50
|
+
Leptris::XML::FFI.serialize_into_string(
|
|
51
|
+
ELEMENT_SERIALIZE_INTO, c_ptr, DEFAULT_OPTIONS.pointer)
|
|
52
|
+
end
|
|
53
|
+
|
|
36
54
|
# Display-form document serialization (libleptris 1.9.9, #129):
|
|
37
55
|
# the ext struct's indent_text hands ALL whitespace to the
|
|
38
56
|
# formatter — text and mixed content indent too. Display-oriented:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
# Compiled XQuery 1.0 query (libleptris 1.9.64+): parse once,
|
|
6
|
+
# evaluate many times against any document. The engine layer
|
|
7
|
+
# orchestrates the XPath engine — the prolog (declare variable /
|
|
8
|
+
# namespace / function local:*) binds into the evaluation context,
|
|
9
|
+
# FLWOR clauses run as a tuple stream, and results reuse the XPath
|
|
10
|
+
# result handle.
|
|
11
|
+
#
|
|
12
|
+
# query = Leptris::XML::XQuery.parse(<<~XQ)
|
|
13
|
+
# for $i in //item
|
|
14
|
+
# where $i/@qty > 3
|
|
15
|
+
# order by $i/@qty descending
|
|
16
|
+
# return <big>{$i/name/text()}</big>
|
|
17
|
+
# XQ
|
|
18
|
+
# query.eval(doc) # => Leptris::XML::NodeSet
|
|
19
|
+
#
|
|
20
|
+
class Leptris::XML::XQuery
|
|
21
|
+
class Handle < ::FFI::AutoPointer
|
|
22
|
+
def self.release(ptr)
|
|
23
|
+
Leptris::XML::FFI.leptris_xquery_free(ptr)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.parse(query)
|
|
28
|
+
query = query.to_s
|
|
29
|
+
raw = Leptris::XML::FFI.leptris_xquery_parse(query, query.bytesize)
|
|
30
|
+
if raw.null?
|
|
31
|
+
raise Leptris::XML::XPathError,
|
|
32
|
+
"query parse failed: #{Leptris::XML::FFI.leptris_last_error}"
|
|
33
|
+
end
|
|
34
|
+
new(query, Handle.new(raw))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
attr_reader :query
|
|
38
|
+
|
|
39
|
+
def initialize(query, handle)
|
|
40
|
+
@query = query
|
|
41
|
+
@handle = handle
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Evaluates against +doc_or_element+. Plain-expression bodies keep
|
|
45
|
+
# their XPath result type (NodeSet / true/false / Float / String);
|
|
46
|
+
# FLWOR results arrive as the sequence channel — read them through
|
|
47
|
+
# an aggregate until the engine materializes readable sequence
|
|
48
|
+
# items (same caveat as the XPath for-return subset).
|
|
49
|
+
def eval(doc_or_element)
|
|
50
|
+
case doc_or_element
|
|
51
|
+
when Leptris::XML::Document
|
|
52
|
+
document = doc_or_element
|
|
53
|
+
context = nil
|
|
54
|
+
when Leptris::XML::Element
|
|
55
|
+
document = doc_or_element.document
|
|
56
|
+
context = doc_or_element.c_ptr
|
|
57
|
+
else
|
|
58
|
+
raise ArgumentError,
|
|
59
|
+
"expected a Leptris::XML::Document or Element, got #{doc_or_element.class}"
|
|
60
|
+
end
|
|
61
|
+
result_ptr = Leptris::XML::FFI.leptris_xquery_eval(
|
|
62
|
+
@handle, document.c_ptr, context)
|
|
63
|
+
if result_ptr.null?
|
|
64
|
+
raise Leptris::XML::XPathError,
|
|
65
|
+
Leptris::XML::FFI.leptris_last_error.to_s
|
|
66
|
+
end
|
|
67
|
+
Leptris::XML::Searchable.wrap_xpath_result(document, result_ptr)
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -23,6 +23,7 @@ module Leptris
|
|
|
23
23
|
autoload :SAX, "leptris/xml/sax"
|
|
24
24
|
autoload :XPath, "leptris/xml/xpath"
|
|
25
25
|
autoload :XSLT, "leptris/xml/xslt"
|
|
26
|
+
autoload :XQuery, "leptris/xml/xquery"
|
|
26
27
|
autoload :Pull, "leptris/xml/pull"
|
|
27
28
|
autoload :Iterparse, "leptris/xml/iterparse"
|
|
28
29
|
|
|
@@ -33,6 +34,16 @@ module Leptris
|
|
|
33
34
|
recover: recover)
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
# Does +string+ contain a named entity reference that is not
|
|
38
|
+
# one of the five predefined XML entities (nor numeric)? One C
|
|
39
|
+
# pass (libleptris 1.9.62) — downstream adapters use it to skip
|
|
40
|
+
# whole-buffer rewrites when a document is already clean.
|
|
41
|
+
def self.buffer_has_nonstandard_entity?(string)
|
|
42
|
+
string = string.to_s
|
|
43
|
+
Leptris::XML::FFI.leptris_str_has_nonstandard_entity(
|
|
44
|
+
string, string.bytesize) != 0
|
|
45
|
+
end
|
|
46
|
+
|
|
36
47
|
def self.parse_file(path)
|
|
37
48
|
Document.parse_file(path)
|
|
38
49
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.56
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -105,6 +105,7 @@ files:
|
|
|
105
105
|
- lib/leptris/xml/serialization.rb
|
|
106
106
|
- lib/leptris/xml/text.rb
|
|
107
107
|
- lib/leptris/xml/xpath.rb
|
|
108
|
+
- lib/leptris/xml/xquery.rb
|
|
108
109
|
- lib/leptris/xml/xslt.rb
|
|
109
110
|
homepage: https://github.com/leptris/leptris-ruby
|
|
110
111
|
licenses:
|