leptris 1.6.0 → 1.6.2
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 +70 -0
- data/CLAUDE.md +76 -87
- data/README.adoc +80 -6
- data/Rakefile +1 -1
- data/benchmark/README.md +8 -164
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/cdata.rb +1 -0
- data/lib/leptris/xml/comment.rb +1 -0
- data/lib/leptris/xml/document.rb +29 -14
- data/lib/leptris/xml/element.rb +21 -3
- data/lib/leptris/xml/ffi.rb +3 -13
- data/lib/leptris/xml/node.rb +32 -10
- data/lib/leptris/xml/node_set.rb +6 -5
- data/lib/leptris/xml/processing_instruction.rb +2 -0
- data/lib/leptris/xml/searchable.rb +1 -1
- data/lib/leptris/xml/serialization.rb +20 -2
- data/lib/leptris/xml/text.rb +1 -0
- data/lib/leptris/xml.rb +3 -2
- metadata +1 -36
- data/TODO.impl/01-architecture.md +0 -217
- data/TODO.impl/02-ffi-declarations.md +0 -236
- data/TODO.impl/03-document-node-element-nodeset.md +0 -382
- data/TODO.impl/04-sax-parser.md +0 -203
- data/TODO.impl/05-serialize-c14n-memory-specs-css.md +0 -276
- data/docs/ARCHITECTURE.adoc +0 -559
- data/docs/BUILD.md +0 -395
- data/docs/ERROR_MESSAGES.md +0 -458
- data/docs/FFI_ARCHITECTURE.md +0 -439
- data/docs/FUTURE_VISION.md +0 -303
- data/docs/GITHUB_ACTIONS.md +0 -293
- data/docs/OPTIMIZATIONS_IMPLEMENTED.adoc +0 -459
- data/docs/PERFORMANCE.adoc +0 -668
- data/docs/PERFORMANCE.md +0 -448
- data/docs/RELEASE_NOTES_v1.0.0.md +0 -515
- data/docs/XPATH_SPEC_COMPLIANCE.md +0 -298
- data/docs/completion/leptris.bash +0 -86
- data/docs/completion/leptris.zsh +0 -74
- data/docs/man/leptris-format.1 +0 -227
- data/docs/man/leptris-parse.1 +0 -178
- data/docs/man/leptris-xpath.1 +0 -312
- data/docs/man/leptris.1 +0 -160
- data/docs/v0.9.0_PERFORMANCE_IMPROVEMENTS.md +0 -217
- data/docs/v0.9.0_RELEASE_SUMMARY.md +0 -281
- data/docs/v1.0.0_CONTINUATION_PLAN.md +0 -172
- data/docs/v1.0.0_CONTINUATION_PROMPT.md +0 -382
- data/docs/v1.0.0_SESSION_6_CONTINUATION.md +0 -434
- data/docs/v1.0.0_SESSION_6_PROMPT.md +0 -231
- data/docs/v1.0.0_STATUS_TRACKER.md +0 -224
- data/docs/v1.1.0_CONTINUATION_PLAN.md +0 -299
- data/docs/v1.1.0_FINAL_CONTINUATION_PLAN.md +0 -201
- data/docs/v1.1.0_SESSION_3_PROMPT.md +0 -223
- data/docs/v1.1.0_STATUS_TRACKER.md +0 -355
- data/docs/xml-performance.adoc +0 -115
- data/docs/xpath-performance.adoc +0 -379
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -17,6 +17,7 @@ class Leptris::XML::Document
|
|
|
17
17
|
def initialize(c_ptr = nil, freed = Freed.new(:alive))
|
|
18
18
|
@c_ptr = c_ptr
|
|
19
19
|
@freed = freed
|
|
20
|
+
@readonly = false
|
|
20
21
|
# Per-document STRONG cache for Node wrappers, keyed on c_ptr
|
|
21
22
|
# address. Every wrapper is created through Node.wrap, which is the
|
|
22
23
|
# single construction path, so the same C node always yields the
|
|
@@ -33,38 +34,38 @@ class Leptris::XML::Document
|
|
|
33
34
|
@wrapper_cache = {}
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
def self.parse(xml_or_io, options: nil)
|
|
37
|
+
def self.parse(xml_or_io, options: nil, readonly: false)
|
|
37
38
|
xml = xml_or_io.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
|
|
38
39
|
if xml.empty?
|
|
39
40
|
raise Leptris::XML::ParseError, "empty input"
|
|
40
41
|
end
|
|
41
42
|
flags = resolve_flags(options)
|
|
42
|
-
|
|
43
|
+
# The status out-param is nullable; the thread-local last error
|
|
44
|
+
# carries failure detail, and skipping the per-parse MemoryPointer
|
|
45
|
+
# is measurable on small documents.
|
|
43
46
|
raw =
|
|
44
47
|
if flags.zero?
|
|
45
|
-
Leptris::XML::FFI.leptris_parse_string(xml, xml.bytesize,
|
|
48
|
+
Leptris::XML::FFI.leptris_parse_string(xml, xml.bytesize, nil)
|
|
46
49
|
else
|
|
47
50
|
Leptris::XML::FFI.leptris_parse_string_flags(
|
|
48
|
-
xml, xml.bytesize, flags,
|
|
51
|
+
xml, xml.bytesize, flags, nil)
|
|
49
52
|
end
|
|
50
53
|
if raw.null?
|
|
51
|
-
status = status_ptr.read_int
|
|
52
54
|
raise Leptris::XML::ParseError,
|
|
53
|
-
"leptris_parse_string failed
|
|
54
|
-
Leptris::XML::FFI.
|
|
55
|
+
"leptris_parse_string failed: " +
|
|
56
|
+
Leptris::XML::FFI.leptris_last_error.to_s
|
|
55
57
|
end
|
|
56
|
-
wrap(raw)
|
|
58
|
+
wrap(raw).tap { |doc| doc.readonly! if readonly }
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
def self.parse_file(path)
|
|
60
|
-
|
|
61
|
-
raw = Leptris::XML::FFI.leptris_parse_file(path, status_ptr)
|
|
61
|
+
def self.parse_file(path, readonly: false)
|
|
62
|
+
raw = Leptris::XML::FFI.leptris_parse_file(path, nil)
|
|
62
63
|
if raw.null?
|
|
63
|
-
status = status_ptr.read_int
|
|
64
64
|
raise Leptris::XML::ParseError,
|
|
65
|
-
"leptris_parse_file failed
|
|
65
|
+
"leptris_parse_file failed: " +
|
|
66
|
+
Leptris::XML::FFI.leptris_last_error.to_s
|
|
66
67
|
end
|
|
67
|
-
wrap(raw)
|
|
68
|
+
wrap(raw).tap { |doc| doc.readonly! if readonly }
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
# Create an empty document (no root element) backed by its own memory
|
|
@@ -252,6 +253,20 @@ class Leptris::XML::Document
|
|
|
252
253
|
self
|
|
253
254
|
end
|
|
254
255
|
|
|
256
|
+
# Marks the document read-only: tree mutations raise
|
|
257
|
+
# Leptris::XML::ReadOnlyError, and read paths memoize aggressively
|
|
258
|
+
# (names, content, children, attributes) since they can never go
|
|
259
|
+
# stale. The C document is also frozen (advisory upstream). One-way.
|
|
260
|
+
def readonly!
|
|
261
|
+
Leptris::XML::FFI.leptris_document_freeze(@c_ptr)
|
|
262
|
+
@readonly = true
|
|
263
|
+
self
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def readonly?
|
|
267
|
+
@readonly == true
|
|
268
|
+
end
|
|
269
|
+
|
|
255
270
|
# The most recent error recorded against this document, or nil.
|
|
256
271
|
def last_error
|
|
257
272
|
msg = Leptris::XML::FFI.leptris_document_last_error(@c_ptr)
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -2,22 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
class Leptris::XML::Element < Leptris::XML::Node
|
|
4
4
|
def name
|
|
5
|
-
Leptris::XML::FFI.leptris_element_name(@c_ptr)
|
|
5
|
+
@name ||= Leptris::XML::FFI.leptris_element_name(@c_ptr)
|
|
6
6
|
end
|
|
7
7
|
alias_method :node_name, :name
|
|
8
8
|
|
|
9
9
|
def name=(new_name)
|
|
10
|
+
ensure_writable!
|
|
10
11
|
Leptris::XML::FFI.check_status(
|
|
11
12
|
Leptris::XML::FFI.leptris_element_set_name(@c_ptr, new_name))
|
|
12
|
-
new_name
|
|
13
|
+
@name = new_name
|
|
13
14
|
end
|
|
14
15
|
alias_method :node_name=, :name=
|
|
15
16
|
|
|
16
17
|
def content
|
|
17
|
-
|
|
18
|
+
return @content if readonly_cached?(:@content)
|
|
19
|
+
Leptris::XML::FFI.leptris_element_text(@c_ptr).tap do |text|
|
|
20
|
+
@content = text if @document&.readonly?
|
|
21
|
+
end
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
def content=(new_content)
|
|
25
|
+
ensure_writable!
|
|
21
26
|
Leptris::XML::FFI.check_status(
|
|
22
27
|
Leptris::XML::FFI.leptris_element_set_text(@c_ptr, new_content.to_s))
|
|
23
28
|
new_content
|
|
@@ -30,6 +35,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
30
35
|
alias_method :get_attribute, :[]
|
|
31
36
|
|
|
32
37
|
def []=(key, value)
|
|
38
|
+
ensure_writable!
|
|
33
39
|
Leptris::XML::FFI.check_status(
|
|
34
40
|
Leptris::XML::FFI.leptris_element_set_attribute(@c_ptr, key.to_s, value.to_s))
|
|
35
41
|
value
|
|
@@ -42,6 +48,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
42
48
|
alias_method :has_attribute?, :key?
|
|
43
49
|
|
|
44
50
|
def remove_attribute(name)
|
|
51
|
+
ensure_writable!
|
|
45
52
|
Leptris::XML::FFI.check_status(
|
|
46
53
|
Leptris::XML::FFI.leptris_element_remove_attribute(@c_ptr, name.to_s))
|
|
47
54
|
self
|
|
@@ -72,8 +79,10 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
72
79
|
end
|
|
73
80
|
|
|
74
81
|
def attributes
|
|
82
|
+
return @attributes if readonly_cached?(:@attributes)
|
|
75
83
|
result = {}
|
|
76
84
|
each_attribute { |attr| result[attr.name] = attr }
|
|
85
|
+
@attributes = result if @document&.readonly?
|
|
77
86
|
result
|
|
78
87
|
end
|
|
79
88
|
|
|
@@ -88,30 +97,35 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
88
97
|
end
|
|
89
98
|
|
|
90
99
|
def prepend_child(node)
|
|
100
|
+
ensure_writable!
|
|
91
101
|
Leptris::XML::FFI.check_status(
|
|
92
102
|
Leptris::XML::FFI.leptris_element_prepend_child(@c_ptr, node.c_ptr))
|
|
93
103
|
node
|
|
94
104
|
end
|
|
95
105
|
|
|
96
106
|
def add_next_sibling(node)
|
|
107
|
+
ensure_writable!
|
|
97
108
|
Leptris::XML::FFI.check_status(
|
|
98
109
|
Leptris::XML::FFI.leptris_element_insert_after(@c_ptr, node.c_ptr))
|
|
99
110
|
node
|
|
100
111
|
end
|
|
101
112
|
|
|
102
113
|
def add_previous_sibling(node)
|
|
114
|
+
ensure_writable!
|
|
103
115
|
Leptris::XML::FFI.check_status(
|
|
104
116
|
Leptris::XML::FFI.leptris_element_insert_before(@c_ptr, node.c_ptr))
|
|
105
117
|
node
|
|
106
118
|
end
|
|
107
119
|
|
|
108
120
|
def remove_child(node)
|
|
121
|
+
ensure_writable!
|
|
109
122
|
Leptris::XML::FFI.check_status(
|
|
110
123
|
Leptris::XML::FFI.leptris_element_remove_child(@c_ptr, node.c_ptr))
|
|
111
124
|
node
|
|
112
125
|
end
|
|
113
126
|
|
|
114
127
|
def children=(node_or_nodes)
|
|
128
|
+
ensure_writable!
|
|
115
129
|
# Remove existing children, then attach the new ones in source order.
|
|
116
130
|
Leptris::XML::FFI.check_status(
|
|
117
131
|
Leptris::XML::FFI.leptris_element_remove_children(@c_ptr))
|
|
@@ -168,6 +182,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
168
182
|
alias_method :clone, :dup
|
|
169
183
|
|
|
170
184
|
def add_child(node_or_markup)
|
|
185
|
+
ensure_writable!
|
|
171
186
|
case node_or_markup
|
|
172
187
|
when Leptris::XML::Node
|
|
173
188
|
Leptris::XML::FFI.check_status(
|
|
@@ -243,6 +258,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
243
258
|
alias_method :c14n, :canonicalize
|
|
244
259
|
|
|
245
260
|
def add_namespace_definition(prefix, href)
|
|
261
|
+
ensure_writable!
|
|
246
262
|
Leptris::XML::FFI.check_status(
|
|
247
263
|
Leptris::XML::FFI.leptris_element_add_namespace_definition(
|
|
248
264
|
@c_ptr, prefix.to_s, href.to_s))
|
|
@@ -251,12 +267,14 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
251
267
|
alias_method :add_namespace, :add_namespace_definition
|
|
252
268
|
|
|
253
269
|
def default_namespace=(href)
|
|
270
|
+
ensure_writable!
|
|
254
271
|
Leptris::XML::FFI.check_status(
|
|
255
272
|
Leptris::XML::FFI.leptris_element_set_default_namespace(@c_ptr, href.to_s))
|
|
256
273
|
href
|
|
257
274
|
end
|
|
258
275
|
|
|
259
276
|
def remove_namespace_definition(prefix)
|
|
277
|
+
ensure_writable!
|
|
260
278
|
Leptris::XML::FFI.check_status(
|
|
261
279
|
Leptris::XML::FFI.leptris_element_remove_namespace_definition(@c_ptr, prefix.to_s))
|
|
262
280
|
self
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -341,8 +341,6 @@ module Leptris
|
|
|
341
341
|
[:leptris_xpath_result], :int
|
|
342
342
|
attach_function :leptris_xpath_result_count,
|
|
343
343
|
[:leptris_xpath_result], :size_t
|
|
344
|
-
attach_function :leptris_xpath_result_get,
|
|
345
|
-
[:leptris_xpath_result, :size_t], :leptris_element
|
|
346
344
|
# Mixed nodesets (v1.1.0): unlike result_get (elements only),
|
|
347
345
|
# get_node returns the node whatever its kind; node_kind reports
|
|
348
346
|
# which (element / synthetic attribute / text / other).
|
|
@@ -359,10 +357,8 @@ module Leptris
|
|
|
359
357
|
# a heap string the library frees.
|
|
360
358
|
attach_function :leptris_xpath_register_function,
|
|
361
359
|
[:leptris_document, :string, :pointer, :pointer], :leptris_status
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
# v1.3.0: copies ALL node kinds (the legacy accessor drops
|
|
365
|
-
# non-element entries) and can also report per-entry kinds.
|
|
360
|
+
# v1.3.0: copies ALL node kinds in one call, optionally
|
|
361
|
+
# reporting per-entry kinds.
|
|
366
362
|
attach_function :leptris_xpath_result_get_nodes_ex,
|
|
367
363
|
[:leptris_xpath_result, :pointer, :pointer, :size_t], :size_t
|
|
368
364
|
attach_function :leptris_xpath_result_boolean,
|
|
@@ -393,10 +389,8 @@ module Leptris
|
|
|
393
389
|
[], :leptris_xpath_ns_set
|
|
394
390
|
attach_function :leptris_xpath_ns_set_free,
|
|
395
391
|
[:leptris_xpath_ns_set], :void
|
|
396
|
-
attach_function :leptris_xpath_ns_set_add,
|
|
397
|
-
[:leptris_xpath_ns_set, :string, :string], :leptris_status
|
|
398
392
|
# One-call constructor: flat array of 2*pair_count alternating
|
|
399
|
-
# prefix/URI strings
|
|
393
|
+
# prefix/URI strings.
|
|
400
394
|
attach_function :leptris_xpath_ns_set_new_from_pairs,
|
|
401
395
|
[:pointer, :size_t], :leptris_xpath_ns_set
|
|
402
396
|
attach_function :leptris_xpath_eval_ns,
|
|
@@ -457,10 +451,6 @@ module Leptris
|
|
|
457
451
|
|
|
458
452
|
attach_function :leptris_document_serialize,
|
|
459
453
|
[:leptris_document, :pointer], :pointer
|
|
460
|
-
# Legacy simple serializer, declared retroactively in v1.1.0.
|
|
461
|
-
# Prefer leptris_document_serialize; bound for API completeness.
|
|
462
|
-
attach_function :leptris_serialize_document,
|
|
463
|
-
[:leptris_document], :pointer
|
|
464
454
|
attach_function :leptris_document_get_dtd,
|
|
465
455
|
[:leptris_document], :pointer
|
|
466
456
|
attach_function :leptris_element_serialize,
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
class Leptris::XML::Node
|
|
4
4
|
attr_reader :c_ptr, :document
|
|
5
5
|
|
|
6
|
-
def initialize(c_ptr, document, parent: nil)
|
|
6
|
+
def initialize(c_ptr, document, parent: nil, node_type: nil)
|
|
7
7
|
@c_ptr = c_ptr
|
|
8
8
|
@document = document
|
|
9
9
|
@parent = parent
|
|
10
|
+
# wrap() already calls leptris_node_get_type for dispatch; reusing
|
|
11
|
+
# the result makes every predicate and #type call FFI-free.
|
|
12
|
+
@node_type = node_type
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
def self.wrap(c_ptr, document, parent: nil)
|
|
@@ -18,20 +21,21 @@ class Leptris::XML::Node
|
|
|
18
21
|
return cached
|
|
19
22
|
end
|
|
20
23
|
|
|
24
|
+
node_type = Leptris::XML::FFI.leptris_node_get_type(c_ptr)
|
|
21
25
|
node =
|
|
22
|
-
case
|
|
26
|
+
case node_type
|
|
23
27
|
when Leptris::XML::FFI::NODE_ELEMENT
|
|
24
|
-
Leptris::XML::Element.new(c_ptr, document, parent: parent)
|
|
28
|
+
Leptris::XML::Element.new(c_ptr, document, parent: parent, node_type: node_type)
|
|
25
29
|
when Leptris::XML::FFI::NODE_TEXT
|
|
26
|
-
Leptris::XML::Text.new(c_ptr, document, parent: parent)
|
|
30
|
+
Leptris::XML::Text.new(c_ptr, document, parent: parent, node_type: node_type)
|
|
27
31
|
when Leptris::XML::FFI::NODE_COMMENT
|
|
28
|
-
Leptris::XML::Comment.new(c_ptr, document, parent: parent)
|
|
32
|
+
Leptris::XML::Comment.new(c_ptr, document, parent: parent, node_type: node_type)
|
|
29
33
|
when Leptris::XML::FFI::NODE_CDATA
|
|
30
|
-
Leptris::XML::CDATA.new(c_ptr, document, parent: parent)
|
|
34
|
+
Leptris::XML::CDATA.new(c_ptr, document, parent: parent, node_type: node_type)
|
|
31
35
|
when Leptris::XML::FFI::NODE_PI
|
|
32
|
-
Leptris::XML::ProcessingInstruction.new(c_ptr, document, parent: parent)
|
|
36
|
+
Leptris::XML::ProcessingInstruction.new(c_ptr, document, parent: parent, node_type: node_type)
|
|
33
37
|
else
|
|
34
|
-
new(c_ptr, document, parent: parent)
|
|
38
|
+
new(c_ptr, document, parent: parent, node_type: node_type)
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
document.wrapper_cache[c_ptr.address] = node if document
|
|
@@ -58,7 +62,7 @@ class Leptris::XML::Node
|
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
def type
|
|
61
|
-
Leptris::XML::FFI.leptris_node_get_type(@c_ptr)
|
|
65
|
+
@node_type ||= Leptris::XML::FFI.leptris_node_get_type(@c_ptr)
|
|
62
66
|
end
|
|
63
67
|
alias_method :node_type, :type
|
|
64
68
|
|
|
@@ -78,6 +82,14 @@ class Leptris::XML::Node
|
|
|
78
82
|
Leptris::XML::Node.wrap(ptr, @document)
|
|
79
83
|
end
|
|
80
84
|
|
|
85
|
+
# Raises ReadOnlyError when the owning document was marked readonly.
|
|
86
|
+
def ensure_writable!
|
|
87
|
+
if @document&.readonly?
|
|
88
|
+
raise Leptris::XML::ReadOnlyError,
|
|
89
|
+
"document is readonly — mutation attempted on #{inspect}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
81
93
|
def line
|
|
82
94
|
Leptris::XML::FFI.leptris_node_line(@c_ptr)
|
|
83
95
|
end
|
|
@@ -95,13 +107,18 @@ class Leptris::XML::Node
|
|
|
95
107
|
end
|
|
96
108
|
|
|
97
109
|
def children
|
|
110
|
+
# Immutable in readonly mode: the walk (first_child + one
|
|
111
|
+
# next_sibling per child) plus wrapper construction is paid once.
|
|
112
|
+
return @children if readonly_cached?(:@children)
|
|
98
113
|
nodes = []
|
|
99
114
|
ptr = Leptris::XML::FFI.leptris_node_first_child(@c_ptr)
|
|
100
115
|
until ptr.nil? || ptr.null?
|
|
101
116
|
nodes << Leptris::XML::Node.wrap(ptr, @document, parent: as_element_or_self)
|
|
102
117
|
ptr = Leptris::XML::FFI.leptris_node_next_sibling(ptr)
|
|
103
118
|
end
|
|
104
|
-
Leptris::XML::NodeSet.new(@document, nodes)
|
|
119
|
+
result = Leptris::XML::NodeSet.new(@document, nodes)
|
|
120
|
+
@children = result if @document&.readonly?
|
|
121
|
+
result
|
|
105
122
|
end
|
|
106
123
|
|
|
107
124
|
def next_sibling
|
|
@@ -150,6 +167,7 @@ class Leptris::XML::Node
|
|
|
150
167
|
end
|
|
151
168
|
|
|
152
169
|
def unlink
|
|
170
|
+
ensure_writable!
|
|
153
171
|
Leptris::XML::FFI.check_status(
|
|
154
172
|
Leptris::XML::FFI.leptris_node_unlink(@c_ptr))
|
|
155
173
|
@parent = nil
|
|
@@ -212,6 +230,10 @@ class Leptris::XML::Node
|
|
|
212
230
|
|
|
213
231
|
protected
|
|
214
232
|
|
|
233
|
+
def readonly_cached?(ivar)
|
|
234
|
+
@document&.readonly? && instance_variable_defined?(ivar)
|
|
235
|
+
end
|
|
236
|
+
|
|
215
237
|
def as_element_or_self
|
|
216
238
|
is_a?(Leptris::XML::Element) ? self : nil
|
|
217
239
|
end
|
data/lib/leptris/xml/node_set.rb
CHANGED
|
@@ -63,15 +63,16 @@ class Leptris::XML::NodeSet
|
|
|
63
63
|
def each
|
|
64
64
|
return enum_for(:each) unless block_given?
|
|
65
65
|
if @result_ptr
|
|
66
|
-
# Batch-fetch all node pointers in one FFI call
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# Node.wrap, so
|
|
66
|
+
# Batch-fetch all node pointers in one FFI call
|
|
67
|
+
# (leptris_xpath_result_get_nodes_ex copies every node kind, not
|
|
68
|
+
# just elements) and wrap each. Wrappers are cached per-Document
|
|
69
|
+
# via Node.wrap, so re-iteration of the same NodeSet hits it.
|
|
70
70
|
n = length
|
|
71
71
|
if n > 0
|
|
72
72
|
buf = ::FFI::MemoryPointer.new(:pointer, n)
|
|
73
73
|
begin
|
|
74
|
-
copied = Leptris::XML::FFI.
|
|
74
|
+
copied = Leptris::XML::FFI.leptris_xpath_result_get_nodes_ex(
|
|
75
|
+
@result_ptr, buf, nil, n)
|
|
75
76
|
copied.times do |i|
|
|
76
77
|
ptr = buf.get_pointer(i * ::FFI.type_size(:pointer))
|
|
77
78
|
next if ptr.null?
|
|
@@ -11,12 +11,14 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def target=(new_target)
|
|
14
|
+
ensure_writable!
|
|
14
15
|
Leptris::XML::FFI.check_status(
|
|
15
16
|
Leptris::XML::FFI.leptris_pi_node_set_target(@c_ptr, new_target.to_s))
|
|
16
17
|
new_target
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def data=(new_data)
|
|
21
|
+
ensure_writable!
|
|
20
22
|
Leptris::XML::FFI.check_status(
|
|
21
23
|
Leptris::XML::FFI.leptris_pi_node_set_data(@c_ptr, new_data.to_s))
|
|
22
24
|
new_data
|
|
@@ -89,7 +89,7 @@ module Leptris::XML::Searchable
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
# Shared by Searchable#xpath and Leptris::XML::XPath (compiled
|
|
92
|
-
# expressions): wraps a raw
|
|
92
|
+
# expressions): wraps a raw XPathResult pointer into the
|
|
93
93
|
# Ruby-typed result and frees the C handle.
|
|
94
94
|
def self.wrap_xpath_result(document, result_ptr)
|
|
95
95
|
type = Leptris::XML::FFI.leptris_xpath_result_type(result_ptr)
|
|
@@ -6,11 +6,29 @@
|
|
|
6
6
|
# struct, the encoding anchor, the C call, and the read-and-free all
|
|
7
7
|
# live here.
|
|
8
8
|
module Leptris::XML::Serialization
|
|
9
|
+
# The default options struct (indent 0, declaration on, no encoding
|
|
10
|
+
# override) is constant: build it once and reuse its pointer on the
|
|
11
|
+
# fast path instead of allocating a fresh struct per call.
|
|
12
|
+
DEFAULT_OPTIONS, _default_anchor = begin
|
|
13
|
+
opts = Leptris::XML::FFI::SerializeOptions.new
|
|
14
|
+
opts[:indent] = 0
|
|
15
|
+
opts[:xml_declaration] = 1
|
|
16
|
+
opts[:encoding] = nil
|
|
17
|
+
[opts, nil]
|
|
18
|
+
end
|
|
19
|
+
private_constant :DEFAULT_OPTIONS
|
|
20
|
+
|
|
9
21
|
# +ffi_function+ is a bound FFI function taking (c_ptr, opts_ptr):
|
|
10
22
|
# leptris_document_serialize or leptris_element_serialize.
|
|
11
23
|
def self.to_xml(ffi_function, c_ptr, indent: 0, no_decl: false, encoding: nil)
|
|
12
|
-
opts
|
|
13
|
-
|
|
24
|
+
opts =
|
|
25
|
+
if indent.to_i.zero? && !no_decl && encoding.nil?
|
|
26
|
+
DEFAULT_OPTIONS
|
|
27
|
+
else
|
|
28
|
+
opts, _encoding_anchor = build_options(
|
|
29
|
+
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
30
|
+
opts
|
|
31
|
+
end
|
|
14
32
|
str_ptr = ffi_function.call(c_ptr, opts.pointer)
|
|
15
33
|
Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
16
34
|
end
|
data/lib/leptris/xml/text.rb
CHANGED
data/lib/leptris/xml.rb
CHANGED
|
@@ -27,8 +27,8 @@ 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)
|
|
31
|
-
Document.parse(xml_or_io, options: options)
|
|
30
|
+
def self.parse(xml_or_io, options: nil, readonly: false)
|
|
31
|
+
Document.parse(xml_or_io, options: options, readonly: readonly)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def self.parse_file(path)
|
|
@@ -37,6 +37,7 @@ module Leptris
|
|
|
37
37
|
|
|
38
38
|
class Error < StandardError; end
|
|
39
39
|
class ParseError < Error; end
|
|
40
|
+
class ReadOnlyError < Error; end
|
|
40
41
|
class XPathError < Error; end
|
|
41
42
|
class UseAfterFreeError < Error; end
|
|
42
43
|
end
|
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.6.
|
|
4
|
+
version: 1.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -70,43 +70,8 @@ files:
|
|
|
70
70
|
- LICENSE.md
|
|
71
71
|
- README.adoc
|
|
72
72
|
- Rakefile
|
|
73
|
-
- TODO.impl/01-architecture.md
|
|
74
|
-
- TODO.impl/02-ffi-declarations.md
|
|
75
|
-
- TODO.impl/03-document-node-element-nodeset.md
|
|
76
|
-
- TODO.impl/04-sax-parser.md
|
|
77
|
-
- TODO.impl/05-serialize-c14n-memory-specs-css.md
|
|
78
73
|
- benchmark/README.md
|
|
79
74
|
- benchmark/leptris_vs_nokogiri.rb
|
|
80
|
-
- docs/ARCHITECTURE.adoc
|
|
81
|
-
- docs/BUILD.md
|
|
82
|
-
- docs/ERROR_MESSAGES.md
|
|
83
|
-
- docs/FFI_ARCHITECTURE.md
|
|
84
|
-
- docs/FUTURE_VISION.md
|
|
85
|
-
- docs/GITHUB_ACTIONS.md
|
|
86
|
-
- docs/OPTIMIZATIONS_IMPLEMENTED.adoc
|
|
87
|
-
- docs/PERFORMANCE.adoc
|
|
88
|
-
- docs/PERFORMANCE.md
|
|
89
|
-
- docs/RELEASE_NOTES_v1.0.0.md
|
|
90
|
-
- docs/XPATH_SPEC_COMPLIANCE.md
|
|
91
|
-
- docs/completion/leptris.bash
|
|
92
|
-
- docs/completion/leptris.zsh
|
|
93
|
-
- docs/man/leptris-format.1
|
|
94
|
-
- docs/man/leptris-parse.1
|
|
95
|
-
- docs/man/leptris-xpath.1
|
|
96
|
-
- docs/man/leptris.1
|
|
97
|
-
- docs/v0.9.0_PERFORMANCE_IMPROVEMENTS.md
|
|
98
|
-
- docs/v0.9.0_RELEASE_SUMMARY.md
|
|
99
|
-
- docs/v1.0.0_CONTINUATION_PLAN.md
|
|
100
|
-
- docs/v1.0.0_CONTINUATION_PROMPT.md
|
|
101
|
-
- docs/v1.0.0_SESSION_6_CONTINUATION.md
|
|
102
|
-
- docs/v1.0.0_SESSION_6_PROMPT.md
|
|
103
|
-
- docs/v1.0.0_STATUS_TRACKER.md
|
|
104
|
-
- docs/v1.1.0_CONTINUATION_PLAN.md
|
|
105
|
-
- docs/v1.1.0_FINAL_CONTINUATION_PLAN.md
|
|
106
|
-
- docs/v1.1.0_SESSION_3_PROMPT.md
|
|
107
|
-
- docs/v1.1.0_STATUS_TRACKER.md
|
|
108
|
-
- docs/xml-performance.adoc
|
|
109
|
-
- docs/xpath-performance.adoc
|
|
110
75
|
- leptris.gemspec
|
|
111
76
|
- lib/leptris.rb
|
|
112
77
|
- lib/leptris/version.rb
|