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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -0
  3. data/CLAUDE.md +76 -87
  4. data/README.adoc +80 -6
  5. data/Rakefile +1 -1
  6. data/benchmark/README.md +8 -164
  7. data/lib/leptris/version.rb +1 -1
  8. data/lib/leptris/xml/cdata.rb +1 -0
  9. data/lib/leptris/xml/comment.rb +1 -0
  10. data/lib/leptris/xml/document.rb +29 -14
  11. data/lib/leptris/xml/element.rb +21 -3
  12. data/lib/leptris/xml/ffi.rb +3 -13
  13. data/lib/leptris/xml/node.rb +32 -10
  14. data/lib/leptris/xml/node_set.rb +6 -5
  15. data/lib/leptris/xml/processing_instruction.rb +2 -0
  16. data/lib/leptris/xml/searchable.rb +1 -1
  17. data/lib/leptris/xml/serialization.rb +20 -2
  18. data/lib/leptris/xml/text.rb +1 -0
  19. data/lib/leptris/xml.rb +3 -2
  20. metadata +1 -36
  21. data/TODO.impl/01-architecture.md +0 -217
  22. data/TODO.impl/02-ffi-declarations.md +0 -236
  23. data/TODO.impl/03-document-node-element-nodeset.md +0 -382
  24. data/TODO.impl/04-sax-parser.md +0 -203
  25. data/TODO.impl/05-serialize-c14n-memory-specs-css.md +0 -276
  26. data/docs/ARCHITECTURE.adoc +0 -559
  27. data/docs/BUILD.md +0 -395
  28. data/docs/ERROR_MESSAGES.md +0 -458
  29. data/docs/FFI_ARCHITECTURE.md +0 -439
  30. data/docs/FUTURE_VISION.md +0 -303
  31. data/docs/GITHUB_ACTIONS.md +0 -293
  32. data/docs/OPTIMIZATIONS_IMPLEMENTED.adoc +0 -459
  33. data/docs/PERFORMANCE.adoc +0 -668
  34. data/docs/PERFORMANCE.md +0 -448
  35. data/docs/RELEASE_NOTES_v1.0.0.md +0 -515
  36. data/docs/XPATH_SPEC_COMPLIANCE.md +0 -298
  37. data/docs/completion/leptris.bash +0 -86
  38. data/docs/completion/leptris.zsh +0 -74
  39. data/docs/man/leptris-format.1 +0 -227
  40. data/docs/man/leptris-parse.1 +0 -178
  41. data/docs/man/leptris-xpath.1 +0 -312
  42. data/docs/man/leptris.1 +0 -160
  43. data/docs/v0.9.0_PERFORMANCE_IMPROVEMENTS.md +0 -217
  44. data/docs/v0.9.0_RELEASE_SUMMARY.md +0 -281
  45. data/docs/v1.0.0_CONTINUATION_PLAN.md +0 -172
  46. data/docs/v1.0.0_CONTINUATION_PROMPT.md +0 -382
  47. data/docs/v1.0.0_SESSION_6_CONTINUATION.md +0 -434
  48. data/docs/v1.0.0_SESSION_6_PROMPT.md +0 -231
  49. data/docs/v1.0.0_STATUS_TRACKER.md +0 -224
  50. data/docs/v1.1.0_CONTINUATION_PLAN.md +0 -299
  51. data/docs/v1.1.0_FINAL_CONTINUATION_PLAN.md +0 -201
  52. data/docs/v1.1.0_SESSION_3_PROMPT.md +0 -223
  53. data/docs/v1.1.0_STATUS_TRACKER.md +0 -355
  54. data/docs/xml-performance.adoc +0 -115
  55. data/docs/xpath-performance.adoc +0 -379
@@ -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
- status_ptr = ::FFI::MemoryPointer.new(:int)
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, status_ptr)
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, status_ptr)
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 (status=#{status}): " +
54
- Leptris::XML::FFI.status_message(status)
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
- status_ptr = ::FFI::MemoryPointer.new(:int)
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 (status=#{status})"
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)
@@ -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
- Leptris::XML::FFI.leptris_element_text(@c_ptr)
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
@@ -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
- attach_function :leptris_xpath_result_get_nodes,
363
- [:leptris_xpath_result, :pointer, :size_t], :size_t
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 (v1.3.0).
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,
@@ -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 Leptris::XML::FFI.leptris_node_get_type(c_ptr)
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
@@ -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 (leptris_xpath_result_get_nodes,
67
- # libleptris v0.11.4) and wrap each. Saves N-1 FFI calls vs the per-index
68
- # leptris_xpath_result_get loop. Wrappers are still cached per-Document via
69
- # Node.wrap, so a re-iteration of the same NodeSet hits the cache.
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.leptris_xpath_result_get_nodes(@result_ptr, buf, n)
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 TaurusXPathResult pointer into the
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, _encoding_anchor = build_options(
13
- indent: indent, no_decl: no_decl, encoding: encoding)
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
@@ -8,6 +8,7 @@ class Leptris::XML::Text < Leptris::XML::Node
8
8
  end
9
9
 
10
10
  def content=(new_content)
11
+ ensure_writable!
11
12
  Leptris::XML::FFI.check_status(
12
13
  Leptris::XML::FFI.leptris_text_node_set_content(@c_ptr, new_content.to_s))
13
14
  new_content
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.0
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