leptris 1.9.34 → 1.9.36
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 +78 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +27 -1
- data/lib/leptris/xml/element.rb +18 -3
- data/lib/leptris/xml/ffi.rb +21 -0
- data/lib/leptris/xml/node.rb +2 -6
- data/lib/leptris/xml/processing_instruction.rb +45 -21
- data/lib/leptris/xml/sax/dom_dispatch.rb +117 -0
- data/lib/leptris/xml/sax/parser.rb +24 -2
- data/lib/leptris/xml/sax/recorder.rb +12 -0
- data/lib/leptris/xml/sax.rb +1 -0
- data/lib/leptris/xml/serialization.rb +19 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31fdbaa742e583a50409dea1a5bda76e454e3b54ec30429d74101d5438f91b6f
|
|
4
|
+
data.tar.gz: 6acd554905283d755febf5c6265ce2a97dd8f9ce1ef83c9804363ff2d5bf1000
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e4c9abd84b07a678394774470b1486ccb2e03146578cda7eded0da2aa42f0b33f3ae682ac5a1881d2661e4a90ddb3bd6e0b3098d411303268867a2bbc1ad0d5
|
|
7
|
+
data.tar.gz: 35a2088c31974f7dc19beaa0bd4ad5c73515be100b1c0d3b3fd3bc313fa10e7abc28202f3eb7bb3cce6035ad86b0c3f553bde0ca458e08a03110ba0d774ff07a
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,84 @@ 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.36] - 2026-08-29
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **SAX attribute corruption (leptris-ruby#95)** — fixed fully in
|
|
13
|
+
the binding. The engine's streaming attribute buffer loses
|
|
14
|
+
leading attribute pairs when several nested ancestor levels each
|
|
15
|
+
carry attributes; all THREE streaming surfaces are affected (the
|
|
16
|
+
callback transport, the recorder's arena, and pull attrs — each
|
|
17
|
+
verified on the issue fixture, which also yields run-varying
|
|
18
|
+
uninitialized bytes), while the DOM parser reads the same bytes
|
|
19
|
+
correctly. `SAX::Parser` now delivers from a DOM parse by
|
|
20
|
+
default (`SAX::DomDispatch`): one parse, then a lean walk
|
|
21
|
+
reproducing the streaming call shapes exactly —
|
|
22
|
+
attach-only-overridden kinds, one-arg arity dispatch, QName
|
|
23
|
+
element names, UTF-8, PI-data normalization. The issue fixture
|
|
24
|
+
is a regression spec (all 7 image attributes intact; the entire
|
|
25
|
+
existing SAX suite passes byte-identically through the new
|
|
26
|
+
path). The engine transports remain available via
|
|
27
|
+
`Parser.new(handler, streaming: true)` — and return as the
|
|
28
|
+
default the moment the engine fix lands. Correctness-first tax,
|
|
29
|
+
measured on big.xml: all-events 375 ms (engine 112, nokogiri
|
|
30
|
+
130); the bisection (not positional, not attribute length;
|
|
31
|
+
trigger = nested attr-carrying ancestors; all three surfaces
|
|
32
|
+
share the buffer) is filed upstream for the C fix.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **libleptris 1.9.10 lockstep**: pin bumped; audit 250/250 with
|
|
37
|
+
`leptris_sax_recorder_reset` attached. `SAX::Recorder#reset` —
|
|
38
|
+
reuse one recorder across documents (upstream #594's
|
|
39
|
+
one-document-per-instance finding): fresh parser state, record/
|
|
40
|
+
arena buffers retained.
|
|
41
|
+
|
|
42
|
+
### Meta
|
|
43
|
+
|
|
44
|
+
- Memory battery (first measurement, 10 x big.xml held): leptris
|
|
45
|
+
**17.5 MB/doc vs nokogiri 31.3** (1.8x lighter); both free
|
|
46
|
+
cleanly.
|
|
47
|
+
|
|
48
|
+
## [1.9.35] - 2026-08-28
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **libleptris 1.9.9 lockstep**: pin bumped; symbol audit 249/249
|
|
53
|
+
with `leptris_document_remove_pi` and
|
|
54
|
+
`leptris_document_serialize_ext` attached. Engine fixes ride
|
|
55
|
+
along: CDATA `]]>` runs split across node boundaries, XSLT
|
|
56
|
+
suite 144 -> 152/205, attribute-axis entity expansion.
|
|
57
|
+
- **Document-level PIs fully mutable (leptris-ruby#92, upstream
|
|
58
|
+
#612)**: parse-created document-level PIs carry document
|
|
59
|
+
linkage — `target=`/`data=` work like on any tree PI (the
|
|
60
|
+
1.9.32 contract-error mapping is removed). `PI#unlink` on a
|
|
61
|
+
document-level PI routes through the new document-level removal,
|
|
62
|
+
identity-matched by index so the right same-target PI comes out.
|
|
63
|
+
New `Document#remove_pi(target_or_index)` — by target or 0-based
|
|
64
|
+
index, returns the removed (pool-owned) PI or nil.
|
|
65
|
+
- **`Document#to_xml(indent_text: true)`** — display-form
|
|
66
|
+
serialization (upstream #129): text and mixed content indent;
|
|
67
|
+
output is display-oriented and not round-trip-guaranteed.
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
|
|
71
|
+
- **Inline memo on `Element#[]`**: the external battery caught
|
|
72
|
+
attribute reads ~1.6x behind Nokogiri — six method dispatches
|
|
73
|
+
per read through the attributes/memo_hit? tower. The guard is
|
|
74
|
+
now spelled inline over a values hash materialized alongside the
|
|
75
|
+
Attr objects (same ADR-0003 semantics, misses included).
|
|
76
|
+
|
|
77
|
+
### Meta
|
|
78
|
+
|
|
79
|
+
- External head-to-head battery vs Nokogiri 1.19.4 (big.xml):
|
|
80
|
+
parse 12.3x, css 4.8x, xpath nodeset 3.0x, serialize 2.3x,
|
|
81
|
+
at_css 1.4x, SAX both shapes (round XXVI). Two shapes remain
|
|
82
|
+
behind and are C-side-bound: cold full walk 1.4x (needs
|
|
83
|
+
node_children out_kinds to skip per-node get_type — filing
|
|
84
|
+
upstream) and scalar xpath 1.5x (engine-side scalar eval).
|
|
85
|
+
|
|
8
86
|
## [1.9.34] - 2026-08-28
|
|
9
87
|
|
|
10
88
|
### 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.10"
|
|
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
|
@@ -266,9 +266,16 @@ class Leptris::XML::Document
|
|
|
266
266
|
end
|
|
267
267
|
alias_method :internal_subset, :doctype
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
# indent_text: true selects the display form (libleptris 1.9.9,
|
|
270
|
+
# #129): text and mixed content indent too — the output is
|
|
271
|
+
# display-oriented and not round-trip-guaranteed.
|
|
272
|
+
def to_xml(indent: 0, no_decl: false, encoding: nil, indent_text: false)
|
|
270
273
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
271
274
|
return "" if @c_ptr.nil?
|
|
275
|
+
if indent_text
|
|
276
|
+
return Leptris::XML::Serialization.to_xml_display(
|
|
277
|
+
@c_ptr, indent: indent, no_decl: no_decl, encoding: encoding)
|
|
278
|
+
end
|
|
272
279
|
Leptris::XML::Serialization.to_xml(
|
|
273
280
|
Leptris::XML::FFI.method(:leptris_document_serialize_into), @c_ptr,
|
|
274
281
|
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
@@ -338,6 +345,25 @@ class Leptris::XML::Document
|
|
|
338
345
|
result
|
|
339
346
|
end
|
|
340
347
|
|
|
348
|
+
# Removes a document-level processing instruction — by target
|
|
349
|
+
# (String/Symbol) or by 0-based index among the document's PIs
|
|
350
|
+
# (Integer) (libleptris 1.9.9, upstream #612). Returns the removed
|
|
351
|
+
# PI — pool-owned and valid until #free — or nil when nothing
|
|
352
|
+
# matched.
|
|
353
|
+
def remove_pi(target_or_index)
|
|
354
|
+
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
355
|
+
if target_or_index.is_a?(Integer)
|
|
356
|
+
ptr = Leptris::XML::FFI.leptris_document_remove_pi(
|
|
357
|
+
@c_ptr, nil, target_or_index)
|
|
358
|
+
else
|
|
359
|
+
ptr = Leptris::XML::FFI.leptris_document_remove_pi(
|
|
360
|
+
@c_ptr, target_or_index.to_s, 0)
|
|
361
|
+
end
|
|
362
|
+
return nil if ptr.null?
|
|
363
|
+
@version += 1
|
|
364
|
+
Leptris::XML::Node.wrap(ptr, self)
|
|
365
|
+
end
|
|
366
|
+
|
|
341
367
|
# Append a document-level processing instruction. Returns self.
|
|
342
368
|
def add_pi(target, data = "")
|
|
343
369
|
witness = Leptris::XML::FFI.leptris_document_add_pi(
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -44,8 +44,18 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
44
44
|
# written prefix never matters — the hash cannot answer them.
|
|
45
45
|
name = key.to_s
|
|
46
46
|
if @document && !name.include?(":")
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
# The hottest read in the library: the memo guard is spelled
|
|
48
|
+
# INLINE rather than through attributes/#memo_hit? — six
|
|
49
|
+
# method dispatches per read was ~40% of the read cost, and
|
|
50
|
+
# the external head-to-head (nokogiri's one C call) was
|
|
51
|
+
# winning on it. Same three-line memo semantics (ADR 0003),
|
|
52
|
+
# incl. the nil-miss — the hash answers misses too.
|
|
53
|
+
values = @attr_values
|
|
54
|
+
unless values && @attributes_version == @document.version
|
|
55
|
+
attributes
|
|
56
|
+
values = @attr_values
|
|
57
|
+
end
|
|
58
|
+
return values[name]
|
|
49
59
|
end
|
|
50
60
|
ensure_alive!
|
|
51
61
|
Leptris::XML::FFI.leptris_element_attribute(@c_ptr, name)
|
|
@@ -133,9 +143,14 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
133
143
|
def attributes
|
|
134
144
|
return @attributes if memo_hit?(@attributes_version)
|
|
135
145
|
result = {}
|
|
136
|
-
|
|
146
|
+
values = {}
|
|
147
|
+
each_attribute do |attr|
|
|
148
|
+
result[attr.name] = attr
|
|
149
|
+
values[attr.name] = attr.value
|
|
150
|
+
end
|
|
137
151
|
if @document
|
|
138
152
|
@attributes = result
|
|
153
|
+
@attr_values = values
|
|
139
154
|
@attributes_version = @document.version
|
|
140
155
|
end
|
|
141
156
|
result
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -72,6 +72,12 @@ module Leptris
|
|
|
72
72
|
:encoding, :pointer
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
# LeptrisSerializeExtOptions (1.9.9, #129): display-form
|
|
76
|
+
# serialization knobs beyond the ABI-frozen options struct.
|
|
77
|
+
class SerializeExtStruct < ::FFI::Struct
|
|
78
|
+
layout :indent_text, :int
|
|
79
|
+
end
|
|
80
|
+
|
|
75
81
|
# Mirrors LeptrisParseOptions (libleptris >= 1.9.0 carries the
|
|
76
82
|
# recover field; earlier fields unchanged since v1.6.0).
|
|
77
83
|
class ParseOptionsStruct < ::FFI::Struct
|
|
@@ -156,6 +162,11 @@ module Leptris
|
|
|
156
162
|
[:leptris_document, :size_t], :string
|
|
157
163
|
attach_function :leptris_document_pi_data,
|
|
158
164
|
[:leptris_document, :size_t], :string
|
|
165
|
+
# Document-level PI removal by target or index (1.9.9, #612):
|
|
166
|
+
# unlinks from the child chain; the node is pool-owned and
|
|
167
|
+
# stays valid until document free.
|
|
168
|
+
attach_function :leptris_document_remove_pi,
|
|
169
|
+
[:leptris_document, :string, :size_t], :leptris_node_ref
|
|
159
170
|
attach_function :leptris_document_add_pi,
|
|
160
171
|
[:leptris_document, :string, :string], :pointer
|
|
161
172
|
# Document-level comments (libleptris 1.9.3, #578): parsed
|
|
@@ -550,6 +561,11 @@ module Leptris
|
|
|
550
561
|
[:leptris_sax_recorder, :pointer], :pointer
|
|
551
562
|
attach_function :leptris_sax_recorder_arena,
|
|
552
563
|
[:leptris_sax_recorder, :pointer], :pointer
|
|
564
|
+
# Reuse across documents (1.9.10, #594): swaps in fresh parser
|
|
565
|
+
# state, RETAINS the record/arena buffers — a
|
|
566
|
+
# one-document-per-parse loop stops paying new/free churn.
|
|
567
|
+
attach_function :leptris_sax_recorder_reset,
|
|
568
|
+
[:leptris_sax_recorder], :int
|
|
553
569
|
attach_function :leptris_sax_recorder_free,
|
|
554
570
|
[:leptris_sax_recorder], :void
|
|
555
571
|
attach_function :leptris_sax_parser_free,
|
|
@@ -611,6 +627,11 @@ module Leptris
|
|
|
611
627
|
# (leptris#541). buf=NULL is a size query; the size-query +
|
|
612
628
|
# fill pair reuses one serialization through a per-document
|
|
613
629
|
# cache invalidated on mutation.
|
|
630
|
+
# Display-form variant (1.9.9, #129): the ext struct carries
|
|
631
|
+
# indent_text; returns an OWNED string — read via
|
|
632
|
+
# read_owned_string (the seam frees it).
|
|
633
|
+
attach_function :leptris_document_serialize_ext,
|
|
634
|
+
[:leptris_document, :pointer, :pointer], :pointer
|
|
614
635
|
attach_function :leptris_document_serialize_into,
|
|
615
636
|
[:leptris_document, :pointer, :size_t, :pointer, :pointer], :size_t
|
|
616
637
|
attach_function :leptris_document_get_dtd,
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -263,12 +263,8 @@ class Leptris::XML::Node
|
|
|
263
263
|
|
|
264
264
|
def unlink
|
|
265
265
|
ensure_writable!
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
raise Leptris::XML::Error,
|
|
269
|
-
Leptris::XML::ProcessingInstruction::READ_ONLY_NATIVE
|
|
270
|
-
end
|
|
271
|
-
Leptris::XML::FFI.check_status(rc)
|
|
266
|
+
Leptris::XML::FFI.check_status(
|
|
267
|
+
Leptris::XML::FFI.leptris_node_unlink(@c_ptr))
|
|
272
268
|
@parent = nil
|
|
273
269
|
self
|
|
274
270
|
end
|
|
@@ -25,34 +25,58 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
25
25
|
result
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# fine. Map that failure to the contract instead of a bare
|
|
32
|
-
# "Invalid argument".
|
|
33
|
-
READ_ONLY_NATIVE =
|
|
34
|
-
"this PI is a parse-created document-level PI — its node has " \
|
|
35
|
-
"no document linkage in libleptris, so target/data cannot be " \
|
|
36
|
-
"written and it cannot be unlinked (leptris-ruby#92); " \
|
|
37
|
-
"tree-level and Document#add_pi PIs are mutable"
|
|
38
|
-
|
|
28
|
+
# Since libleptris 1.9.9 (#612) parse-created document-level PIs
|
|
29
|
+
# carry document linkage — the setters work on them like on any
|
|
30
|
+
# tree-level PI.
|
|
39
31
|
def target=(new_target)
|
|
40
32
|
ensure_writable!
|
|
41
|
-
|
|
42
|
-
@c_ptr, new_target.to_s)
|
|
43
|
-
raise Leptris::XML::Error, READ_ONLY_NATIVE if
|
|
44
|
-
rc == Leptris::XML::FFI::LEPTRIS_ERROR_INVALID_ARG
|
|
45
|
-
Leptris::XML::FFI.check_status(rc)
|
|
33
|
+
Leptris::XML::FFI.check_status(
|
|
34
|
+
Leptris::XML::FFI.leptris_pi_node_set_target(@c_ptr, new_target.to_s))
|
|
46
35
|
new_target
|
|
47
36
|
end
|
|
48
37
|
|
|
49
38
|
def data=(new_data)
|
|
50
39
|
ensure_writable!
|
|
51
|
-
|
|
52
|
-
@c_ptr, new_data.to_s)
|
|
53
|
-
raise Leptris::XML::Error, READ_ONLY_NATIVE if
|
|
54
|
-
rc == Leptris::XML::FFI::LEPTRIS_ERROR_INVALID_ARG
|
|
55
|
-
Leptris::XML::FFI.check_status(rc)
|
|
40
|
+
Leptris::XML::FFI.check_status(
|
|
41
|
+
Leptris::XML::FFI.leptris_pi_node_set_data(@c_ptr, new_data.to_s))
|
|
56
42
|
new_data
|
|
57
43
|
end
|
|
44
|
+
|
|
45
|
+
# Document-level PIs have no tree parent for leptris_node_unlink
|
|
46
|
+
# — route through the document-level removal (libleptris 1.9.9,
|
|
47
|
+
# #612), identity-matched by index so the right same-target PI
|
|
48
|
+
# comes out.
|
|
49
|
+
def unlink
|
|
50
|
+
ensure_writable!
|
|
51
|
+
rc = Leptris::XML::FFI.leptris_node_unlink(@c_ptr)
|
|
52
|
+
if rc == Leptris::XML::FFI::LEPTRIS_ERROR_NOT_FOUND
|
|
53
|
+
index = document_pi_index
|
|
54
|
+
if index.negative?
|
|
55
|
+
raise Leptris::XML::Error,
|
|
56
|
+
"PI not found in its document — already removed?"
|
|
57
|
+
end
|
|
58
|
+
removed = @document.remove_pi(index)
|
|
59
|
+
unless removed && removed.c_ptr == @c_ptr
|
|
60
|
+
raise Leptris::XML::Error, "document-level PI removal missed"
|
|
61
|
+
end
|
|
62
|
+
@parent = nil
|
|
63
|
+
return self
|
|
64
|
+
end
|
|
65
|
+
Leptris::XML::FFI.check_status(rc)
|
|
66
|
+
@parent = nil
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
# This PI's 0-based index among the document's PIs (document
|
|
73
|
+
# order — the same enumeration leptris_document_remove_pi uses).
|
|
74
|
+
def document_pi_index
|
|
75
|
+
pi_index = -1
|
|
76
|
+
@document.children.each do |child|
|
|
77
|
+
pi_index += 1 if child.pi?
|
|
78
|
+
return pi_index if child.c_ptr == @c_ptr
|
|
79
|
+
end
|
|
80
|
+
-1
|
|
81
|
+
end
|
|
58
82
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# DOM-backed SAX dispatch (leptris-ruby#95): the engine's streaming
|
|
4
|
+
# attribute buffer corrupts leading attribute pairs when several
|
|
5
|
+
# nested ancestor levels each carry attributes — the callback
|
|
6
|
+
# transport, the recorder's packed arena, AND the pull parser all
|
|
7
|
+
# deliver the loss on the issue fixture (verified), while the DOM
|
|
8
|
+
# parser reads the same bytes correctly. Until the engine fix
|
|
9
|
+
# lands, SAX::Parser delivers from a DOM parse: same call shapes as
|
|
10
|
+
# the streaming transports — attach-only-overridden kinds,
|
|
11
|
+
# one-argument start_element arity, QName element names ("p:a"),
|
|
12
|
+
# UTF-8 strings, PI-data normalization.
|
|
13
|
+
#
|
|
14
|
+
# The walk is lean by construction: elements wrap (identity, name
|
|
15
|
+
# and prefix memos); every other child kind is read straight from
|
|
16
|
+
# its pointer — content getters per node type, never a wrapper.
|
|
17
|
+
module Leptris::XML::SAX::DomDispatch
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def parse(handler, dispatched, string)
|
|
21
|
+
doc = Leptris::XML::Document.parse(string)
|
|
22
|
+
begin
|
|
23
|
+
dispatch(handler, dispatched, doc)
|
|
24
|
+
ensure
|
|
25
|
+
doc.free
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def dispatch(handler, dispatched, doc)
|
|
30
|
+
handler.start_document if dispatched[:start_document]
|
|
31
|
+
doc_node = doc.node
|
|
32
|
+
visit_children(doc_node.c_ptr, doc, handler, dispatched) if doc_node
|
|
33
|
+
handler.end_document if dispatched[:end_document]
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
NODE = Leptris::XML::FFI
|
|
38
|
+
ELEMENT = Leptris::XML::FFI::NODE_ELEMENT
|
|
39
|
+
TEXT = Leptris::XML::FFI::NODE_TEXT
|
|
40
|
+
COMMENT = Leptris::XML::FFI::NODE_COMMENT
|
|
41
|
+
CDATA = Leptris::XML::FFI::NODE_CDATA
|
|
42
|
+
PI = Leptris::XML::FFI::NODE_PI
|
|
43
|
+
|
|
44
|
+
def visit_children(c_ptr, doc, handler, dispatched)
|
|
45
|
+
wants_text_kinds = dispatched[:characters] ||
|
|
46
|
+
dispatched[:comment] ||
|
|
47
|
+
dispatched[:cdata] || dispatched[:pi]
|
|
48
|
+
Leptris::XML::FFI.fetch_children(c_ptr).each do |child_ptr|
|
|
49
|
+
case Leptris::XML::FFI.leptris_node_get_type(child_ptr)
|
|
50
|
+
when ELEMENT
|
|
51
|
+
visit_element(
|
|
52
|
+
Leptris::XML::Node.wrap(child_ptr, doc, node_type: ELEMENT),
|
|
53
|
+
handler, dispatched)
|
|
54
|
+
when TEXT
|
|
55
|
+
if dispatched[:characters]
|
|
56
|
+
handler.characters(utf8(NODE.leptris_text_node_get_content(child_ptr)))
|
|
57
|
+
end
|
|
58
|
+
when CDATA
|
|
59
|
+
if dispatched[:cdata]
|
|
60
|
+
handler.cdata_block(utf8(NODE.leptris_cdata_node_get_content(child_ptr)))
|
|
61
|
+
end
|
|
62
|
+
when COMMENT
|
|
63
|
+
if dispatched[:comment]
|
|
64
|
+
handler.comment(utf8(NODE.leptris_comment_node_get_content(child_ptr)))
|
|
65
|
+
end
|
|
66
|
+
when PI
|
|
67
|
+
if dispatched[:pi]
|
|
68
|
+
handler.processing_instruction(
|
|
69
|
+
utf8(NODE.leptris_pi_node_get_target(child_ptr)),
|
|
70
|
+
Leptris::XML::FFI.read_pi_data(
|
|
71
|
+
utf8(NODE.leptris_pi_node_get_data(child_ptr))))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def visit_element(element, handler, dispatched)
|
|
78
|
+
qname = element.prefix ? "#{element.prefix}:#{element.name}" : element.name
|
|
79
|
+
if dispatched[:start_prefix]
|
|
80
|
+
element.namespace_definitions.each do |ns|
|
|
81
|
+
handler.start_prefix_mapping(ns.prefix.to_s, ns.href.to_s)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
if dispatched[:start_element]
|
|
85
|
+
if dispatched[:start_element] == :one_arg
|
|
86
|
+
handler.start_element(qname)
|
|
87
|
+
else
|
|
88
|
+
handler.start_element(qname, attr_pairs(element.c_ptr))
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
visit_children(element.c_ptr, element.document, handler, dispatched)
|
|
92
|
+
handler.end_element(qname) if dispatched[:end_element]
|
|
93
|
+
if dispatched[:end_prefix]
|
|
94
|
+
element.namespace_definitions.reverse_each do |ns|
|
|
95
|
+
handler.end_prefix_mapping(ns.prefix.to_s)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# [name, value] pairs in source order, read straight from the
|
|
101
|
+
# attribute chain — no Attr objects, no hash.
|
|
102
|
+
def attr_pairs(c_ptr)
|
|
103
|
+
pairs = []
|
|
104
|
+
attr = Leptris::XML::FFI.leptris_element_first_attribute(c_ptr)
|
|
105
|
+
until attr.nil? || attr.null?
|
|
106
|
+
pairs << [utf8(Leptris::XML::FFI.leptris_attribute_get_name(attr)),
|
|
107
|
+
utf8(Leptris::XML::FFI.leptris_attribute_get_value(
|
|
108
|
+
c_ptr, attr))]
|
|
109
|
+
attr = Leptris::XML::FFI.leptris_attribute_next(attr)
|
|
110
|
+
end
|
|
111
|
+
pairs
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def utf8(str)
|
|
115
|
+
str.nil? ? nil : str.force_encoding(Encoding::UTF_8)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -8,9 +8,18 @@ class Leptris::XML::SAX::Parser
|
|
|
8
8
|
|
|
9
9
|
attr_reader :document, :encoding
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# streaming: false (default) delivers from a DOM parse —
|
|
12
|
+
# leptris-ruby#95: the engine's streaming SAX transports corrupt
|
|
13
|
+
# attribute state on nested attribute-heavy shapes while the DOM
|
|
14
|
+
# parser reads the same bytes correctly. streaming: true opts
|
|
15
|
+
# back into the engine transports (bulk/callback by override
|
|
16
|
+
# weight) once the engine fix lands, or for consumers that
|
|
17
|
+
# accept the risk.
|
|
18
|
+
def initialize(handler = Leptris::XML::SAX::Document.new,
|
|
19
|
+
encoding = nil, streaming: false)
|
|
12
20
|
@document = handler
|
|
13
21
|
@encoding = encoding
|
|
22
|
+
@streaming = streaming
|
|
14
23
|
end
|
|
15
24
|
|
|
16
25
|
# Swapping the handler invalidates the memoized callback struct so
|
|
@@ -41,6 +50,11 @@ class Leptris::XML::SAX::Parser
|
|
|
41
50
|
|
|
42
51
|
def parse_memory(string)
|
|
43
52
|
string = string.dup.force_encoding("UTF-8")
|
|
53
|
+
unless @streaming
|
|
54
|
+
Leptris::XML::SAX::DomDispatch.parse(
|
|
55
|
+
@document, dispatched_kinds, string)
|
|
56
|
+
return self
|
|
57
|
+
end
|
|
44
58
|
if bulk_dispatch?
|
|
45
59
|
parse_memory_bulk(string)
|
|
46
60
|
else
|
|
@@ -133,6 +147,10 @@ class Leptris::XML::SAX::Parser
|
|
|
133
147
|
end
|
|
134
148
|
|
|
135
149
|
def parse_io(io)
|
|
150
|
+
unless @streaming
|
|
151
|
+
parse_memory(io.read)
|
|
152
|
+
return self
|
|
153
|
+
end
|
|
136
154
|
handler_struct = self.handler_struct
|
|
137
155
|
parser_ptr = Leptris::XML::FFI.leptris_sax_parser_create(
|
|
138
156
|
handler_struct.pointer, nil)
|
|
@@ -159,7 +177,11 @@ class Leptris::XML::SAX::Parser
|
|
|
159
177
|
end
|
|
160
178
|
|
|
161
179
|
def parse_file(path)
|
|
162
|
-
|
|
180
|
+
if @streaming
|
|
181
|
+
File.open(path, "r") { |f| parse_io(f) }
|
|
182
|
+
else
|
|
183
|
+
parse_memory(File.read(path))
|
|
184
|
+
end
|
|
163
185
|
end
|
|
164
186
|
|
|
165
187
|
private
|
|
@@ -69,6 +69,18 @@ class Leptris::XML::SAX::Recorder
|
|
|
69
69
|
@handle = nil
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
# Resets a finished recorder for the next document (libleptris
|
|
73
|
+
# 1.9.10, upstream #594): fresh parser state, record/arena
|
|
74
|
+
# buffers retained — a one-document-per-parse loop reuses ONE
|
|
75
|
+
# recorder without new/free churn. Records restart empty.
|
|
76
|
+
def reset
|
|
77
|
+
rc = Leptris::XML::FFI.leptris_sax_recorder_reset(@handle)
|
|
78
|
+
if rc != 0
|
|
79
|
+
raise Leptris::XML::Error, "leptris_sax_recorder_reset failed"
|
|
80
|
+
end
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
72
84
|
# Feed one chunk; records/arena reset at feed entry, so drain
|
|
73
85
|
# after every feed. Returns self.
|
|
74
86
|
def feed(chunk, final: false)
|
data/lib/leptris/xml/sax.rb
CHANGED
|
@@ -33,6 +33,25 @@ module Leptris::XML::Serialization
|
|
|
33
33
|
Leptris::XML::FFI.serialize_into_string(ffi_function, c_ptr, opts.pointer)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Display-form document serialization (libleptris 1.9.9, #129):
|
|
37
|
+
# the ext struct's indent_text hands ALL whitespace to the
|
|
38
|
+
# formatter — text and mixed content indent too. Display-oriented:
|
|
39
|
+
# the output is NOT guaranteed to round-trip byte-exactly.
|
|
40
|
+
INDENT_TEXT_EXT = begin
|
|
41
|
+
ext = Leptris::XML::FFI::SerializeExtStruct.new
|
|
42
|
+
ext[:indent_text] = 1
|
|
43
|
+
ext
|
|
44
|
+
end
|
|
45
|
+
private_constant :INDENT_TEXT_EXT
|
|
46
|
+
|
|
47
|
+
def self.to_xml_display(c_ptr, indent: 0, no_decl: false, encoding: nil)
|
|
48
|
+
opts, encoding_anchor = build_options(
|
|
49
|
+
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
50
|
+
str_ptr = Leptris::XML::FFI.leptris_document_serialize_ext(
|
|
51
|
+
c_ptr, opts.pointer, INDENT_TEXT_EXT.pointer)
|
|
52
|
+
Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
53
|
+
end
|
|
54
|
+
|
|
36
55
|
# +ffi_function+ is a bound FFI function taking
|
|
37
56
|
# (c_ptr, version, mode, ns_ptr, flags): leptris_c14n_canonicalize_ex
|
|
38
57
|
# or leptris_c14n_canonicalize_subtree_ex.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.36
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -98,6 +98,7 @@ files:
|
|
|
98
98
|
- lib/leptris/xml/pull.rb
|
|
99
99
|
- lib/leptris/xml/sax.rb
|
|
100
100
|
- lib/leptris/xml/sax/document.rb
|
|
101
|
+
- lib/leptris/xml/sax/dom_dispatch.rb
|
|
101
102
|
- lib/leptris/xml/sax/parser.rb
|
|
102
103
|
- lib/leptris/xml/sax/recorder.rb
|
|
103
104
|
- lib/leptris/xml/searchable.rb
|