leptris 1.7.0 → 1.9.1
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 +77 -53
- data/README.adoc +16 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/attr.rb +27 -5
- data/lib/leptris/xml/document.rb +23 -14
- data/lib/leptris/xml/element.rb +19 -2
- data/lib/leptris/xml/ffi.rb +58 -9
- data/lib/leptris/xml/parse_options.rb +35 -2
- data/lib/leptris/xml/serialization.rb +18 -4
- data/lib/leptris/xml.rb +3 -2
- data/lib/leptris.rb +26 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed2f1d5755923f389734df36cc9c40c513535e58e7abe5a40aa87dd93f858788
|
|
4
|
+
data.tar.gz: eb4e9c9c3b12a7584572746053c9aaf318ee5481fa002909d8502d5565bcf543
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35a1a34584014746679c39098f60c6009871007e3e5016562b669437e6f00d84412c7a26f286034f628421f72846c810b1b3568faacbbae86fa3e6c47b6db20f
|
|
7
|
+
data.tar.gz: 5e4785ee975db46c2f7798a4effd7c3d2be729e94ded98db78e3cdd830891b0704170e104d9b2287ff1e4e8a98833917a70a7ec4d5591615931020cf67f18e54
|
data/CHANGELOG.md
CHANGED
|
@@ -2,70 +2,78 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Leptris will be documented in this file.
|
|
4
4
|
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/
|
|
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.
|
|
8
|
+
## [1.9.1] - 2026-08-24
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
leptris_node_children (all-kind batch) and the document/element
|
|
12
|
-
serialize-into caller buffers (#535).
|
|
10
|
+
### Fixed
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
- **Autoload manifest restored after `require "leptris"`** (issue
|
|
13
|
+
#53): 1.9.0's eager FFI require ran inside `module Leptris`
|
|
14
|
+
before `autoload :XML` was registered, so ffi.rb's module opening
|
|
15
|
+
created `Leptris::XML` first — the autoload registration was
|
|
16
|
+
shadowed, xml.rb never loaded, and the entire API was unreachable
|
|
17
|
+
(`Leptris::XML.constants == [:FFI]`, no `Leptris::XML.parse`).
|
|
18
|
+
The eager require now runs after the registration: ffi.rb's
|
|
19
|
+
module opening triggers the autoload, FFI lands inside the real
|
|
20
|
+
manifest module, and a downstream `require "leptris/xml"` is a
|
|
21
|
+
no-op (the moxml workaround becomes unnecessary). Eager library
|
|
22
|
+
resolution at require time (#49) is unchanged. Regression spec
|
|
23
|
+
runs the cold `require "leptris"` path in a subprocess.
|
|
15
24
|
|
|
16
|
-
|
|
25
|
+
## [1.9.0] - 2026-08-24
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- CI/build pin libleptris v1.6.2, carrying the serializer fix for
|
|
21
|
-
mixed-content indentation (upstream #534 — never indent inside
|
|
22
|
-
mixed-content elements).
|
|
23
|
-
|
|
24
|
-
## [1.6.1] - 2026-08-24
|
|
27
|
+
Lockstep with libleptris 1.9.0 (covers the 1.8.0 and 1.9.0 engine
|
|
28
|
+
releases; CI/build pin libleptris v1.9.0).
|
|
25
29
|
|
|
26
30
|
### Added
|
|
27
31
|
|
|
28
|
-
- **
|
|
29
|
-
`
|
|
30
|
-
`
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
same-parent node moves no longer corrupt the sibling chain (#518),
|
|
53
|
-
detached PI/comment/CDATA mutation works on rootless documents
|
|
54
|
-
(#519); union nodesets keep attribute identity (#514); document-level
|
|
55
|
-
processing instructions gained a public API (#526).
|
|
32
|
+
- **Expanded-name attribute access** (engine #542):
|
|
33
|
+
`Element#attribute_ns(uri, local)` and `#has_attribute_ns?(uri,
|
|
34
|
+
local)` look attributes up by namespace URI + local name with XML
|
|
35
|
+
Namespaces 1.0 semantics — cross-prefix matches, nil/"" URI for
|
|
36
|
+
no-namespace only, xmlns declarations invisible. `Element#[]`
|
|
37
|
+
inherits the corrected by-name semantics: bare names match only
|
|
38
|
+
no-namespace attributes, qualified names resolve through
|
|
39
|
+
declarations, undeclared prefixes return nil.
|
|
40
|
+
- **Per-attribute namespace accessors** (engine #542): `Attr#prefix`
|
|
41
|
+
(as written in the QName) and `Attr#namespace_uri` (resolved
|
|
42
|
+
through the owning element's in-scope declarations at read time;
|
|
43
|
+
xml prebound; nil for undeclared prefixes). `Attr#namespace`
|
|
44
|
+
serves the URI instead of the pre-1.8.0 nil stub.
|
|
45
|
+
- **Recover parsing** (engine #547): `Leptris::XML.parse(xml,
|
|
46
|
+
recover: true)` / `ParseOptions.recovering` — a parse failure
|
|
47
|
+
returns an empty document (failure detail on the thread-global
|
|
48
|
+
last error) instead of raising ParseError: the libxml2
|
|
49
|
+
XML_PARSE_RECOVER semantics adapters emulate. Struct-only option;
|
|
50
|
+
carrying it routes the parse through `leptris_parse_string_ex`.
|
|
51
|
+
- **Caller-buffer serialization**: `#to_xml` (document + element)
|
|
52
|
+
now rides `leptris_document/element_serialize_into` (engine #541)
|
|
53
|
+
— options carried, the size-query + fill pair reuses one
|
|
54
|
+
serialization through a per-document cache invalidated on
|
|
55
|
+
mutation, and no C-side result-string allocation remains.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
### Fixed
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
- Rootless documents with document-level PIs no longer serialize to
|
|
60
|
+
"" (engine #546) — the declaration and every document-level PI are
|
|
61
|
+
emitted.
|
|
62
|
+
- Detached sibling inserts chain (engine #540): bottom-up
|
|
63
|
+
construction via `add_next_sibling` on detached elements works;
|
|
64
|
+
attaching the head of a detached chain carries the whole chain.
|
|
65
|
+
- Eager library resolution at require time (issue leptris-ruby#49):
|
|
66
|
+
the FFI library list is loaded when the gem loads, so a
|
|
67
|
+
ruby-platform install (no vendored libleptris) fails immediately
|
|
68
|
+
with an actionable message instead of deep inside Document.parse
|
|
69
|
+
- Packaging policy: this gem never publishes ruby-platform releases
|
|
70
|
+
without the vendored library — platform variants only
|
|
62
71
|
|
|
63
|
-
## [1.
|
|
72
|
+
## [1.7.0] - 2026-08-24
|
|
64
73
|
|
|
65
|
-
Lockstep with libleptris 1.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
declarations. Binding-side adoption of the new APIs follows.
|
|
74
|
+
Lockstep with libleptris 1.7.0. New engine surface:
|
|
75
|
+
leptris_node_children (all-kind batch) and the document/element
|
|
76
|
+
serialize-into caller buffers (#535).
|
|
69
77
|
|
|
70
78
|
## [1.6.2] - 2026-08-24
|
|
71
79
|
|
|
@@ -104,8 +112,11 @@ Lockstep with libleptris 1.6.2 (fix releases, no public API changes).
|
|
|
104
112
|
|
|
105
113
|
## [1.6.0] - 2026-08-24
|
|
106
114
|
|
|
107
|
-
Lockstep with libleptris 1.6.0
|
|
108
|
-
|
|
115
|
+
Lockstep with libleptris 1.6.0 — the moxml-adapter blockers fixed:
|
|
116
|
+
same-parent node moves no longer corrupt the sibling chain (#518),
|
|
117
|
+
detached PI/comment/CDATA mutation works on rootless documents
|
|
118
|
+
(#519); union nodesets keep attribute identity (#514); document-level
|
|
119
|
+
processing instructions gained a public API (#526).
|
|
109
120
|
|
|
110
121
|
### Added
|
|
111
122
|
|
|
@@ -130,6 +141,19 @@ public API changes).
|
|
|
130
141
|
- `leptris_parse_string_ex` bound (options-struct parse; the flags
|
|
131
142
|
path remains the default).
|
|
132
143
|
|
|
144
|
+
## [1.5.0] - 2026-08-24
|
|
145
|
+
|
|
146
|
+
Lockstep with libleptris 1.5.0 — the engine pins the TODO.engine
|
|
147
|
+
release: file-backed pull/iterparse streaming (bounded memory off
|
|
148
|
+
disk), compiled XPath ns/vars contexts, Rust crate publish workflow.
|
|
149
|
+
|
|
150
|
+
## [1.4.0] - 2026-08-23
|
|
151
|
+
|
|
152
|
+
Lockstep with libleptris 1.4.0 — engine pins the TODO.bindings
|
|
153
|
+
release: pull (StAX) API, bounded iterparse, compiled XPath
|
|
154
|
+
expressions, per-parse options, truthful serialization encoding
|
|
155
|
+
declarations. Binding-side adoption of the new APIs follows.
|
|
156
|
+
|
|
133
157
|
## [1.3.0] - 2026-08-23
|
|
134
158
|
|
|
135
159
|
Lockstep with libleptris 1.3.0 — takes full advantage of the
|
data/README.adoc
CHANGED
|
@@ -91,6 +91,18 @@ doc = Leptris::XML.parse_file("books.xml")
|
|
|
91
91
|
This is the direct Nokogiri equivalent of `Nokogiri::XML(...)`. The
|
|
92
92
|
returned object is a `Leptris::XML::Document`.
|
|
93
93
|
|
|
94
|
+
Malformed input raises `Leptris::XML::ParseError`. Parse with
|
|
95
|
+
`recover: true` to get libxml2's `XML_PARSE_RECOVER` semantics
|
|
96
|
+
instead — an empty document back, with the failure recorded on the
|
|
97
|
+
thread-global last error:
|
|
98
|
+
|
|
99
|
+
[source,ruby]
|
|
100
|
+
----
|
|
101
|
+
doc = Leptris::XML.parse("<broken", recover: true)
|
|
102
|
+
doc.root # => nil
|
|
103
|
+
Leptris::XML::FFI.leptris_last_error # => "..."
|
|
104
|
+
----
|
|
105
|
+
|
|
94
106
|
== Reading nodes
|
|
95
107
|
|
|
96
108
|
[horizontal]
|
|
@@ -251,6 +263,10 @@ doc.root.name = "catalog"
|
|
|
251
263
|
`Element#add_namespace_definition(prefix, href)` (alias `#add_namespace`) :: declare `xmlns:prefix="href"` on this element.
|
|
252
264
|
`Element#default_namespace=(href)` :: declare/replace `xmlns="href"`.
|
|
253
265
|
`Element#remove_namespace_definition(prefix)` :: drop a declaration.
|
|
266
|
+
`Element#attribute_ns(uri, local)` :: attribute value by expanded name (URI + local); nil URI matches no-namespace attributes only.
|
|
267
|
+
`Element#has_attribute_ns?(uri, local)` :: presence by expanded name.
|
|
268
|
+
`Attr#prefix` :: the attribute's prefix as written (`nil` when none).
|
|
269
|
+
`Attr#namespace_uri` :: resolved through the owning element's in-scope declarations at read time (`xml` prebound; `nil` for undeclared prefixes).
|
|
254
270
|
|
|
255
271
|
[source,ruby]
|
|
256
272
|
----
|
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.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.0"
|
|
12
12
|
|
|
13
13
|
CMAKE_FLAGS = %w[
|
|
14
14
|
-DCMAKE_BUILD_TYPE=Release
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/attr.rb
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Lightweight attribute wrapper: a (name, value, parent_element)
|
|
4
|
-
# materialized from the attribute-iteration face
|
|
5
|
-
# the
|
|
3
|
+
# Lightweight attribute wrapper: a (name, value, parent_element)
|
|
4
|
+
# triple materialized from the attribute-iteration face, carrying
|
|
5
|
+
# the C attribute handle for the per-attribute namespace accessors.
|
|
6
|
+
# Mutation goes through the parent element via #[].
|
|
6
7
|
|
|
7
8
|
class Leptris::XML::Attr
|
|
8
9
|
attr_reader :name, :value, :element
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
# c_handle is the C LeptrisAttribute pointer from the owning
|
|
12
|
+
# element's iteration face — document-owned and borrowed. Absent
|
|
13
|
+
# only when an Attr is constructed by hand.
|
|
14
|
+
def initialize(name, value, element, c_handle: nil)
|
|
11
15
|
@name = name
|
|
12
16
|
@value = value
|
|
13
17
|
@element = element
|
|
18
|
+
@c_handle = c_handle
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
def value=(new_value)
|
|
@@ -18,8 +23,25 @@ class Leptris::XML::Attr
|
|
|
18
23
|
@value = new_value
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
# The prefix as written in the qualified name (bytes before the
|
|
27
|
+
# colon), nil for a no-namespace attribute. Name-derived and
|
|
28
|
+
# immutable — the same semantics as leptris_attribute_prefix, so
|
|
29
|
+
# no declaration lookup is involved.
|
|
30
|
+
def prefix
|
|
31
|
+
@name.include?(":") ? @name.split(":", 2).first : nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The attribute's namespace URI, resolved through the OWNING
|
|
35
|
+
# element's in-scope declarations at read time (libleptris 1.8.0).
|
|
36
|
+
# nil for a no-namespace attribute or an undeclared prefix; xml
|
|
37
|
+
# is prebound to http://www.w3.org/XML/1998/namespace.
|
|
38
|
+
def namespace_uri
|
|
39
|
+
return nil if @c_handle.nil?
|
|
40
|
+
Leptris::XML::FFI.leptris_attribute_namespace_uri(@c_handle)
|
|
41
|
+
end
|
|
42
|
+
|
|
21
43
|
def namespace
|
|
22
|
-
|
|
44
|
+
namespace_uri
|
|
23
45
|
end
|
|
24
46
|
|
|
25
47
|
def remove
|
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -34,23 +34,41 @@ class Leptris::XML::Document
|
|
|
34
34
|
@wrapper_cache = {}
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def self.parse(xml_or_io, options: nil, readonly: false)
|
|
37
|
+
def self.parse(xml_or_io, options: nil, readonly: false, recover: false)
|
|
38
38
|
xml = xml_or_io.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
|
|
39
39
|
if xml.empty?
|
|
40
40
|
raise Leptris::XML::ParseError, "empty input"
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
if options.nil?
|
|
43
|
+
options = Leptris::XML::ParseOptions.new(recover: recover)
|
|
44
|
+
elsif recover && !options.recover?
|
|
45
|
+
options = options | Leptris::XML::ParseOptions.new(recover: true)
|
|
46
|
+
elsif !options.is_a?(Leptris::XML::ParseOptions)
|
|
47
|
+
raise ArgumentError, "options must be a Leptris::XML::ParseOptions"
|
|
48
|
+
end
|
|
43
49
|
# The status out-param is nullable; the thread-local last error
|
|
44
50
|
# carries failure detail, and skipping the per-parse MemoryPointer
|
|
45
51
|
# is measurable on small documents.
|
|
46
52
|
raw =
|
|
47
|
-
if
|
|
53
|
+
if options.struct_required?
|
|
54
|
+
# Recover is a struct field, not a parse flag — the options
|
|
55
|
+
# struct path (leptris_parse_string_ex) is the only carrier.
|
|
56
|
+
options_struct = options.to_c_struct
|
|
57
|
+
Leptris::XML::FFI.leptris_parse_string_ex(
|
|
58
|
+
xml, xml.bytesize, options_struct.pointer, nil)
|
|
59
|
+
elsif options.flags.zero?
|
|
48
60
|
Leptris::XML::FFI.leptris_parse_string(xml, xml.bytesize, nil)
|
|
49
61
|
else
|
|
50
62
|
Leptris::XML::FFI.leptris_parse_string_flags(
|
|
51
|
-
xml, xml.bytesize, flags, nil)
|
|
63
|
+
xml, xml.bytesize, options.flags, nil)
|
|
52
64
|
end
|
|
53
65
|
if raw.null?
|
|
66
|
+
if options.recover?
|
|
67
|
+
# Unreachable in practice: recover returns an empty document
|
|
68
|
+
# rather than NULL; kept so a contract change fails loudly.
|
|
69
|
+
raise Leptris::XML::Error,
|
|
70
|
+
"leptris_parse_string_ex returned NULL under recover"
|
|
71
|
+
end
|
|
54
72
|
raise Leptris::XML::ParseError,
|
|
55
73
|
"leptris_parse_string failed: " +
|
|
56
74
|
Leptris::XML::FFI.leptris_last_error.to_s
|
|
@@ -102,15 +120,6 @@ class Leptris::XML::Document
|
|
|
102
120
|
end
|
|
103
121
|
private_class_method :finalizer
|
|
104
122
|
|
|
105
|
-
def self.resolve_flags(options)
|
|
106
|
-
return Leptris::XML::FFI::LEPTRIS_PARSE_DEFAULT if options.nil?
|
|
107
|
-
unless options.is_a?(Leptris::XML::ParseOptions)
|
|
108
|
-
raise ArgumentError, "options must be a Leptris::XML::ParseOptions"
|
|
109
|
-
end
|
|
110
|
-
options.flags
|
|
111
|
-
end
|
|
112
|
-
private_class_method :resolve_flags
|
|
113
|
-
|
|
114
123
|
def root
|
|
115
124
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
116
125
|
return nil if @c_ptr.nil?
|
|
@@ -182,7 +191,7 @@ class Leptris::XML::Document
|
|
|
182
191
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
183
192
|
return "" if @c_ptr.nil?
|
|
184
193
|
Leptris::XML::Serialization.to_xml(
|
|
185
|
-
Leptris::XML::FFI.method(:
|
|
194
|
+
Leptris::XML::FFI.method(:leptris_document_serialize_into), @c_ptr,
|
|
186
195
|
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
187
196
|
end
|
|
188
197
|
alias_method :to_s, :to_xml
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -47,6 +47,21 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
47
47
|
end
|
|
48
48
|
alias_method :has_attribute?, :key?
|
|
49
49
|
|
|
50
|
+
# Expanded-name attribute access (libleptris 1.8.0): URI + local
|
|
51
|
+
# name with XML Namespaces 1.0 semantics — the written prefix
|
|
52
|
+
# never matters, only what it resolves to through this element's
|
|
53
|
+
# in-scope declarations. uri nil/"" matches no-namespace
|
|
54
|
+
# attributes only; xmlns declarations are invisible.
|
|
55
|
+
def attribute_ns(uri, local)
|
|
56
|
+
Leptris::XML::FFI.leptris_element_attribute_ns(
|
|
57
|
+
@c_ptr, uri&.to_s, local.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def has_attribute_ns?(uri, local)
|
|
61
|
+
Leptris::XML::FFI.leptris_element_has_attribute_ns(
|
|
62
|
+
@c_ptr, uri&.to_s, local.to_s) != 0
|
|
63
|
+
end
|
|
64
|
+
|
|
50
65
|
def remove_attribute(name)
|
|
51
66
|
ensure_writable!
|
|
52
67
|
Leptris::XML::FFI.check_status(
|
|
@@ -58,13 +73,15 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
58
73
|
# Iterates the element's attributes via the v1.1.0 linked-list face
|
|
59
74
|
# (one FFI call per attribute; the name_at/value_at indexing API
|
|
60
75
|
# re-walks the list per index, making it O(n^2) per element).
|
|
76
|
+
# The C attribute handle rides along so Attr can serve the
|
|
77
|
+
# per-attribute namespace accessors (libleptris 1.8.0).
|
|
61
78
|
def each_attribute
|
|
62
79
|
return enum_for(:each_attribute) unless block_given?
|
|
63
80
|
attr = Leptris::XML::FFI.leptris_element_first_attribute(@c_ptr)
|
|
64
81
|
until attr.nil? || attr.null?
|
|
65
82
|
name = Leptris::XML::FFI.leptris_attribute_get_name(attr)
|
|
66
83
|
value = Leptris::XML::FFI.leptris_attribute_get_value(@c_ptr, attr)
|
|
67
|
-
yield Leptris::XML::Attr.new(name, value, self)
|
|
84
|
+
yield Leptris::XML::Attr.new(name, value, self, c_handle: attr)
|
|
68
85
|
attr = Leptris::XML::FFI.leptris_attribute_next(attr)
|
|
69
86
|
end
|
|
70
87
|
self
|
|
@@ -238,7 +255,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
238
255
|
|
|
239
256
|
def to_xml(indent: 0, no_decl: false, encoding: nil)
|
|
240
257
|
Leptris::XML::Serialization.to_xml(
|
|
241
|
-
Leptris::XML::FFI.method(:
|
|
258
|
+
Leptris::XML::FFI.method(:leptris_element_serialize_into), @c_ptr,
|
|
242
259
|
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
243
260
|
end
|
|
244
261
|
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -7,15 +7,32 @@ module Leptris
|
|
|
7
7
|
module FFI
|
|
8
8
|
extend ::FFI::Library
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
# Issue leptris-ruby#49: name the remedy when the library is
|
|
11
|
+
# missing (ruby-platform gem without a vendored libleptris).
|
|
12
|
+
begin
|
|
13
|
+
ffi_lib [
|
|
14
|
+
ENV["LEPTRIS_LIB_PATH"],
|
|
15
|
+
File.expand_path("../../libleptris.dylib", __dir__),
|
|
16
|
+
File.expand_path("../../libleptris.so", __dir__),
|
|
17
|
+
File.expand_path("../../libleptris.dll", __dir__),
|
|
18
|
+
"/usr/local/lib/libleptris.dylib",
|
|
19
|
+
"/usr/local/lib/libleptris.so",
|
|
20
|
+
"leptris",
|
|
21
|
+
].compact
|
|
22
|
+
rescue LoadError => e
|
|
23
|
+
raise LoadError, <<~MSG
|
|
24
|
+
leptris: cannot load the vendored libleptris library.
|
|
25
|
+
You may have installed the ruby-platform variant of this
|
|
26
|
+
gem (#{Gem.loaded_specs["leptris"]&.full_name rescue "unknown"}),
|
|
27
|
+
which does not vendor the native library. Fix:
|
|
28
|
+
|
|
29
|
+
bundle update leptris # force platform gems
|
|
30
|
+
# or: gem install leptris --platform ruby is unsupported
|
|
31
|
+
# or: set LEPTRIS_LIB_PATH=/path/to/libleptris.so
|
|
32
|
+
|
|
33
|
+
Original error: #{e.message}
|
|
34
|
+
MSG
|
|
35
|
+
end
|
|
19
36
|
|
|
20
37
|
typedef :pointer, :leptris_document
|
|
21
38
|
typedef :pointer, :leptris_element
|
|
@@ -53,6 +70,16 @@ module Leptris
|
|
|
53
70
|
:encoding, :pointer
|
|
54
71
|
end
|
|
55
72
|
|
|
73
|
+
# Mirrors LeptrisParseOptions (libleptris >= 1.9.0 carries the
|
|
74
|
+
# recover field; earlier fields unchanged since v1.6.0).
|
|
75
|
+
class ParseOptionsStruct < ::FFI::Struct
|
|
76
|
+
layout \
|
|
77
|
+
:flags, :int,
|
|
78
|
+
:strict_mode, :int,
|
|
79
|
+
:max_depth, :int,
|
|
80
|
+
:recover, :int
|
|
81
|
+
end
|
|
82
|
+
|
|
56
83
|
attach_function :leptris_version, [], :string
|
|
57
84
|
attach_function :leptris_version_components, [:pointer, :pointer, :pointer], :void
|
|
58
85
|
|
|
@@ -209,6 +236,20 @@ module Leptris
|
|
|
209
236
|
[:leptris_element, :string, :int], :int
|
|
210
237
|
attach_function :leptris_element_has_attribute,
|
|
211
238
|
[:leptris_element, :string], :int
|
|
239
|
+
# libleptris 1.8.0: expanded-name lookup — (uri, local) with
|
|
240
|
+
# XML Namespaces 1.0 semantics; NULL/"" uri matches only
|
|
241
|
+
# no-namespace attributes, xmlns declarations never match.
|
|
242
|
+
attach_function :leptris_element_attribute_ns,
|
|
243
|
+
[:leptris_element, :string, :string], :string
|
|
244
|
+
attach_function :leptris_element_has_attribute_ns,
|
|
245
|
+
[:leptris_element, :string, :string], :int
|
|
246
|
+
# libleptris 1.8.0: per-attribute prefix (name-derived) and
|
|
247
|
+
# namespace URI (resolved through the owning element's
|
|
248
|
+
# in-scope declarations at read time).
|
|
249
|
+
attach_function :leptris_attribute_prefix,
|
|
250
|
+
[:leptris_attribute], :string
|
|
251
|
+
attach_function :leptris_attribute_namespace_uri,
|
|
252
|
+
[:leptris_attribute], :string
|
|
212
253
|
attach_function :leptris_element_first_attribute,
|
|
213
254
|
[:leptris_element], :leptris_attribute
|
|
214
255
|
attach_function :leptris_attribute_next,
|
|
@@ -451,10 +492,18 @@ module Leptris
|
|
|
451
492
|
|
|
452
493
|
attach_function :leptris_document_serialize,
|
|
453
494
|
[:leptris_document, :pointer], :pointer
|
|
495
|
+
# libleptris >= 1.9.0: caller-buffer serialization with options
|
|
496
|
+
# (leptris#541). buf=NULL is a size query; the size-query +
|
|
497
|
+
# fill pair reuses one serialization through a per-document
|
|
498
|
+
# cache invalidated on mutation.
|
|
499
|
+
attach_function :leptris_document_serialize_into,
|
|
500
|
+
[:leptris_document, :pointer, :size_t, :pointer, :pointer], :size_t
|
|
454
501
|
attach_function :leptris_document_get_dtd,
|
|
455
502
|
[:leptris_document], :pointer
|
|
456
503
|
attach_function :leptris_element_serialize,
|
|
457
504
|
[:leptris_element, :pointer], :pointer
|
|
505
|
+
attach_function :leptris_element_serialize_into,
|
|
506
|
+
[:leptris_element, :pointer, :size_t, :pointer, :pointer], :size_t
|
|
458
507
|
attach_function :leptris_document_save_file,
|
|
459
508
|
[:leptris_document, :string, :pointer], :leptris_status
|
|
460
509
|
attach_function :leptris_c14n_canonicalize,
|
|
@@ -11,19 +11,52 @@ class Leptris::XML::ParseOptions
|
|
|
11
11
|
|
|
12
12
|
attr_reader :flags
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# Recover (libleptris 1.9.0, #547): a parse failure returns an
|
|
15
|
+
# empty document with the failure recorded in Document#last_error
|
|
16
|
+
# instead of raising — the libxml2 XML_PARSE_RECOVER semantics
|
|
17
|
+
# adapters emulate. Struct-only (not a parse flag): carrying it
|
|
18
|
+
# routes Document.parse through leptris_parse_string_ex.
|
|
19
|
+
attr_reader :recover
|
|
20
|
+
|
|
21
|
+
def initialize(flags = Leptris::XML::FFI::LEPTRIS_PARSE_DEFAULT,
|
|
22
|
+
recover: false)
|
|
15
23
|
@flags = flags.to_i
|
|
24
|
+
@recover = recover ? true : false
|
|
16
25
|
end
|
|
17
26
|
|
|
18
27
|
def self.noblanks
|
|
19
28
|
new(NOBLANKS)
|
|
20
29
|
end
|
|
21
30
|
|
|
31
|
+
def self.recovering
|
|
32
|
+
new(recover: true)
|
|
33
|
+
end
|
|
34
|
+
|
|
22
35
|
def noblanks?
|
|
23
36
|
@flags & NOBLANKS != 0
|
|
24
37
|
end
|
|
25
38
|
|
|
39
|
+
def recover?
|
|
40
|
+
@recover == true
|
|
41
|
+
end
|
|
42
|
+
|
|
26
43
|
def |(other)
|
|
27
|
-
self.class.new(@flags | other.flags)
|
|
44
|
+
self.class.new(@flags | other.flags, recover: @recover || other.recover?)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# True when the options cannot ride the flags-only parse path and
|
|
48
|
+
# need the full LeptrisParseOptions struct (leptris_parse_string_ex).
|
|
49
|
+
def struct_required?
|
|
50
|
+
recover?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Builds the C LeptrisParseOptions struct mirroring this instance.
|
|
54
|
+
def to_c_struct
|
|
55
|
+
struct = Leptris::XML::FFI::ParseOptionsStruct.new
|
|
56
|
+
struct[:flags] = @flags
|
|
57
|
+
struct[:strict_mode] = -1
|
|
58
|
+
struct[:max_depth] = 0
|
|
59
|
+
struct[:recover] = @recover ? 1 : 0
|
|
60
|
+
struct
|
|
28
61
|
end
|
|
29
62
|
end
|
|
@@ -18,8 +18,12 @@ module Leptris::XML::Serialization
|
|
|
18
18
|
end
|
|
19
19
|
private_constant :DEFAULT_OPTIONS
|
|
20
20
|
|
|
21
|
-
# +ffi_function+ is a bound
|
|
22
|
-
#
|
|
21
|
+
# +ffi_function+ is a bound _serialize_into function taking
|
|
22
|
+
# (c_ptr, buf, capacity, out_len, opts): size query with buf=NULL,
|
|
23
|
+
# then one fill into a caller buffer. Since libleptris 1.9.0 the
|
|
24
|
+
# pair reuses a single serialization through a per-document cache
|
|
25
|
+
# (leptris#541) — no C-side result-string allocation, and repeated
|
|
26
|
+
# serializations of an unmutated document skip the serialize pass.
|
|
23
27
|
def self.to_xml(ffi_function, c_ptr, indent: 0, no_decl: false, encoding: nil)
|
|
24
28
|
opts =
|
|
25
29
|
if indent.to_i.zero? && !no_decl && encoding.nil?
|
|
@@ -29,8 +33,18 @@ module Leptris::XML::Serialization
|
|
|
29
33
|
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
30
34
|
opts
|
|
31
35
|
end
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
need = ffi_function.call(c_ptr, nil, 0, nil, opts.pointer)
|
|
37
|
+
return "" if need.zero?
|
|
38
|
+
buf = ::FFI::MemoryPointer.new(:char, need)
|
|
39
|
+
begin
|
|
40
|
+
ffi_function.call(c_ptr, buf, need, nil, opts.pointer)
|
|
41
|
+
# The buffer holds exactly the serialization + NUL; XML output
|
|
42
|
+
# is NUL-free, so a bounded read_string is exact. (Avoids the
|
|
43
|
+
# out_len param — no portable size_t read exists on Pointer.)
|
|
44
|
+
buf.read_string
|
|
45
|
+
ensure
|
|
46
|
+
buf.free
|
|
47
|
+
end
|
|
34
48
|
end
|
|
35
49
|
|
|
36
50
|
# +ffi_function+ is a bound FFI function taking
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -27,8 +27,9 @@ module Leptris
|
|
|
27
27
|
|
|
28
28
|
# Nokogiri-style top-level entry points (Nokogiri::XML(...) /
|
|
29
29
|
# Nokogiri::XML.parse). Delegates to Document.parse.
|
|
30
|
-
def self.parse(xml_or_io, options: nil, readonly: false)
|
|
31
|
-
Document.parse(xml_or_io, options: options, readonly: readonly
|
|
30
|
+
def self.parse(xml_or_io, options: nil, readonly: false, recover: false)
|
|
31
|
+
Document.parse(xml_or_io, options: options, readonly: readonly,
|
|
32
|
+
recover: recover)
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def self.parse_file(path)
|
data/lib/leptris.rb
CHANGED
|
@@ -5,3 +5,29 @@ require "leptris/version"
|
|
|
5
5
|
module Leptris
|
|
6
6
|
autoload :XML, "leptris/xml"
|
|
7
7
|
end
|
|
8
|
+
|
|
9
|
+
# Eager library resolution (issue leptris-ruby#49): resolve the
|
|
10
|
+
# native library at require time instead of lazily inside
|
|
11
|
+
# Document.parse, so a missing library fails immediately.
|
|
12
|
+
#
|
|
13
|
+
# The require MUST come after the autoload registration above.
|
|
14
|
+
# ffi.rb opens `module Leptris; module XML`, and when that runs
|
|
15
|
+
# before `autoload :XML` exists it creates the constant first — the
|
|
16
|
+
# later autoload registration is then shadowed, xml.rb never loads,
|
|
17
|
+
# and the entire API is unreachable (Leptris::XML.constants ==
|
|
18
|
+
# [:FFI]; issue leptris-ruby#53). Requiring from here after the
|
|
19
|
+
# registration makes ffi.rb's module opening trigger the autoload,
|
|
20
|
+
# landing FFI inside the real manifest module; a downstream
|
|
21
|
+
# `require "leptris/xml"` is then a no-op.
|
|
22
|
+
begin
|
|
23
|
+
require "leptris/xml/ffi"
|
|
24
|
+
rescue LoadError => e
|
|
25
|
+
raise LoadError, <<~MSG
|
|
26
|
+
Leptris could not load the native libleptris library.
|
|
27
|
+
The ruby-platform gem is a fallback variant and ships no
|
|
28
|
+
binary; install the platform-specific variant for your system
|
|
29
|
+
(e.g. `gem install leptris --platform=arm64-darwin`), or set
|
|
30
|
+
LEPTRIS_LIB_PATH to a libleptris.{so,dylib,dll} file.
|
|
31
|
+
(Underlying error: #{e.message})
|
|
32
|
+
MSG
|
|
33
|
+
end
|