leptris 1.9.121.0 → 1.9.144.0
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.
Potentially problematic release.
This version of leptris might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/diff.rb +74 -0
- data/lib/leptris/xml/element.rb +24 -0
- data/lib/leptris/xml/ffi.rb +66 -0
- data/lib/leptris/xml/schematron.rb +88 -0
- data/lib/leptris/xml/searchable.rb +36 -0
- data/lib/leptris/xml/xquery.rb +21 -3
- data/lib/leptris/xml.rb +10 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce24363303ca7b04d2cc2862985a5db2a2358e03910cac76f1fc21c417a84ed5
|
|
4
|
+
data.tar.gz: 97f81edbb20997881c632c369eda7c2d2cb219582cd9e19cf8b0e458855c4a4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06622c78d7f4ed9cfcf8aae54f696d237f819d5d33f0875f2b3145e63b624f837c45fb7b160dc2e5c11de23ea95db7cfffbdc49ea04d5f0087f7e04cfb57fb23
|
|
7
|
+
data.tar.gz: 23e5b135a9146e96d7d452255e6ff7d77d875285c687bd58af6c6276486b5c161e1c82911ca90a419f72422eae7cb2f538e609451d2b9882ce7bb31464dbb95b
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,58 @@ 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.144.0] - 2026-09-12
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Lockstep with libleptris 1.9.144** (1.9.121 → 1.9.144; audit
|
|
13
|
+
270 → 289 after attaching 19 new symbols).
|
|
14
|
+
- **`Leptris::XML::Schematron`** — ISO Schematron validation
|
|
15
|
+
(libleptris 1.9.127/1.9.128; schematron-conformance corpus
|
|
16
|
+
50/50): `Schematron.parse(schema_xml, phase: nil)` / `.parse_file`
|
|
17
|
+
compile once, then `#valid?(doc)` answers the boolean and
|
|
18
|
+
`#validate(doc)` returns the SVRL report as a queryable
|
|
19
|
+
`Document` (failed-assert / successful-report with @location).
|
|
20
|
+
- **`Leptris::XML::Diff`** — native tree diff (libleptris
|
|
21
|
+
1.9.127, namespace-aware 1.9.129): `Leptris::XML.diff(a, b,
|
|
22
|
+
ignore_ws: false)` → `#ops` (`{type:, name:, path:, before:,
|
|
23
|
+
after:}`; insert/delete/update_text/update_attr with
|
|
24
|
+
positional `/name[i]` paths) and `#to_s` (the serialized op
|
|
25
|
+
list); `Diff.identical?(a, b, ignore_ws:)` answers the boolean
|
|
26
|
+
fast path.
|
|
27
|
+
- **XQuery external variables** (libleptris 1.9.133) —
|
|
28
|
+
`XQuery#eval(doc, params)` binds `declare variable $x external`
|
|
29
|
+
through `leptris_xquery_eval_params` with QT3 `<param select>`
|
|
30
|
+
semantics (each value is an XPath expression in an empty
|
|
31
|
+
context); a binding overrides a default initializer, and an
|
|
32
|
+
empty params hash takes the same lane — the engine decides
|
|
33
|
+
between the default and failure.
|
|
34
|
+
- **`Element#expanded_name`** (libleptris 1.9.144) —
|
|
35
|
+
`{local:, prefix:, namespace_uri:}` in one C call.
|
|
36
|
+
- **`#xpath_versioned(expression, version)`** — version-pinned
|
|
37
|
+
evaluation via `leptris_xpath_eval_versioned`: `:xpath10`
|
|
38
|
+
keeps the strict 1.0 surface (3.x syntax raises), `:xpath31`
|
|
39
|
+
evaluates the full grammar. A separate method by design —
|
|
40
|
+
`#xpath`'s trailing-hash argument is the namespace-binding
|
|
41
|
+
channel.
|
|
42
|
+
- **`Node#digest(drop_ws:)`** — the #869 content-defined Merkle
|
|
43
|
+
digest behind diff pruning.
|
|
44
|
+
- **Serialization: `to_xml(..., expand_empty:)`** — the
|
|
45
|
+
`<e></e>` vs `<e/>` switch at the C seam.
|
|
46
|
+
- Engine arc: the W3C QT3 suite adoption (fn/substring 45/45,
|
|
47
|
+
fn/contains 41/41, fn/starts-with 43/43, fn/ends-with 34/34,
|
|
48
|
+
fn/concat 80/80 — int64 integer fidelity keeps
|
|
49
|
+
`xs:integer('999999999999999999')` exact) and the WHATWG HTML
|
|
50
|
+
completion arc (frameset, in-head-noscript, character
|
|
51
|
+
references, comment close forms, **full adoption agency**
|
|
52
|
+
1.9.137 — `"<h1>a<h2>b</h1>c</h2>"` now yields
|
|
53
|
+
`<h1>a</h1><h2>b</h2>c` per the spec — initial-mode comment
|
|
54
|
+
placement, in-head comments, foreign-content integration
|
|
55
|
+
scopes, in-table clear-stack, script-data escapes, RCDATA/
|
|
56
|
+
rawtext, numeric-reference end states; html5lib floor
|
|
57
|
+
928 → 933). #965: relational comparisons no longer swap
|
|
58
|
+
operands (`10 >= @n` compares as written).
|
|
59
|
+
|
|
8
60
|
## [1.9.115.0] - 2026-09-09
|
|
9
61
|
|
|
10
62
|
### Changed
|
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.144"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
data/lib/leptris/version.rb
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
# Tree diff between two documents (libleptris >= 1.9.126):
|
|
6
|
+
# equal subtrees prune in O(1) via the #869 content-defined Merkle
|
|
7
|
+
# digest; diverging regions align through an LCS on child digests,
|
|
8
|
+
# then same-named elements recurse. Attribute add/remove are
|
|
9
|
+
# UPDATE_ATTR ops with an empty side.
|
|
10
|
+
#
|
|
11
|
+
# diff = Leptris::XML.diff(before_doc, after_doc)
|
|
12
|
+
# diff.ops # => [{type: :update_text, name: "price",
|
|
13
|
+
# # path: "/r/item[2]/price", before: "3", after: "4"}, ...]
|
|
14
|
+
# diff.to_s # => serialized op list
|
|
15
|
+
# Leptris::XML.diff(a, b, ignore_ws: true) # ws-only text absent
|
|
16
|
+
#
|
|
17
|
+
class Leptris::XML::Diff
|
|
18
|
+
class Handle < ::FFI::AutoPointer
|
|
19
|
+
def self.release(ptr)
|
|
20
|
+
Leptris::XML::FFI.leptris_diff_free(ptr)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
OP_TYPES = {
|
|
25
|
+
1 => :insert,
|
|
26
|
+
2 => :delete,
|
|
27
|
+
3 => :update_text,
|
|
28
|
+
4 => :update_attr,
|
|
29
|
+
}.freeze
|
|
30
|
+
private_constant :OP_TYPES
|
|
31
|
+
|
|
32
|
+
def self.compute(document_a, document_b, ignore_ws: false)
|
|
33
|
+
flags = ignore_ws ? 1 : 0
|
|
34
|
+
raw = Leptris::XML::FFI.leptris_diff(
|
|
35
|
+
document_a.c_ptr, document_b.c_ptr, flags, nil)
|
|
36
|
+
if raw.null?
|
|
37
|
+
raise Leptris::XML::Error,
|
|
38
|
+
"leptris_diff failed: #{Leptris::XML::FFI.leptris_last_error}"
|
|
39
|
+
end
|
|
40
|
+
new(Handle.new(raw))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The boolean fast path canon-style consumers want: are the two
|
|
44
|
+
# trees structurally identical? Zero ops == identical — the
|
|
45
|
+
# digest pruning makes the common equal case O(1) per subtree.
|
|
46
|
+
def self.identical?(document_a, document_b, ignore_ws: false)
|
|
47
|
+
compute(document_a, document_b, ignore_ws: ignore_ws).ops.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(handle)
|
|
51
|
+
@handle = handle
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The op list in document order: {type:, name:, path:, before:,
|
|
55
|
+
# after:} — paths are root-relative ("/r/i[2]"); before/after
|
|
56
|
+
# carry payloads (attribute values / text) where the op has them.
|
|
57
|
+
def ops
|
|
58
|
+
count = Leptris::XML::FFI.leptris_diff_op_count(@handle)
|
|
59
|
+
Array.new(count) do |i|
|
|
60
|
+
{
|
|
61
|
+
type: OP_TYPES[Leptris::XML::FFI.leptris_diff_op_type(@handle, i)],
|
|
62
|
+
name: Leptris::XML::FFI.leptris_diff_op_name(@handle, i),
|
|
63
|
+
path: Leptris::XML::FFI.leptris_diff_op_path(@handle, i),
|
|
64
|
+
before: Leptris::XML::FFI.leptris_diff_op_before(@handle, i),
|
|
65
|
+
after: Leptris::XML::FFI.leptris_diff_op_after(@handle, i),
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_s
|
|
71
|
+
str_ptr = Leptris::XML::FFI.leptris_diff_serialize(@handle)
|
|
72
|
+
Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -341,6 +341,30 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
341
341
|
uri
|
|
342
342
|
end
|
|
343
343
|
|
|
344
|
+
# The local name, prefix, and resolved namespace URI in ONE
|
|
345
|
+
# call (libleptris 1.9.144) — the adapter fan-out read.
|
|
346
|
+
# Equivalent to #name + #prefix + #namespace, single entry.
|
|
347
|
+
def expanded_name
|
|
348
|
+
ensure_alive!
|
|
349
|
+
local = ::FFI::MemoryPointer.new(:pointer)
|
|
350
|
+
prefix = ::FFI::MemoryPointer.new(:pointer)
|
|
351
|
+
uri = ::FFI::MemoryPointer.new(:pointer)
|
|
352
|
+
Leptris::XML::FFI.leptris_element_expanded_name(
|
|
353
|
+
@c_ptr, local, prefix, uri)
|
|
354
|
+
local_ptr = local.read_pointer
|
|
355
|
+
prefix_ptr = prefix.read_pointer
|
|
356
|
+
uri_ptr = uri.read_pointer
|
|
357
|
+
result = {
|
|
358
|
+
local: local_ptr.read_string,
|
|
359
|
+
prefix: prefix_ptr.null? ? nil : prefix_ptr.read_string,
|
|
360
|
+
namespace_uri: uri_ptr.null? ? nil : uri_ptr.read_string,
|
|
361
|
+
}
|
|
362
|
+
local.free
|
|
363
|
+
prefix.free
|
|
364
|
+
uri.free
|
|
365
|
+
result
|
|
366
|
+
end
|
|
367
|
+
|
|
344
368
|
def namespace_definitions
|
|
345
369
|
return @namespace_definitions if memo_hit?(@namespace_definitions_version)
|
|
346
370
|
ensure_alive!
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -64,6 +64,8 @@ module Leptris
|
|
|
64
64
|
typedef :pointer, :leptris_relaxng
|
|
65
65
|
typedef :pointer, :leptris_xslt
|
|
66
66
|
typedef :pointer, :leptris_xquery
|
|
67
|
+
typedef :pointer, :leptris_schematron
|
|
68
|
+
typedef :pointer, :leptris_diff
|
|
67
69
|
typedef :pointer, :leptris_sax_recorder
|
|
68
70
|
typedef :pointer, :leptris_pull_parser
|
|
69
71
|
typedef :pointer, :leptris_iterparse
|
|
@@ -744,8 +746,72 @@ attach_function :leptris_parse_string,
|
|
|
744
746
|
attach_function :leptris_xquery_eval,
|
|
745
747
|
[:leptris_xquery, :leptris_document, :leptris_element],
|
|
746
748
|
:leptris_xpath_result
|
|
749
|
+
# External-variable XQuery evaluation (libleptris 1.9.133):
|
|
750
|
+
# names[i]/selects[i] bind `declare variable $name external`
|
|
751
|
+
# — each select is an XPath expression in an empty context
|
|
752
|
+
# (QT3 <param select> semantics), overriding any default
|
|
753
|
+
# initializer. An external with neither binding nor default
|
|
754
|
+
# fails evaluation.
|
|
755
|
+
attach_function :leptris_xquery_eval_params,
|
|
756
|
+
[:leptris_xquery, :leptris_document, :leptris_element,
|
|
757
|
+
:pointer, :pointer, :size_t], :leptris_xpath_result
|
|
747
758
|
attach_function :leptris_xquery_free, [:leptris_xquery], :void
|
|
748
759
|
|
|
760
|
+
# Schematron validation (libleptris >= 1.9.126 family):
|
|
761
|
+
# parse once (optionally selecting a PHASE), validate any
|
|
762
|
+
# number of documents — #validate returns the SVRL report as
|
|
763
|
+
# a Document; #valid? answers through the SVRL root name.
|
|
764
|
+
attach_function :leptris_schematron_parse,
|
|
765
|
+
[:string, :size_t, :pointer], :leptris_schematron
|
|
766
|
+
attach_function :leptris_schematron_parse_file,
|
|
767
|
+
[:string, :pointer], :leptris_schematron
|
|
768
|
+
attach_function :leptris_schematron_parse_phase,
|
|
769
|
+
[:string, :size_t, :string, :pointer], :leptris_schematron
|
|
770
|
+
attach_function :leptris_schematron_free,
|
|
771
|
+
[:leptris_schematron], :void
|
|
772
|
+
attach_function :leptris_schematron_valid,
|
|
773
|
+
[:leptris_schematron, :leptris_document], :int
|
|
774
|
+
attach_function :leptris_schematron_validate,
|
|
775
|
+
[:leptris_schematron, :leptris_document], :leptris_document
|
|
776
|
+
attach_function :leptris_schematron_error,
|
|
777
|
+
[:leptris_schematron], :string
|
|
778
|
+
|
|
779
|
+
# Tree diff (libleptris >= 1.9.126 family): equal subtrees
|
|
780
|
+
# prune in O(1) by the #869 Merkle digest; diverging regions
|
|
781
|
+
# align via LCS on child digests, then recurse by name.
|
|
782
|
+
attach_function :leptris_diff,
|
|
783
|
+
[:leptris_document, :leptris_document, :int, :pointer],
|
|
784
|
+
:leptris_diff
|
|
785
|
+
attach_function :leptris_diff_free, [:leptris_diff], :void
|
|
786
|
+
attach_function :leptris_diff_op_count, [:leptris_diff], :size_t
|
|
787
|
+
attach_function :leptris_diff_op_type,
|
|
788
|
+
[:leptris_diff, :size_t], :int
|
|
789
|
+
attach_function :leptris_diff_op_name,
|
|
790
|
+
[:leptris_diff, :size_t], :string
|
|
791
|
+
attach_function :leptris_diff_op_path,
|
|
792
|
+
[:leptris_diff, :size_t], :string
|
|
793
|
+
attach_function :leptris_diff_op_before,
|
|
794
|
+
[:leptris_diff, :size_t], :string
|
|
795
|
+
attach_function :leptris_diff_op_after,
|
|
796
|
+
[:leptris_diff, :size_t], :string
|
|
797
|
+
attach_function :leptris_diff_serialize,
|
|
798
|
+
[:leptris_diff], :pointer
|
|
799
|
+
|
|
800
|
+
# One-call expanded name (libleptris 1.9.144): local name +
|
|
801
|
+
# prefix + resolved namespace URI in a single entry — the
|
|
802
|
+
# FFI-adapter fan-out read.
|
|
803
|
+
attach_function :leptris_element_expanded_name,
|
|
804
|
+
[:leptris_element, :pointer, :pointer, :pointer], :void
|
|
805
|
+
|
|
806
|
+
# Versioned XPath evaluation (libleptris lane 15): XPATH_10
|
|
807
|
+
# keeps the strict 1.0 surface (3.x syntax rejected with
|
|
808
|
+
# INVALID_ARG); XPATH_31 evaluates the full grammar.
|
|
809
|
+
attach_function :leptris_xpath_eval_versioned,
|
|
810
|
+
[:leptris_document, :leptris_element, :string, :int, :pointer],
|
|
811
|
+
:leptris_xpath_result
|
|
812
|
+
XPATH_10 = 1 # strict XPath 1.0: the 3.x surface is rejected
|
|
813
|
+
XPATH_31 = 3 # the full grammar (the default entry's surface)
|
|
814
|
+
|
|
749
815
|
# One-pass entity pre-scan (libleptris 1.9.62, leptris#745):
|
|
750
816
|
# 1 when the buffer holds a named entity that is not one of
|
|
751
817
|
# the five predefined XML entities (nor numeric) — the moxml
|
|
@@ -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
|
|
@@ -85,6 +85,27 @@ module Leptris::XML::Searchable
|
|
|
85
85
|
at_xpath(expr)
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
# Version-pinned evaluation (libleptris lane 15,
|
|
89
|
+
# leptris_xpath_eval_versioned): :xpath10 keeps the strict XPath
|
|
90
|
+
# 1.0 surface — 3.x syntax (arrow, bang, lookup, let, inline
|
|
91
|
+
# functions, string templates) raises; :xpath31 evaluates the
|
|
92
|
+
# full grammar (identical to #xpath's default entry). A separate
|
|
93
|
+
# method by design: #xpath's trailing-hash argument is the
|
|
94
|
+
# namespace-binding channel, and a declared keyword would
|
|
95
|
+
# capture it.
|
|
96
|
+
def xpath_versioned(expression, version)
|
|
97
|
+
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
98
|
+
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
99
|
+
result_ptr = Leptris::XML::FFI.leptris_xpath_eval_versioned(
|
|
100
|
+
doc_ptr, context_ptr, expression,
|
|
101
|
+
Leptris::XML::Searchable.xpath_version_code(version), nil)
|
|
102
|
+
if result_ptr.null?
|
|
103
|
+
raise Leptris::XML::XPathError,
|
|
104
|
+
Leptris::XML::FFI.leptris_last_error.to_s
|
|
105
|
+
end
|
|
106
|
+
Leptris::XML::Searchable.wrap_xpath_result(document, result_ptr)
|
|
107
|
+
end
|
|
108
|
+
|
|
88
109
|
protected
|
|
89
110
|
|
|
90
111
|
# Evaluates against a caller-owned namespace binding set; the
|
|
@@ -95,6 +116,21 @@ module Leptris::XML::Searchable
|
|
|
95
116
|
end
|
|
96
117
|
end
|
|
97
118
|
|
|
119
|
+
|
|
120
|
+
# Version pin for xpath_versioned: :xpath10
|
|
121
|
+
# keeps the strict XPath 1.0 surface (3.x syntax — arrow, bang,
|
|
122
|
+
# lookup, let, inline functions — raises); :xpath31 evaluates the
|
|
123
|
+
# full grammar (identical to the default entry).
|
|
124
|
+
def self.xpath_version_code(version)
|
|
125
|
+
case version
|
|
126
|
+
when :xpath10, "1.0" then Leptris::XML::FFI::XPATH_10
|
|
127
|
+
when :xpath31, "3.1" then Leptris::XML::FFI::XPATH_31
|
|
128
|
+
else
|
|
129
|
+
raise ArgumentError,
|
|
130
|
+
"version must be :xpath10 or :xpath31, got #{version.inspect}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
98
134
|
def parse_search_args(args)
|
|
99
135
|
handler = args.find { |a| !a.is_a?(String) && !a.is_a?(Hash) && !a.is_a?(Symbol) }
|
|
100
136
|
args = args - [handler] if handler
|
data/lib/leptris/xml/xquery.rb
CHANGED
|
@@ -46,10 +46,28 @@ class Leptris::XML::XQuery
|
|
|
46
46
|
# FLWOR results arrive as the sequence channel — read them through
|
|
47
47
|
# an aggregate until the engine materializes readable sequence
|
|
48
48
|
# items (same caveat as the XPath for-return subset).
|
|
49
|
-
|
|
49
|
+
# +params+ binds external variables (libleptris 1.9.133): each
|
|
50
|
+
# value is an XPath expression evaluated in an empty context
|
|
51
|
+
# (QT3 <param select> semantics) — {"x" => "21 * 2"} binds
|
|
52
|
+
# `declare variable $x external`. A binding overrides the
|
|
53
|
+
# declaration's default initializer; an external with neither
|
|
54
|
+
# fails evaluation. An empty params hash takes the same lane —
|
|
55
|
+
# the engine decides between default initializer and failure.
|
|
56
|
+
def eval(doc_or_element, params = nil)
|
|
50
57
|
context = Leptris::XML::EvaluationContext.of(doc_or_element)
|
|
51
|
-
result_ptr =
|
|
52
|
-
|
|
58
|
+
result_ptr =
|
|
59
|
+
if params
|
|
60
|
+
names, name_anchors = Leptris::XML::CStringArray.to_c(
|
|
61
|
+
params.keys.map(&:to_s))
|
|
62
|
+
selects, select_anchors = Leptris::XML::CStringArray.to_c(
|
|
63
|
+
params.values.map(&:to_s))
|
|
64
|
+
Leptris::XML::FFI.leptris_xquery_eval_params(
|
|
65
|
+
@handle, context.document.c_ptr, context.context_node_ptr,
|
|
66
|
+
names, selects, params.size)
|
|
67
|
+
else
|
|
68
|
+
Leptris::XML::FFI.leptris_xquery_eval(
|
|
69
|
+
@handle, context.document.c_ptr, context.context_node_ptr)
|
|
70
|
+
end
|
|
53
71
|
if result_ptr.null?
|
|
54
72
|
raise Leptris::XML::XPathError,
|
|
55
73
|
Leptris::XML::FFI.leptris_last_error.to_s
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -23,6 +23,8 @@ module Leptris
|
|
|
23
23
|
autoload :SAX, "leptris/xml/sax"
|
|
24
24
|
autoload :XPath, "leptris/xml/xpath"
|
|
25
25
|
autoload :EvaluationContext, "leptris/xml/evaluation_context"
|
|
26
|
+
autoload :Schematron, "leptris/xml/schematron"
|
|
27
|
+
autoload :Diff, "leptris/xml/diff"
|
|
26
28
|
autoload :IterationScope, "leptris/xml/iteration_scope"
|
|
27
29
|
autoload :ResultText, "leptris/xml/result_text"
|
|
28
30
|
autoload :ResultAttr, "leptris/xml/result_attr"
|
|
@@ -39,6 +41,14 @@ module Leptris
|
|
|
39
41
|
recover: recover)
|
|
40
42
|
end
|
|
41
43
|
|
|
44
|
+
# Tree diff between two documents: equal subtrees prune in
|
|
45
|
+
# O(1) by content digest; diverging regions align by LCS.
|
|
46
|
+
# +ignore_ws+ treats whitespace-only text nodes as absent.
|
|
47
|
+
def self.diff(document_a, document_b, ignore_ws: false)
|
|
48
|
+
Leptris::XML::Diff.compute(document_a, document_b,
|
|
49
|
+
ignore_ws: ignore_ws)
|
|
50
|
+
end
|
|
51
|
+
|
|
42
52
|
# Does +string+ contain a named entity reference that is not
|
|
43
53
|
# one of the five predefined XML entities (nor numeric)? One C
|
|
44
54
|
# pass (libleptris 1.9.62) — downstream adapters use it to skip
|
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.144.0
|
|
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-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -106,6 +106,7 @@ files:
|
|
|
106
106
|
- lib/leptris/xml/cdata.rb
|
|
107
107
|
- lib/leptris/xml/comment.rb
|
|
108
108
|
- lib/leptris/xml/css_to_xpath.rb
|
|
109
|
+
- lib/leptris/xml/diff.rb
|
|
109
110
|
- lib/leptris/xml/doc_type.rb
|
|
110
111
|
- lib/leptris/xml/document.rb
|
|
111
112
|
- lib/leptris/xml/document_fragment.rb
|
|
@@ -128,6 +129,7 @@ files:
|
|
|
128
129
|
- lib/leptris/xml/sax/dom_dispatch.rb
|
|
129
130
|
- lib/leptris/xml/sax/parser.rb
|
|
130
131
|
- lib/leptris/xml/sax/recorder.rb
|
|
132
|
+
- lib/leptris/xml/schematron.rb
|
|
131
133
|
- lib/leptris/xml/searchable.rb
|
|
132
134
|
- lib/leptris/xml/serialization.rb
|
|
133
135
|
- lib/leptris/xml/text.rb
|