leptris 1.9.222.3 → 1.9.225.0

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/Rakefile +1 -1
  4. data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
  5. data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
  6. data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
  7. data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
  8. data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
  9. data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
  10. data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
  11. data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
  12. data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
  13. data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
  14. data/lib/leptris/version.rb +1 -1
  15. data/lib/leptris/xml/document.rb +12 -4
  16. data/lib/leptris/xml/element.rb +1 -1
  17. data/lib/leptris/xml/ffi.rb +27 -1
  18. data/lib/leptris/xml/serialization.rb +27 -0
  19. data/lib/leptris/xml.rb +0 -1
  20. data/vendor-src/README.md +1 -1
  21. data/vendor-src/libleptris/CHANGELOG.md +73 -0
  22. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  23. data/vendor-src/libleptris/src/CMakeLists.txt +2 -0
  24. data/vendor-src/libleptris/src/include/leptris/sax/sax.h +87 -0
  25. data/vendor-src/libleptris/src/include/leptris/types.h +7 -1
  26. data/vendor-src/libleptris/src/include/leptris.h +49 -2
  27. data/vendor-src/libleptris/src/leptris/dom/digest.c +11 -0
  28. data/vendor-src/libleptris/src/leptris/dom/elem_pos.c +88 -0
  29. data/vendor-src/libleptris/src/leptris/dom/element.c +2 -1
  30. data/vendor-src/libleptris/src/leptris/dom/element.h +89 -56
  31. data/vendor-src/libleptris/src/leptris/dom/element_query.c +6 -5
  32. data/vendor-src/libleptris/src/leptris/dom/node_public.c +10 -23
  33. data/vendor-src/libleptris/src/leptris/dom/text.c +14 -79
  34. data/vendor-src/libleptris/src/leptris/dom/text.h +21 -33
  35. data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +150 -58
  36. data/vendor-src/libleptris/src/leptris/flat/il_scan.h +69 -0
  37. data/vendor-src/libleptris/src/leptris/html/html_parse.c +3 -0
  38. data/vendor-src/libleptris/src/leptris/leptris.c +10 -0
  39. data/vendor-src/libleptris/src/leptris/leptris_internal.h +9 -0
  40. data/vendor-src/libleptris/src/leptris/sax/records.c +135 -0
  41. data/vendor-src/libleptris/src/leptris/serialize/serialize.c +61 -47
  42. data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +3 -8
  43. data/vendor-src/libleptris/test/CMakeLists.txt +2 -0
  44. data/vendor-src/libleptris/test/dom/test_digest.cpp +49 -0
  45. data/vendor-src/libleptris/test/dom/test_text_borrowed.cpp +51 -40
  46. data/vendor-src/libleptris/test/html/test_html.cpp +23 -9
  47. data/vendor-src/libleptris/test/html/test_html_builder.cpp +190 -0
  48. data/vendor-src/libleptris/test/perf/test_perf.cpp +28 -17
  49. data/vendor-src/libleptris/test/sax/test_records.cpp +196 -0
  50. data/vendor-src/libleptris/vcpkg.json +1 -1
  51. metadata +6 -2
  52. data/lib/leptris/xml/html_serialize.rb +0 -97
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65c927a5db55dcad7c2b10b3eb7f4dc568cddd7e9c1762693a19697b9e4adaf7
4
- data.tar.gz: 472acbd73babf9608170f2f1f5d439d39e0dd97594034364db22cd5bacb94c21
3
+ metadata.gz: a65d81fa73df8297d35b8e095e69c656d4ff8819620a40bd2c173ae685727452
4
+ data.tar.gz: de25ee0a34c202432ccbc870a5a40b689000827902851d4387c1a1f98aebfa80
5
5
  SHA512:
6
- metadata.gz: fc9fb646488eade1050dbd2bb32cb88d94bf1df6858480480fc5f6b7aabe333225b689e72311483682367fe44f0bbbfbaa8624af2d65b91e4c88d963c24d896d
7
- data.tar.gz: 56075f78ab3fff389106a27c819967de112b80dbbafdaab83d6e0cc8ba56c8d9993b1984a74f69ca8e16ab40eeda57938a040b439f14941a4778dfffe64bd18b
6
+ metadata.gz: 2a61094f9eff1615f87cccb47791ecb899b8d2ca1a23afd4579238bbc9cf3afea51220a82ad5a6556ae21462a7f928c008277da2d10045e41c52a8ed6422d76f
7
+ data.tar.gz: 6833ca765302ade1e2874962bb86ae419b17671caa75e556b7324636b7930b5c568f1dc759abd7e6767388b36a3c1ddab89c539f263c1a139c303ef1683754f4
data/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ 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.225.0] - 2026-09-22
9
+
10
+ ### Added — native HTML serialization, construction, and the bulk SAX drain (engine 1.9.223–225)
11
+
12
+ - **`to_html` re-pointed at the engine's native HTML mode**
13
+ (1.9.225, upstream #1309/#1311 — our filing answered in one
14
+ release): `Document#to_html` → `leptris_document_serialize_html`,
15
+ `Element#to_html` → the ext serializer with `html_method: 1`.
16
+ Byte-parity with the interim Ruby walk is pinned by the #309
17
+ spec battery; the native path also fixes the walk's PI branch
18
+ (the Ruby walk crashed on processing instructions — target/data
19
+ readers don't exist on the wrapper). The Ruby walk is removed.
20
+ - **`Document#save(path, mode: :html)`** → `leptris_document_save_html`.
21
+ - **`leptris_document_create_html` attached** (native HTML-mode
22
+ documents — serialize AS HTML by default, Nokogiri behavior);
23
+ the facade's `create` stays on the XML base + doctype preset so
24
+ `to_xml` output on facade docs is unchanged. `html_method`
25
+ (tri-state) rides `SerializeExtStruct` — `to_xml` on an HTML
26
+ document can force XML with `-1` when the face needs it.
27
+ - **Bulk SAX record drain attached** (1.9.223, upstream #1298,
28
+ Canon-reported): `leptris_sax_records_*` — one call yields the
29
+ whole document as a flat record table (tree edges, source
30
+ offsets, flat attribute table with §3.3.3 normalization flags).
31
+ Attach-only until the SAX face wants the bulk lane.
32
+ - 1.9.224 rides along (LeakSanitizer fix on the direct-parse
33
+ failure path). Audit 351/351, suite 780/0.
34
+
8
35
  ## [1.9.222.0] - 2026-09-22
9
36
 
10
37
  ### Fixed — engine sync (1.9.222): the interleaved lane's namespace regression
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.222"
11
+ LIBLEPTRIS_VERSION = "1.9.225"
12
12
  # Vendored alongside libleptris for fn:normalize-unicode (TODO
13
13
  # .restructure/20): built per platform with a RELOCATABLE @rpath
14
14
  # install name, loaded by ffi.rb before libleptris so the
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.222.3"
4
+ VERSION = "1.9.225.0"
5
5
  end
@@ -481,16 +481,24 @@ class Leptris::XML::Document
481
481
  # the perf headroom this face gates on.
482
482
  def to_html
483
483
  raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
484
- Leptris::XML::HTMLSerialize.document(self)
484
+ Leptris::XML::Serialization.document_html(c_ptr)
485
485
  end
486
486
 
487
- def save(path, **opts)
487
+ def save(path, mode: :xml, **opts)
488
488
  opts_struct, _encoding_anchor = Leptris::XML::Serialization.build_options(
489
489
  indent: opts.fetch(:indent, 0),
490
490
  no_decl: opts.fetch(:no_decl, false),
491
491
  encoding: opts[:encoding])
492
- status = Leptris::XML::FFI.leptris_document_save_file(
493
- c_ptr, path, opts_struct.pointer)
492
+ status =
493
+ if mode == :html
494
+ Leptris::XML::FFI.leptris_document_save_html(
495
+ c_ptr, path, opts_struct.pointer)
496
+ elsif mode != :xml
497
+ raise ArgumentError, "mode must be :xml or :html, got #{mode.inspect}"
498
+ else
499
+ Leptris::XML::FFI.leptris_document_save_file(
500
+ c_ptr, path, opts_struct.pointer)
501
+ end
494
502
  Leptris::XML::FFI.check_status(status)
495
503
  self
496
504
  end
@@ -784,7 +784,7 @@ class Leptris::XML::Element < Leptris::XML::Node
784
784
  # Document#to_html for the semantics).
785
785
  def to_html
786
786
  ensure_alive!
787
- Leptris::XML::HTMLSerialize.element(self)
787
+ Leptris::XML::Serialization.element_html(c_ptr)
788
788
  end
789
789
 
790
790
  def canonicalize(version = Leptris::XML::FFI::C14N_1_0,
@@ -169,7 +169,11 @@ module Leptris
169
169
  class SerializeExtStruct < ::FFI::Struct
170
170
  layout :indent_text, :int,
171
171
  :indent_unit, :pointer,
172
- :expand_empty, :int
172
+ :expand_empty, :int,
173
+ # libleptris 1.9.225 (#1311): tri-state — 0 = the
174
+ # document's default method, 1 = force HTML, -1 =
175
+ # force XML (to_xml on an HTML document).
176
+ :html_method, :int
173
177
  end
174
178
 
175
179
  # Mirrors LeptrisParseOptions (libleptris >= 1.9.0 carries the
@@ -992,6 +996,28 @@ attach_function :leptris_parse_string,
992
996
  [:leptris_element, :pointer, :size_t, :pointer, :pointer], :size_t
993
997
  attach_function :leptris_document_save_file,
994
998
  [:leptris_document, :string, :pointer], :leptris_status
999
+ # HTML construction + serialization (libleptris 1.9.225,
1000
+ # upstream #1309/#1311 — the engine side of #309): native HTML
1001
+ # documents carry the html serialization mode (voids unclosed,
1002
+ # raw-text script/style, explicit closes, no declaration).
1003
+ attach_function :leptris_document_create_html, [], :leptris_document
1004
+ attach_function :leptris_document_serialize_html,
1005
+ [:leptris_document, :pointer], :pointer
1006
+ attach_function :leptris_document_save_html,
1007
+ [:leptris_document, :string, :pointer], :leptris_status
1008
+ # Bulk SAX record drain (libleptris 1.9.223, upstream #1298):
1009
+ # ONE call parses the document into a flat record table —
1010
+ # tree edges, source offsets, and a flat attribute table, all
1011
+ # offset-views into a parser-owned buffer. Attach-only until
1012
+ # the SAX face wants the bulk lane.
1013
+ attach_function :leptris_sax_records_parse,
1014
+ [:string, :size_t, :uint, :pointer], :leptris_status
1015
+ attach_function :leptris_sax_records_count, [:pointer], :size_t
1016
+ attach_function :leptris_sax_records_data, [:pointer], :pointer
1017
+ attach_function :leptris_sax_records_attrs,
1018
+ [:pointer, :pointer], :pointer
1019
+ attach_function :leptris_sax_records_buffer, [:pointer], :string
1020
+ attach_function :leptris_sax_records_free, [:pointer], :void
995
1021
  # XSLT 1.0 engine (libleptris 1.9.1): compile once, apply many.
996
1022
  # XQuery 1.0 core (libleptris 1.9.64-1.9.66): orchestration
997
1023
  # over the XPath engine — prolog (declare variable/namespace/
@@ -58,6 +58,33 @@ module Leptris::XML::Serialization
58
58
  # Default-options element serialization for inner_html's per-child
59
59
  # loop: no kwargs, no options rebuild, no method allocation —
60
60
  # one FFI fast-path dispatch per child.
61
+ # Document HTML serialization (libleptris 1.9.225, #1311): the
62
+ # native HTML mode — voids unclosed, raw-text script/style,
63
+ # explicit closes, no declaration, DOCTYPE first.
64
+ def self.document_html(c_ptr)
65
+ Leptris::XML::FFI.read_owned_string(
66
+ Leptris::XML::FFI.leptris_document_serialize_html(c_ptr, nil))
67
+ end
68
+
69
+ # Element-level HTML mode through the ext serializer (the
70
+ # html_method tri-state forces HTML regardless of the document
71
+ # flavor).
72
+ HTML_METHOD_EXT = begin
73
+ ext = Leptris::XML::FFI::SerializeExtStruct.new
74
+ ext[:indent_text] = 0
75
+ ext[:indent_unit] = nil
76
+ ext[:expand_empty] = 0
77
+ ext[:html_method] = 1
78
+ ext
79
+ end
80
+ private_constant :HTML_METHOD_EXT
81
+
82
+ def self.element_html(c_ptr)
83
+ Leptris::XML::FFI.read_owned_string(
84
+ Leptris::XML::FFI.leptris_element_serialize_ext(
85
+ c_ptr, nil, HTML_METHOD_EXT.pointer))
86
+ end
87
+
61
88
  def self.element_xml_default(c_ptr)
62
89
  return Leptris::XML::Native.fast_element_xml(c_ptr.address, 0, true,
63
90
  nil) if
data/lib/leptris/xml.rb CHANGED
@@ -21,7 +21,6 @@ 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"
25
24
  autoload :SAX, "leptris/xml/sax"
26
25
  autoload :XPath, "leptris/xml/xpath"
27
26
  autoload :EvaluationContext, "leptris/xml/evaluation_context"
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.222
4
+ - libleptris v1.9.225
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,78 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.225] - 2026-09-22
4
+
5
+ ### Added
6
+
7
+ - **html: public HTML construction + serialization surface (#1309,
8
+ #1311).** HTML documents now serialize AS HTML by default
9
+ (libxml2/Nokogiri behavior): void elements unclosed (`<br>`,
10
+ `<meta charset="utf-8">`), raw-text script/style verbatim,
11
+ childless non-voids explicitly closed, no XML declaration. New:
12
+ `leptris_document_create_html()`, `leptris_document_serialize_html()`,
13
+ `leptris_document_save_html()`, and `LeptrisSerializeExtOptions.html_method`
14
+ (tri-state: 0 = document default, 1 = force HTML, -1 = force XML —
15
+ to_xml on an HTML doc). Element-level `html_method` maps through the
16
+ ext serializer too. HTML-parsed documents (whatwg + html4 modes)
17
+ carry the mode automatically; XSLT `method="html"` output unchanged
18
+ (#1312).
19
+
20
+
21
+ ## [1.9.224] - 2026-09-22
22
+
23
+ ### Fixed
24
+
25
+ - **mem:** the element source-offset side table (new in this
26
+ release) is now also freed on the direct-parse failure path,
27
+ which bypasses leptris_document_free — caught by CI LeakSanitizer.
28
+ - **test:** the runner-contention perf gate
29
+ (SmallDocumentParseIsFast) retries across settling windows instead
30
+ of failing on a single contended measurement (three CI flakes in
31
+ one day, budget unchanged).
32
+
33
+ ### Performance
34
+
35
+ - **dom:** elements shrink **72 -> 64 bytes** (ILP32 56 -> 48;
36
+ #1285 slice 3a): the namespace side-cache pointer became a
37
+ self-relative int32 offset (the attrs' round-19 pattern), and the
38
+ #1124 source offsets moved to a document-owned side table — cold
39
+ diagnostics-only data no longer costs hot-path cache lines.
40
+ Position accessors are unchanged (#1124 specs green). Part of the
41
+ pugi-style node-storage lane (#1222/#1238) (#1308).
42
+
43
+
44
+
45
+ ## [1.9.223] - 2026-09-22
46
+
47
+ ### Added
48
+
49
+ - **sax:** `leptris_sax_records_parse` — a bulk record drain for hot SAX
50
+ loops (#1298, reported by Canon): ONE call returns the whole document
51
+ as a flat record table (element tree edges, start/end offsets, flat
52
+ attribute table with XML 1.0 §3.3.3 normalization flags), all
53
+ NUL-terminated views into a parser-owned buffer. Unsupported
54
+ constructs (comments/PIs/CDATA/DOCTYPE/entities/xmlns) return
55
+ `LEPTRIS_ERROR_NOT_SUPPORTED` — fall back to the pull API. New
56
+ additive status code (#1305).
57
+
58
+ ### Fixed
59
+
60
+ - **dom:** `LEPTRIS_DIGEST_ATTR_ORDER` is now actually honored — the
61
+ 1.9.205 flag shipped only as a C enum with no env reader and no
62
+ binding carrier, so `strings` never found it in shipped bundles
63
+ (#1297, reported by Canon). `leptris_node_digest` reads the env
64
+ per call and ORs it into the passed flags (#1304).
65
+
66
+ ### Performance
67
+
68
+ - **dom:** text nodes shrink **64 → 48 bytes** (#1285 slice 4): the
69
+ `pool`/`borrowed` fields are gone — every parse lane terminates
70
+ text in place and entities decode eagerly at parse time, so
71
+ `leptris_text_get_content` is a plain field read. Content is
72
+ always NUL-terminated; `content_len` is authoritative (#1303).
73
+
74
+
75
+
3
76
  ## [1.9.222] - 2026-09-22
4
77
 
5
78
  ### Fixed
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.222
6
+ VERSION 1.9.225
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -35,6 +35,7 @@ set(LEPTRIS_SOURCES
35
35
  leptris/dom/node_public.c
36
36
  leptris/descriptor/plan.c
37
37
  leptris/dom/element.c
38
+ leptris/dom/elem_pos.c
38
39
  leptris/dom/element_query.c
39
40
  leptris/dom/element_modify.c
40
41
  leptris/dom/element_index.c
@@ -112,6 +113,7 @@ set(LEPTRIS_SOURCES
112
113
  leptris/sax/iterparse.c
113
114
  leptris/sax/streaming.c
114
115
  leptris/sax/recorder.c
116
+ leptris/sax/records.c
115
117
 
116
118
  # DTD
117
119
  leptris/dtd/model.c
@@ -596,6 +596,93 @@ LEPTRIS_API const char* leptris_iterparse_error(LeptrisIterparse it);
596
596
 
597
597
  LEPTRIS_API void leptris_iterparse_free(LeptrisIterparse it);
598
598
 
599
+
600
+ /* ============================================================================
601
+ * Bulk SAX records (issue #1298) — one-crossing whole-document drain
602
+ * ============================================================================ */
603
+
604
+ /** Record kinds (LeptrisSaxRecord.kind). */
605
+ enum {
606
+ LEPTRIS_SAX_REC_ELEMENT = 0, /**< start+end collapsed; tree via parent/next_sib */
607
+ LEPTRIS_SAX_REC_TEXT = 1 /**< character data run (entity-free docs only) */
608
+ };
609
+
610
+ /** One element or text record. Offsets are byte positions into the
611
+ * buffer returned by leptris_sax_records_buffer; every view is
612
+ * NUL-terminated at off+len (names and text both). */
613
+ typedef struct LeptrisSaxRecord {
614
+ uint32_t kind; /**< LEPTRIS_SAX_REC_* */
615
+ uint32_t parent; /**< record index, 0xFFFFFFFF for the root */
616
+ uint32_t next_sib; /**< record index, 0xFFFFFFFF = last sibling */
617
+ uint32_t off; /**< element: name; text: content */
618
+ uint32_t len; /**< byte length at off (excl. NUL) */
619
+ uint32_t line; /**< byte offset of '<' (or text start) + 1 (#1124 dp semantics) */
620
+ uint32_t start_tag_end; /**< byte offset after the open '>' (elements; 0 when unknown) */
621
+ uint32_t elem_end; /**< byte offset after the close '>' (self-close == start) */
622
+ uint32_t attr_first; /**< index into the flat attr table, 0xFFFFFFFF = none */
623
+ uint32_t attr_count; /**< number of attributes */
624
+ uint8_t self_closing; /**< element written <a/> */
625
+ } LeptrisSaxRecord;
626
+
627
+ /** One attribute: flat (off,len) views + 3.3.3 normalization need. */
628
+ typedef struct LeptrisSaxAttr {
629
+ uint32_t name_off, name_len; /**< attribute name (qname, NUL-terminated at [name_len]) */
630
+ uint32_t value_off, value_len; /**< raw value (NUL-terminated at [value_len]) */
631
+ uint8_t value_has_ws; /**< literal \t/\n/\r present — apply XML 1.0 3.3.3 normalization */
632
+ } LeptrisSaxAttr;
633
+
634
+ /** Opaque whole-document record table. */
635
+ typedef struct LeptrisSaxRecords LeptrisSaxRecords;
636
+
637
+ /**
638
+ * Parse a whole document into a flat record table (ONE call)
639
+ *
640
+ * The engine-side surface for #1298: hosts with hot SAX loops drain
641
+ * the entire document with a single crossing and then walk plain C
642
+ * structs — no per-event callback dispatch, no per-event string
643
+ * minting C-side. Records are (off,len) views into the parser's
644
+ * private copy of the input; every view is NUL-terminated in place.
645
+ *
646
+ * Coverage: the interleaved lane's scannable subset — elements,
647
+ * text, attributes; well-formed entity-free documents without
648
+ * comments/PIs/CDATA/DOCTYPE/xmlns declarations. Anything outside
649
+ * that subset returns LEPTRIS_ERROR_NOT_SUPPORTED (zero allocation);
650
+ * hosts fall back to the pull API (leptris_pull_new) for those
651
+ * documents. Malformed input returns LEPTRIS_ERROR_PARSE.
652
+ *
653
+ * @param xml Input (copied; the caller's buffer is untouched)
654
+ * @param len Input length in bytes (< 2 GiB)
655
+ * @param flags Reserved; pass 0
656
+ * @param out Receives the table handle
657
+ * @return LEPTRIS_OK, LEPTRIS_ERROR_NULL_ARG, LEPTRIS_ERROR_NOT_SUPPORTED,
658
+ * LEPTRIS_ERROR_PARSE, or LEPTRIS_ERROR_MEMORY
659
+ *
660
+ * Memory: free with leptris_sax_records_free; all views die with it.
661
+ */
662
+ LEPTRIS_API LeptrisStatus leptris_sax_records_parse(const char* xml,
663
+ size_t len,
664
+ unsigned flags,
665
+ LeptrisSaxRecords** out);
666
+
667
+ /** Number of records. */
668
+ LEPTRIS_API size_t leptris_sax_records_count(const LeptrisSaxRecords* recs);
669
+
670
+ /** Record array (count entries; valid until free). */
671
+ LEPTRIS_API const LeptrisSaxRecord* leptris_sax_records_data(
672
+ const LeptrisSaxRecords* recs);
673
+
674
+ /** Flat attribute table + its length (valid until free). */
675
+ LEPTRIS_API const LeptrisSaxAttr* leptris_sax_records_attrs(
676
+ const LeptrisSaxRecords* recs, size_t* out_attr_count);
677
+
678
+ /** The buffer all record/attr offsets index into (NUL-padded past
679
+ * len). */
680
+ LEPTRIS_API const char* leptris_sax_records_buffer(
681
+ const LeptrisSaxRecords* recs);
682
+
683
+ /** Free the table (invalidates every view). */
684
+ LEPTRIS_API void leptris_sax_records_free(LeptrisSaxRecords* recs);
685
+
599
686
  #ifdef __cplusplus
600
687
  }
601
688
  #endif
@@ -49,7 +49,8 @@ typedef enum {
49
49
  LEPTRIS_ERROR_INVALID_ARG = -5, /* Invalid argument */
50
50
  LEPTRIS_ERROR_NOT_FOUND = -6, /* Resource not found */
51
51
  LEPTRIS_ERROR_IO = -7, /* I/O error (file not found, etc.) */
52
- LEPTRIS_ERROR_NOT_IMPLEMENTED = -8 /* Feature not yet implemented */
52
+ LEPTRIS_ERROR_NOT_IMPLEMENTED = -8, /* Feature not yet implemented */
53
+ LEPTRIS_ERROR_NOT_SUPPORTED = -9 /* Construct outside this API's subset (additive, #1298) */
53
54
  } LeptrisStatus;
54
55
 
55
56
  /* ============================================================================
@@ -342,6 +343,11 @@ typedef struct {
342
343
  * Applies to the XML method only — HTML void elements keep
343
344
  * their HTML shapes. */
344
345
  int expand_empty;
346
+ /* §16.2 HTML method control (#1309): 0 = document default
347
+ * (HTML-parsed and create_html documents serialize as HTML),
348
+ * 1 = force HTML on any document, -1 = force XML (to_xml on an
349
+ * HTML document). XSLT method="html" maps to 1. */
350
+ int html_method;
345
351
  } LeptrisSerializeExtOptions;
346
352
 
347
353
  /* ============================================================================
@@ -191,9 +191,14 @@ LEPTRIS_API size_t leptris_node_child_count(LeptrisNodeRef node);
191
191
  *
192
192
  * @param node Subtree root (any node type; NULL hashes to 0)
193
193
  * @param flags LEPTRIS_DIGEST_DROP_WS_TEXT skips whitespace-only
194
- * text nodes
194
+ * text nodes; LEPTRIS_DIGEST_ATTR_ORDER makes attribute
195
+ * ORDER identity (no sort)
195
196
  * @return 64-bit content digest
196
- */
197
+ *
198
+ * Environment opt-in (#1297): setting LEPTRIS_DIGEST_ATTR_ORDER=1 in
199
+ * the process environment ORs LEPTRIS_DIGEST_ATTR_ORDER into every
200
+ * call — the carrier for hosts whose binding exposes no flags
201
+ * parameter. "0" or an empty value leaves the passed flags alone. */
197
202
  LEPTRIS_API uint64_t leptris_node_digest(LeptrisNodeRef node,
198
203
  LeptrisDigestFlags flags);
199
204
 
@@ -773,6 +778,19 @@ LEPTRIS_API const char* leptris_pi_node_get_data(LeptrisNodeRef node);
773
778
  */
774
779
  LEPTRIS_API LeptrisDocument leptris_document_create(void);
775
780
 
781
+ /**
782
+ * Create an empty HTML-mode document (#1309)
783
+ *
784
+ * Identical to leptris_document_create except the document carries
785
+ * html_mode: the default serializer emits §16.2 HTML output (void
786
+ * elements unclosed, raw-text script/style, HTML attribute
787
+ * escaping). Build with the regular element APIs.
788
+ *
789
+ * @return New empty HTML document, or NULL on failure
790
+ * Memory: free with leptris_document_free.
791
+ */
792
+ LEPTRIS_API LeptrisDocument leptris_document_create_html(void);
793
+
776
794
  /**
777
795
  * Attach an element as the document's root element
778
796
  *
@@ -2451,6 +2469,35 @@ LEPTRIS_API char* leptris_document_serialize_ext_sized(LeptrisDocument doc,
2451
2469
  const LeptrisSerializeExtOptions* ext,
2452
2470
  size_t ext_size);
2453
2471
 
2472
+ /**
2473
+ * Serialize a document with the §16.2 HTML method (#1309)
2474
+ *
2475
+ * Forces the HTML serialization rules (void elements unclosed,
2476
+ * raw-text script/style, HTML attribute escaping) regardless of the
2477
+ * document's mode — the XSLT method="html" semantic, public.
2478
+ * HTML-parsed and create_html documents get this behavior by
2479
+ * default; this entry forces it on any document.
2480
+ *
2481
+ * @param doc Document
2482
+ * @param options Frozen serialize options (NULL = defaults)
2483
+ * @return Serialized HTML string
2484
+ * Memory: Caller must call leptris_free_string() when done.
2485
+ */
2486
+ LEPTRIS_API char* leptris_document_serialize_html(
2487
+ LeptrisDocument doc, const LeptrisSerializeOptions* options);
2488
+
2489
+ /**
2490
+ * Save a document as HTML to a file (#1309)
2491
+ *
2492
+ * @param doc Document
2493
+ * @param filepath Destination path
2494
+ * @param options Frozen serialize options (NULL = defaults)
2495
+ * @return LEPTRIS_OK (0), or a negative LeptrisStatus on failure
2496
+ */
2497
+ LEPTRIS_API int leptris_document_save_html(LeptrisDocument doc,
2498
+ const char* filepath,
2499
+ const LeptrisSerializeOptions* options);
2500
+
2454
2501
  /**
2455
2502
  * Extended element serialization (issue #882): the same option
2456
2503
  * surface as leptris_document_serialize_ext — indent_text,
@@ -224,6 +224,17 @@ static uint64_t digest_element(LeptrisElement e, LeptrisDigestFlags flags) {
224
224
  LEPTRIS_API uint64_t leptris_node_digest(LeptrisNodeRef node,
225
225
  LeptrisDigestFlags flags) {
226
226
  if (!node) return 0;
227
+ /* #1297: process-global opt-in for ATTR_ORDER (#1200 part 1) —
228
+ * hosts whose binding has no flags carrier (moxml's Node#digest
229
+ * takes no flags parameter) set LEPTRIS_DIGEST_ATTR_ORDER. Read
230
+ * per call, same discipline as the LEPTRIS_INTERLEAVED lane
231
+ * gate; empty string counts as unset (MSVC _putenv_s(k, "")),
232
+ * "0" disables. ORs with explicitly passed flags. */
233
+ {
234
+ const char* env = getenv("LEPTRIS_DIGEST_ATTR_ORDER");
235
+ if (env && *env && *env != '0')
236
+ flags = (LeptrisDigestFlags)(flags | LEPTRIS_DIGEST_ATTR_ORDER);
237
+ }
227
238
  LeptrisNode* n = (LeptrisNode*)node;
228
239
  switch (n->type) {
229
240
  case LEPTRIS_NODE_TYPE_ELEMENT:
@@ -0,0 +1,88 @@
1
+ /* lib/src/dom/elem_pos.c — element source-offset side table.
2
+ *
3
+ * #1285 slice 3a: start_tag_end_off/element_end_off (#1124) left the
4
+ * element struct (cold diagnostics-only data was costing hot-path
5
+ * cache lines). Parse lanes record here instead; the single reader
6
+ * (node_public.c position accessor) probes. Open addressing,
7
+ * Fibonacci hash, insert-or-update, power-of-two capacity, freed
8
+ * with the document. */
9
+ #include "../leptris_internal.h"
10
+ #include "element.h"
11
+ #include <stdlib.h>
12
+ #include <stdint.h>
13
+ #include <string.h>
14
+
15
+ struct leptris_elem_pos_entry {
16
+ struct leptris_element* elem;
17
+ uint32_t start_tag_end;
18
+ uint32_t element_end;
19
+ };
20
+
21
+ static size_t elem_pos_hash(const struct leptris_element* e, size_t cap) {
22
+ uintptr_t k = (uintptr_t)e;
23
+ return ((size_t)((k * 0x9E3779B97F4A7C15ULL) >> 32)) & (cap - 1);
24
+ }
25
+
26
+ static void elem_pos_insert_raw(struct leptris_elem_pos_entry* tab,
27
+ size_t cap, struct leptris_element* elem,
28
+ uint32_t ste, uint32_t ee) {
29
+ size_t i = elem_pos_hash(elem, cap);
30
+ while (tab[i].elem && tab[i].elem != elem)
31
+ i = (i + 1) & (cap - 1);
32
+ tab[i].elem = elem;
33
+ tab[i].start_tag_end = ste;
34
+ tab[i].element_end = ee;
35
+ }
36
+
37
+ static int elem_pos_grow(struct leptris_document* doc) {
38
+ size_t ncap = doc->elem_pos_cap ? doc->elem_pos_cap * 2 : 64;
39
+ struct leptris_elem_pos_entry* nt =
40
+ (struct leptris_elem_pos_entry*)calloc(ncap, sizeof(*nt));
41
+ if (!nt) return -1;
42
+ for (size_t i = 0; i < doc->elem_pos_cap; i++) {
43
+ struct leptris_elem_pos_entry* e = &doc->elem_pos[i];
44
+ if (e->elem)
45
+ elem_pos_insert_raw(nt, ncap, e->elem, e->start_tag_end,
46
+ e->element_end);
47
+ }
48
+ free(doc->elem_pos);
49
+ doc->elem_pos = nt;
50
+ doc->elem_pos_cap = ncap;
51
+ return 0;
52
+ }
53
+
54
+ void leptris_elem_pos_record(struct leptris_document* doc,
55
+ struct leptris_element* elem,
56
+ uint32_t start_tag_end, uint32_t element_end) {
57
+ if (!doc || !elem) return;
58
+ if (doc->elem_pos_count * 2 + 2 >= doc->elem_pos_cap) {
59
+ if (elem_pos_grow(doc) != 0) return; /* cold data: skip on OOM */
60
+ }
61
+ struct leptris_elem_pos_entry* tab = doc->elem_pos;
62
+ size_t cap = doc->elem_pos_cap;
63
+ size_t i = elem_pos_hash(elem, cap);
64
+ while (tab[i].elem && tab[i].elem != elem)
65
+ i = (i + 1) & (cap - 1);
66
+ if (!tab[i].elem) {
67
+ tab[i].elem = elem;
68
+ doc->elem_pos_count++;
69
+ }
70
+ tab[i].start_tag_end = start_tag_end;
71
+ tab[i].element_end = element_end;
72
+ }
73
+
74
+ int leptris_elem_pos_lookup(const struct leptris_document* doc,
75
+ const struct leptris_element* elem,
76
+ uint32_t* start_tag_end, uint32_t* element_end) {
77
+ if (!doc || !elem || !doc->elem_pos_cap) return 0;
78
+ size_t i = elem_pos_hash(elem, doc->elem_pos_cap);
79
+ while (doc->elem_pos[i].elem) {
80
+ if (doc->elem_pos[i].elem == elem) {
81
+ if (start_tag_end) *start_tag_end = doc->elem_pos[i].start_tag_end;
82
+ if (element_end) *element_end = doc->elem_pos[i].element_end;
83
+ return 1;
84
+ }
85
+ i = (i + 1) & (doc->elem_pos_cap - 1);
86
+ }
87
+ return 0;
88
+ }
@@ -425,7 +425,8 @@ const char* leptris_element_get_name(LeptrisElement elem) {
425
425
  void leptris_element_set_namespace_uri_view(LeptrisElement elem, LeptrisStringView uri_view) {
426
426
  if (!elem) return;
427
427
  if (leptris_sv_is_empty(&uri_view)) {
428
- if (elem->ns_cache) elem->ns_cache->namespace_uri = NULL;
428
+ struct leptris_ns_cache* nsc = elem_get_ns_cache(elem);
429
+ if (nsc) nsc->namespace_uri = NULL;
429
430
  return;
430
431
  }
431
432
  /* Issue #525: a namespace now exists — unprefixed XPath name