leptris 1.9.222.0 → 1.9.222.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 403ad2f7e9b542b03e8f770c1b070e0e972ac0b4aca5ae16e5ed8f675b7599e4
4
- data.tar.gz: 5e5c49bb4ffdda23e1466b653346e6c20de604212a2941c97a9fa2c6b2297f83
3
+ metadata.gz: bded827a8ed4a8dcac8efcda8a391e79b497c122c57b65115027025b406c027b
4
+ data.tar.gz: 7171d06a07b227717f12de2a2bcccaabefce5067fb0d49d6632f1010b65fdb31
5
5
  SHA512:
6
- metadata.gz: 751de9f301ea9157169b546b3d81c8c65942e6c13fd8516bbd4e7ec6e940877146e634bccb92ab1b043c63f0b77bca5c3ea7cb83d81d539228a38cba6eaba072
7
- data.tar.gz: e38a36cfdcf7d4d52d007389dbc6ca5ab4e5f84f8794af81667837005a5928a776dc532b86b50dc8dc32644e8d0cbde979f9edfaa89695564e91e33dd5320d02
6
+ metadata.gz: 1055292d2190857575b0858cc61a2f1db481324c6ea56097894131a19bf43b360b72db03de87c908e3e071c6f68d71e4a973134580d40619f82ed772b7f106a6
7
+ data.tar.gz: 402c643195961c7159f6af522b8cac0f5366ca1a44ea94490ff69482b6311ab912fbae23044997af721544a0b0810fb37d8a6f2e9048c24e58086295d3b13965
@@ -2150,6 +2150,34 @@ static VALUE nf_plan_structs_typed_p(VALUE self)
2150
2150
  return Qtrue;
2151
2151
  }
2152
2152
 
2153
+ /* ---- Bulk attribute rows (leptris-ruby#278) ----------------------
2154
+ * One C crossing per element: walks the attribute chain and
2155
+ * returns a flat [name, value, address, ...] array. Names
2156
+ * interned (they repeat), values fresh UTF-8 strings, addresses
2157
+ * as Integers for Attr c_handle reconstruction. Identical data
2158
+ * to the per-attribute FFI chain (first/next/get_name/get_value,
2159
+ * 4 crossings each) — the win amplifies with attribute density
2160
+ * (40-attr elements: ~7x on the listing). */
2161
+ static VALUE nf_attribute_rows(VALUE self, VALUE document, VALUE addr)
2162
+ {
2163
+ (void)self;
2164
+ (void)document;
2165
+ void *node = (void *)(uintptr_t)NUM2ULL(addr);
2166
+ VALUE out = rb_ary_new();
2167
+ if (f_node_type(node) != WS_NODE_ELEMENT) return out;
2168
+
2169
+ for (void *a = f_attr_first(node); a; a = f_attr_next(a)) {
2170
+ const char *an = f_attr_name(a);
2171
+ const char *av = f_attr_value(node, a);
2172
+ rb_ary_push(out, an ? rb_enc_interned_str(an, strlen(an),
2173
+ rb_utf8_encoding())
2174
+ : Qnil);
2175
+ rb_ary_push(out, av ? rb_utf8_str_new_cstr(av) : Qnil);
2176
+ rb_ary_push(out, ULL2NUM((uintptr_t)a));
2177
+ }
2178
+ return out;
2179
+ }
2180
+
2153
2181
  static VALUE nf_plan_structs(VALUE self, VALUE document, VALUE addr,
2154
2182
  VALUE spec)
2155
2183
  {
@@ -2732,6 +2760,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
2732
2760
  nf_attribute_pairs, 2);
2733
2761
  rb_define_module_function(m_native, "plan_structs",
2734
2762
  nf_plan_structs, 3);
2763
+ rb_define_module_function(m_native, "attribute_rows",
2764
+ nf_attribute_rows, 2);
2735
2765
  rb_define_module_function(m_native, "plan_structs_typed?",
2736
2766
  nf_plan_structs_typed_p, 0);
2737
2767
  rb_define_module_function(m_native, "doc_handle_attach",
data/lib/leptris/html.rb CHANGED
@@ -10,9 +10,15 @@
10
10
  # Leptris::HTML(html) # the html4 lane (Nokogiri::HTML parity)
11
11
  # Leptris::HTML5.parse(html) # the WHATWG engine (html5lib zero reds)
12
12
  # Leptris::HTML4.parse(html) # explicit html4 lane
13
+ # Leptris::HTML5.create # fresh document, <!DOCTYPE html> preset
13
14
  #
14
15
  # These are pure delegates — no parallel class hierarchy, no second
15
16
  # wrapper cache; identity and the whole XML surface carry over.
17
+ # Construction flows through the same face as XML (create_element,
18
+ # add_child, root=, set_doctype) — `create` presets the doctype so a
19
+ # programmatically built document serializes with it; pair with
20
+ # `to_xml(no_decl: true)` for declaration-free HTML output (void
21
+ # elements serialize with the HTML5-legal trailing slash).
16
22
  module Leptris
17
23
  module HTML
18
24
  module_function
@@ -22,6 +28,37 @@ module Leptris
22
28
  def parse(html)
23
29
  Leptris::XML.parse_html(html, mode: :html4)
24
30
  end
31
+
32
+ # Fresh document with the HTML 4.01 Transitional doctype preset
33
+ # (Nokogiri::HTML::Builder's .doc carries the same). Pass
34
+ # +doctype: nil+ for a bare document.
35
+ def create(doctype: :transitional, skeleton: true)
36
+ create_document(doctype, skeleton: skeleton)
37
+ end
38
+
39
+ def create_document(doctype, skeleton: true)
40
+ doc = Leptris::XML::Document.create
41
+ case doctype
42
+ when :html5, true
43
+ doc.set_doctype("html")
44
+ when :transitional
45
+ doc.set_doctype(
46
+ "HTML",
47
+ public_id: "-//W3C//DTD HTML 4.01 Transitional//EN",
48
+ system_id: "http://www.w3.org/TR/html4/loose.dtd")
49
+ when nil, false then nil
50
+ else raise ArgumentError,
51
+ "doctype must be :html5, :transitional, nil, or false, got #{doctype.inspect}"
52
+ end
53
+ return doc unless skeleton
54
+ html = doc.create_element("html")
55
+ head = doc.create_element("head")
56
+ body = doc.create_element("body")
57
+ html.add_child(head)
58
+ html.add_child(body)
59
+ doc.root = html
60
+ doc
61
+ end
25
62
  end
26
63
 
27
64
  module HTML4
@@ -30,6 +67,10 @@ module Leptris
30
67
  def parse(html)
31
68
  Leptris::XML.parse_html(html, mode: :html4)
32
69
  end
70
+
71
+ def create(doctype: :transitional, skeleton: true)
72
+ Leptris::HTML.create_document(doctype, skeleton: skeleton)
73
+ end
33
74
  end
34
75
 
35
76
  module HTML5
@@ -41,5 +82,11 @@ module Leptris
41
82
  def parse(html)
42
83
  Leptris::XML.parse_html(html, mode: :whatwg)
43
84
  end
85
+
86
+ # Fresh document with the HTML5 doctype (`<!DOCTYPE html>`)
87
+ # preset; pass +doctype: nil+ for a bare document.
88
+ def create(doctype: :html5, skeleton: true)
89
+ Leptris::HTML.create_document(doctype, skeleton: skeleton)
90
+ end
44
91
  end
45
92
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.222.0"
4
+ VERSION = "1.9.222.2"
5
5
  end
@@ -474,6 +474,16 @@ class Leptris::XML::Document
474
474
  alias_method :to_s, :to_xml
475
475
  alias_method :serialize, :to_xml
476
476
 
477
+ # HTML5 serialization (#309): void elements without a trailing
478
+ # slash (`<br>`), script/style content as raw text, text/attr
479
+ # entity escaping per HTML rules, the DOCTYPE emitted first.
480
+ # Walks the tree in Ruby — the engine-side serialization mode is
481
+ # the perf headroom this face gates on.
482
+ def to_html
483
+ raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
484
+ Leptris::XML::HTMLSerialize.document(self)
485
+ end
486
+
477
487
  def save(path, **opts)
478
488
  opts_struct, _encoding_anchor = Leptris::XML::Serialization.build_options(
479
489
  indent: opts.fetch(:indent, 0),
@@ -311,7 +311,11 @@ class Leptris::XML::Element < Leptris::XML::Node
311
311
 
312
312
  def attribute_nodes
313
313
  return @attribute_nodes if memo_hit?(@attribute_nodes_version)
314
- result = each_attribute.to_a
314
+ result = if NATIVE_ATTR_ROWS && @c_address && @document
315
+ attribute_rows_native
316
+ else
317
+ each_attribute.to_a
318
+ end
315
319
  if @document
316
320
  @attribute_nodes = result
317
321
  @attribute_nodes_version = @document.version
@@ -319,6 +323,26 @@ class Leptris::XML::Element < Leptris::XML::Node
319
323
  result
320
324
  end
321
325
 
326
+ # One C crossing per element (leptris-ruby#278): the native face
327
+ # walks the attribute chain and returns flat [name, value,
328
+ # address] triples; the Attr value objects are minted from them
329
+ # with zero further engine crossings — identical objects to
330
+ # each_attribute. The win amplifies with attribute density
331
+ # (40-attr elements list ~7x faster than the chain).
332
+ def attribute_rows_native
333
+ ::Leptris::XML::Native.attribute_rows(@document, @c_address)
334
+ .each_slice(3)
335
+ .map do |name, value, handle|
336
+ ::Leptris::XML::Attr.new(name, value, self,
337
+ c_handle: ::FFI::Pointer.new(handle))
338
+ end
339
+ end
340
+
341
+ NATIVE_ATTR_ROWS =
342
+ defined?(::Leptris::XML::Native) &&
343
+ ::Leptris::XML::Native.respond_to?(:attribute_rows)
344
+ private_constant :NATIVE_ATTR_ROWS
345
+
322
346
  # The element's own namespace prefix (e.g. "foo" for <foo:child/>),
323
347
  # or nil when the element has none.
324
348
  def prefix
@@ -756,6 +780,13 @@ class Leptris::XML::Element < Leptris::XML::Node
756
780
  indent: indent, no_decl: no_decl, encoding: encoding)
757
781
  end
758
782
 
783
+ # HTML5 serialization of this element subtree (#309 — see
784
+ # Document#to_html for the semantics).
785
+ def to_html
786
+ ensure_alive!
787
+ Leptris::XML::HTMLSerialize.element(self)
788
+ end
789
+
759
790
  def canonicalize(version = Leptris::XML::FFI::C14N_1_0,
760
791
  inclusive_namespaces = nil,
761
792
  with_comments: false,
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The HTML5 tree serializer (#309): Ruby-level walk emitting
4
+ # HTML serialization semantics the XML face can't express —
5
+ #
6
+ # - VOID elements (br, meta, img, …) emit without the trailing
7
+ # slash and without a closing tag (`<br>`, `<meta charset="utf-8">`)
8
+ # - script/style content is RAW TEXT — emitted verbatim, never
9
+ # entity-escaped (`a > b` stays `a > b` inside `<style>`)
10
+ # - childless non-void elements get explicit closing tags
11
+ # (`<div></div>`, never `<div/>`)
12
+ # - text escapes &, <, > (and U+00A0 as &nbsp;); attributes escape
13
+ # &, ", <, >
14
+ #
15
+ # `Document#to_html` / `Element#to_html` are one-line delegates.
16
+ # The walk is Ruby-level by design for now — the engine-side
17
+ # serialization mode (raw-text + void handling in C) is the
18
+ # perf headroom this face gates on.
19
+ module Leptris::XML::HTMLSerialize
20
+ VOID = %w[
21
+ area base br col embed hr img input link meta param source
22
+ track wbr
23
+ ].freeze
24
+ RAW_TEXT = %w[script style].freeze
25
+
26
+ def self.document(doc)
27
+ out = +""
28
+ if (dt = doc.doctype)
29
+ out << "<!DOCTYPE" << " #{dt.name}"
30
+ out << " PUBLIC \"#{dt.public_id}\"" if dt.public_id
31
+ out << " \"#{dt.system_id}\"" if dt.system_id
32
+ out << ">"
33
+ end
34
+ doc.children.each { |child| node(child, out) }
35
+ out
36
+ end
37
+
38
+ def self.element(element)
39
+ out = +""
40
+ node(element, out)
41
+ out
42
+ end
43
+
44
+ def self.node(node, out)
45
+ case node
46
+ when Leptris::XML::Element then element_node(node, out)
47
+ when Leptris::XML::Comment then out << "<!--" << node.content.to_s << "-->"
48
+ when Leptris::XML::CDATA
49
+ # HTML5: CDATA sections in HTML serialize as bogus comments.
50
+ out << "<!--[CDATA[" << node.content.to_s << "]]-->"
51
+ when Leptris::XML::ProcessingInstruction
52
+ # HTML5: PIs serialize as bogus comments (?> is not a thing).
53
+ out << "<?" << node.target.to_s
54
+ out << " " << node.data.to_s unless node.data.to_s.empty?
55
+ out << ">"
56
+ when Leptris::XML::Text then text(node, out)
57
+ end
58
+ end
59
+
60
+ def self.element_node(el, out)
61
+ name = el.name
62
+ out << "<" << name
63
+ el.attributes.each do |attr_name, attr|
64
+ out << " " << attr_name << "=\"" << escape_attr(attr.value) << "\""
65
+ end
66
+ out << ">"
67
+ return if VOID.include?(name)
68
+
69
+ if RAW_TEXT.include?(name)
70
+ # Raw text: the element's text content verbatim — no entity
71
+ # escaping (CSS child selectors, JS comparisons survive).
72
+ el.children.each do |child|
73
+ out << child.content.to_s if child.is_a?(Leptris::XML::Text)
74
+ end
75
+ else
76
+ el.children.each { |child| node(child, out) }
77
+ end
78
+ out << "</" << name << ">"
79
+ end
80
+
81
+ def self.text(node, out)
82
+ parent_raw = node.parent && RAW_TEXT.include?(node.parent.name)
83
+ s = node.content.to_s
84
+ out << (parent_raw ? s : escape_text(s))
85
+ end
86
+
87
+ def self.escape_text(s)
88
+ s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
89
+ .gsub("\u00A0", "&nbsp;")
90
+ end
91
+
92
+ def self.escape_attr(s)
93
+ s.to_s.gsub("&", "&amp;").gsub("\"", "&quot;")
94
+ .gsub("<", "&lt;").gsub(">", "&gt;")
95
+ .gsub("\u00A0", "&nbsp;")
96
+ end
97
+ end
data/lib/leptris/xml.rb CHANGED
@@ -21,6 +21,7 @@ module Leptris
21
21
  autoload :CssToXPath, "leptris/xml/css_to_xpath"
22
22
  autoload :CStringArray, "leptris/xml/c_string_array"
23
23
  autoload :Serialization, "leptris/xml/serialization"
24
+ autoload :HTMLSerialize, "leptris/xml/html_serialize"
24
25
  autoload :SAX, "leptris/xml/sax"
25
26
  autoload :XPath, "leptris/xml/xpath"
26
27
  autoload :EvaluationContext, "leptris/xml/evaluation_context"
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.222.0
4
+ version: 1.9.222.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -179,6 +179,7 @@ files:
179
179
  - lib/leptris/xml/entity_reference.rb
180
180
  - lib/leptris/xml/evaluation_context.rb
181
181
  - lib/leptris/xml/ffi.rb
182
+ - lib/leptris/xml/html_serialize.rb
182
183
  - lib/leptris/xml/iteration_scope.rb
183
184
  - lib/leptris/xml/iterparse.rb
184
185
  - lib/leptris/xml/namespace.rb