leptris 1.9.312.0-aarch64-linux → 1.9.312.1-aarch64-linux
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 +18 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/builder.rb +169 -0
- data/lib/leptris/xml/document.rb +14 -0
- data/lib/leptris/xml/element.rb +11 -0
- data/lib/leptris/xml.rb +1 -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: 2eac674bc556fd667fc8e3bd72d92f73c1c939637dd46782d8fbc3c6c39bf025
|
|
4
|
+
data.tar.gz: 998089efd6146235e339bbbe0157d8856562fe1edbed65a8e26237216cb1e3d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eead31d67e4f604af0e7a54a4335c63414f4fc5175abfcae74c6e77494561660e85e41e24cac24c4561b0298625c36d8553d2e6ef3f193ba8de0220796e32a5e
|
|
7
|
+
data.tar.gz: 1cf2990c2f592841b0e6901da330db167d596c3f8e30c87135b831690c063390b41c0be554fa371de9afce5fce006c7fe316a33c9060dffd7024cf2172df7505
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## [1.9.312.1] - 2026-10-07
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Document#build / Element#build — the builder face (#374 lever
|
|
6
|
+
2)**: markup-accumulating construction. The block turns into
|
|
7
|
+
well-formed markup in Ruby (method_missing element dispatch
|
|
8
|
+
with attrs/text, `#text`/`#cdata`/`#comment`/`#raw`, one-pass
|
|
9
|
+
escaping of `& < > "`), and the flush performs ONE native
|
|
10
|
+
crossing for the whole subtree — parse + `root=` adoption for
|
|
11
|
+
document sinks (exactly one root, the parser enforces it;
|
|
12
|
+
returns the installed root wrapper), `append_markup` for
|
|
13
|
+
element sinks (returns the added NodeSet). Measured DSL-vs-DSL
|
|
14
|
+
under load (interleaved CPU-time medians, build(500)):
|
|
15
|
+
leptris builder 2.68 ms vs nokogiri's builder 5.29 ms —
|
|
16
|
+
**~2.0x** — and ~1.2x over our own object face (3.19 ms,
|
|
17
|
+
itself ~1.7x nokogiri after the 1.9.311.3 trim).
|
|
18
|
+
|
|
1
19
|
## [1.9.312.0] - 2026-10-07
|
|
2
20
|
|
|
3
21
|
### Added — engine sync (libleptris 1.9.312)
|
data/lib/leptris/version.rb
CHANGED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Markup-accumulating subtree construction (#374 lever 2): the
|
|
4
|
+
# builder turns a Ruby block into well-formed markup in a plain
|
|
5
|
+
# String (Ruby-side cost only), and the flush performs ONE native
|
|
6
|
+
# crossing — parse + attach — for the whole subtree. The
|
|
7
|
+
# per-element FFI crossing that dominated the object-face build
|
|
8
|
+
# profile collapses to one per #build call.
|
|
9
|
+
#
|
|
10
|
+
# doc.build do |b|
|
|
11
|
+
# b.catalog(id: "c") do
|
|
12
|
+
# b.item(id: 7) { b.name "Item 7" }
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# Element names arriving through method_missing are identifier-
|
|
17
|
+
# shaped by construction (Ruby method names cannot carry markup
|
|
18
|
+
# metacharacters); the explicit #element path checks NAME_RE.
|
|
19
|
+
# Text and attribute values escape in one pass per string.
|
|
20
|
+
class Leptris::XML::Builder
|
|
21
|
+
NAME_RE = /\A[a-zA-Z_][\w.-]*\z/
|
|
22
|
+
|
|
23
|
+
ESCAPE = {
|
|
24
|
+
"&" => "&",
|
|
25
|
+
"<" => "<",
|
|
26
|
+
">" => ">",
|
|
27
|
+
'"' => """,
|
|
28
|
+
}.freeze
|
|
29
|
+
ESCAPE_RE = /[&<>"]/
|
|
30
|
+
private_constant :ESCAPE, :ESCAPE_RE
|
|
31
|
+
|
|
32
|
+
# +sink+ receives the flushed markup (a Document or Element).
|
|
33
|
+
def initialize(sink)
|
|
34
|
+
@sink = sink
|
|
35
|
+
@buf = +""
|
|
36
|
+
@depth = 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def element(name, attributes = nil)
|
|
40
|
+
name = name.to_s
|
|
41
|
+
unless name =~ NAME_RE
|
|
42
|
+
raise ArgumentError,
|
|
43
|
+
"element name must match #{NAME_RE.inspect}, got #{name.inspect}"
|
|
44
|
+
end
|
|
45
|
+
@buf << "<" << name
|
|
46
|
+
write_attributes(attributes) if attributes
|
|
47
|
+
if block_given?
|
|
48
|
+
@buf << ">"
|
|
49
|
+
@depth += 1
|
|
50
|
+
begin
|
|
51
|
+
yield self
|
|
52
|
+
ensure
|
|
53
|
+
@depth -= 1
|
|
54
|
+
end
|
|
55
|
+
@buf << "</" << name << ">"
|
|
56
|
+
else
|
|
57
|
+
@buf << "/>"
|
|
58
|
+
end
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def text(content)
|
|
63
|
+
@buf << escape_text(content.to_s)
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def cdata(content)
|
|
68
|
+
@buf << "<![CDATA[" << content.to_s << "]]>"
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def comment(content)
|
|
73
|
+
@buf << "<!--" << escape_text(content.to_s) << "-->"
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Verbatim markup — the escape hatch for pre-serialized
|
|
78
|
+
# fragments; the caller owns well-formedness.
|
|
79
|
+
def raw(markup)
|
|
80
|
+
@buf << markup.to_s
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def method_missing(name, *args, &block)
|
|
85
|
+
# Method names are identifier-shaped: they cannot carry <, >,
|
|
86
|
+
# &, quotes, or whitespace, so they need no NAME_RE round-trip
|
|
87
|
+
# (trailing = / ? / ! are legal Ruby but never legal XML
|
|
88
|
+
# name tails here — reject them explicitly).
|
|
89
|
+
return super if name.to_s.end_with?("=", "?", "!")
|
|
90
|
+
case args.length
|
|
91
|
+
when 0
|
|
92
|
+
element(name.to_s, &block)
|
|
93
|
+
when 1
|
|
94
|
+
arg = args[0]
|
|
95
|
+
if arg.is_a?(Hash)
|
|
96
|
+
element(name.to_s, arg, &block)
|
|
97
|
+
else
|
|
98
|
+
element(name.to_s) { |b| b.text(arg); block&.call(b) }
|
|
99
|
+
end
|
|
100
|
+
when 2
|
|
101
|
+
attrs, txt = args
|
|
102
|
+
return super unless attrs.is_a?(Hash)
|
|
103
|
+
element(name.to_s, attrs) { |b| b.text(txt); block&.call(b) }
|
|
104
|
+
else
|
|
105
|
+
super
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def respond_to_missing?(name, include_private = false)
|
|
110
|
+
!name.to_s.end_with?("=", "?", "!") || super
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# One native crossing for the accumulated subtree: the flush
|
|
114
|
+
# parses + attaches. Document sinks take exactly one root
|
|
115
|
+
# (set_root_ex adopts the parsed root); element sinks append
|
|
116
|
+
# every fragment child.
|
|
117
|
+
def flush
|
|
118
|
+
unless @depth.zero?
|
|
119
|
+
raise Leptris::XML::Error,
|
|
120
|
+
"unbalanced builder — unclosed elements remain (depth #{@depth})"
|
|
121
|
+
end
|
|
122
|
+
markup = @buf
|
|
123
|
+
return @sink if markup.empty?
|
|
124
|
+
case @sink
|
|
125
|
+
when Leptris::XML::Document
|
|
126
|
+
parsed = Leptris::XML::Document.parse(markup)
|
|
127
|
+
root = parsed.root
|
|
128
|
+
if root.nil?
|
|
129
|
+
raise Leptris::XML::Error,
|
|
130
|
+
"builder markup produced no root element"
|
|
131
|
+
end
|
|
132
|
+
@sink.root = root
|
|
133
|
+
@buf = +""
|
|
134
|
+
# The INSTALLED root — the same wrapper #root now yields
|
|
135
|
+
# (root= returns the parsed source, not the adoption copy).
|
|
136
|
+
@sink.root
|
|
137
|
+
when Leptris::XML::Element
|
|
138
|
+
added = @sink.add_child(markup)
|
|
139
|
+
@buf = +""
|
|
140
|
+
added
|
|
141
|
+
else
|
|
142
|
+
raise ArgumentError,
|
|
143
|
+
"builder sink must be a Document or Element, got #{@sink.class}"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def write_attributes(attributes)
|
|
150
|
+
case attributes
|
|
151
|
+
when Hash
|
|
152
|
+
attributes.each do |k, v|
|
|
153
|
+
@buf << " " << k.to_s << '="' << escape_attr(v) << '"'
|
|
154
|
+
end
|
|
155
|
+
when nil then # nothing
|
|
156
|
+
else
|
|
157
|
+
raise ArgumentError,
|
|
158
|
+
"attributes must be a Hash, got #{attributes.class}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def escape_text(value)
|
|
163
|
+
value.gsub(ESCAPE_RE, ESCAPE)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def escape_attr(value)
|
|
167
|
+
value.to_s.gsub(ESCAPE_RE, ESCAPE)
|
|
168
|
+
end
|
|
169
|
+
end
|
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -232,6 +232,20 @@ class Leptris::XML::Document
|
|
|
232
232
|
wrap(raw)
|
|
233
233
|
end
|
|
234
234
|
|
|
235
|
+
# Markup-accumulating construction (#374 lever 2): the block
|
|
236
|
+
# accumulates well-formed markup in Ruby; the flush performs
|
|
237
|
+
# ONE native crossing for the whole subtree. Exactly one root
|
|
238
|
+
# element — the parsed root is adopted with set_root_ex.
|
|
239
|
+
# Returns the flush's value (the installed root element).
|
|
240
|
+
#
|
|
241
|
+
# doc = Leptris::XML::Document.create
|
|
242
|
+
# doc.build { |b| b.catalog(id: "c") { b.item { b.name "x" } } }
|
|
243
|
+
def build
|
|
244
|
+
builder = Leptris::XML::Builder.new(self)
|
|
245
|
+
yield builder
|
|
246
|
+
builder.flush
|
|
247
|
+
end
|
|
248
|
+
|
|
235
249
|
# Deep copy of +node+ in a NEW document — the one authority
|
|
236
250
|
# behind Node#dup / Element#dup and the indent-unit path.
|
|
237
251
|
# Elements deep-copy through the C copier (every child kind and
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -665,6 +665,17 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
665
665
|
Leptris::XML::Node.wrap_fresh(ptr, @document, Leptris::XML::FFI::NODE_ELEMENT)
|
|
666
666
|
end
|
|
667
667
|
|
|
668
|
+
# Markup-accumulating child construction (#374 lever 2): the
|
|
669
|
+
# block accumulates well-formed child markup in Ruby; the flush
|
|
670
|
+
# appends every fragment child in ONE native crossing
|
|
671
|
+
# (append_markup). Returns the flush's value (the NodeSet of
|
|
672
|
+
# added children).
|
|
673
|
+
def build
|
|
674
|
+
builder = Leptris::XML::Builder.new(self)
|
|
675
|
+
yield builder
|
|
676
|
+
builder.flush
|
|
677
|
+
end
|
|
678
|
+
|
|
668
679
|
def add_child(node_or_markup)
|
|
669
680
|
case node_or_markup
|
|
670
681
|
when Leptris::XML::Node
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -14,6 +14,7 @@ module Leptris
|
|
|
14
14
|
autoload :Namespace, "leptris/xml/namespace"
|
|
15
15
|
autoload :Document, "leptris/xml/document"
|
|
16
16
|
autoload :DocumentFragment, "leptris/xml/document_fragment"
|
|
17
|
+
autoload :Builder, "leptris/xml/builder"
|
|
17
18
|
autoload :DocType, "leptris/xml/doc_type"
|
|
18
19
|
autoload :NodeSet, "leptris/xml/node_set"
|
|
19
20
|
autoload :Searchable, "leptris/xml/searchable"
|
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.312.
|
|
4
|
+
version: 1.9.312.1
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -144,6 +144,7 @@ files:
|
|
|
144
144
|
- lib/leptris/version.rb
|
|
145
145
|
- lib/leptris/xml.rb
|
|
146
146
|
- lib/leptris/xml/attr.rb
|
|
147
|
+
- lib/leptris/xml/builder.rb
|
|
147
148
|
- lib/leptris/xml/c_string_array.rb
|
|
148
149
|
- lib/leptris/xml/cdata.rb
|
|
149
150
|
- lib/leptris/xml/comment.rb
|