leptris 1.9.371.0-ppc64le-linux → 1.9.373.0-ppc64le-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/Rakefile +1 -1
- data/ext/leptris/native/native.c +13 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document_fragment.rb +38 -0
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/native_3_3.so +0 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +37 -0
- data/vendor-src/libleptris/CLAUDE.md +7 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/README.adoc +22 -0
- data/vendor-src/libleptris/docs/MISSION-PLAN.md +163 -0
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +124 -1
- data/vendor-src/libleptris/test/html/test_html5lib.cpp +155 -1
- data/vendor-src/libleptris/test/html/test_html_parse_errors.cpp +24 -7
- data/vendor-src/libleptris/test/xpath/test_xpath.cpp +47 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- 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: 3cfa4759d2078d8a80e6e45c27067fec2d01c9bfde679b5d379411d5d6b23d28
|
|
4
|
+
data.tar.gz: 01752c4ee4c9897dbb5765d7913a36397d251d1d75a02bd48115a3b2accdacdd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 268a5bd3746f3b30fb183cbe4acb076bb2b8670e8fd2577c47f780f0b973fa624a9efb7461e261484beef014dc86456d045f3af26d1ae576c4b7d41eab9ca28c
|
|
7
|
+
data.tar.gz: c7bba592a9065340448326f3d51550ed1ff5802d0801d5127c6726470815fb1a2d3d7b6fe2ae672f40b05ee8ff029b97d7eda88182b5d64850c4df310f1f6080
|
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.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.373"
|
|
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
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -1414,20 +1414,30 @@ static VALUE nf_insert_binding_child(VALUE self, VALUE document,
|
|
|
1414
1414
|
static VALUE nn_create_element(VALUE klass, VALUE document, VALUE name)
|
|
1415
1415
|
{
|
|
1416
1416
|
void *ptr;
|
|
1417
|
+
VALUE mk = doc_klass_for_kind(document, 0);
|
|
1417
1418
|
(void)klass;
|
|
1418
1419
|
ptr = f_elem_create(doc_ptr_of(document), StringValueCStr(name));
|
|
1419
1420
|
if (!ptr)
|
|
1420
1421
|
rb_raise(c_runtime_error, "leptris_element_create failed");
|
|
1422
|
+
/* #340: registered documents mint their element klass here too —
|
|
1423
|
+
* the unattached create factories are the build path (moxml's
|
|
1424
|
+
* create_element per record); plain NativeNode here forced the
|
|
1425
|
+
* consumer into wrapper-mode bridging for the whole document. */
|
|
1426
|
+
if (!NIL_P(mk))
|
|
1427
|
+
return mint_consumer_node(mk, document, ptr);
|
|
1421
1428
|
return wrap_new(document, ptr);
|
|
1422
1429
|
}
|
|
1423
1430
|
|
|
1424
1431
|
static VALUE nn_create_text(VALUE klass, VALUE document, VALUE content)
|
|
1425
1432
|
{
|
|
1426
1433
|
void *ptr;
|
|
1434
|
+
VALUE mk = doc_klass_for_kind(document, 1);
|
|
1427
1435
|
(void)klass;
|
|
1428
1436
|
ptr = f_text_create(doc_ptr_of(document), StringValueCStr(content));
|
|
1429
1437
|
if (!ptr)
|
|
1430
1438
|
rb_raise(c_runtime_error, "leptris_text_node_create failed");
|
|
1439
|
+
if (!NIL_P(mk))
|
|
1440
|
+
return mint_consumer_node(mk, document, ptr);
|
|
1431
1441
|
return wrap_new(document, ptr);
|
|
1432
1442
|
}
|
|
1433
1443
|
|
|
@@ -3548,6 +3558,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
3548
3558
|
rb_define_method(c_native_node, "create_child", nn_create_child, 1);
|
|
3549
3559
|
rb_define_method(c_native_node, "append_child", nn_append_child, 1);
|
|
3550
3560
|
rb_define_method(c_native_node, "add_child", nn_append_child, 1);
|
|
3561
|
+
rb_define_alias(c_native_node, "add_child_native", "add_child");
|
|
3551
3562
|
rb_define_method(c_native_node, "address", nn_address, 0);
|
|
3552
3563
|
rb_define_method(c_native_node, "document", nn_document, 0);
|
|
3553
3564
|
rb_define_method(c_native_node, "attributes", nn_attributes, 0);
|
|
@@ -3564,7 +3575,9 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
3564
3575
|
* method-cache cost instead of UnboundMethod#bind_call. */
|
|
3565
3576
|
rb_define_alias(c_native_node, "attr_read", "attribute");
|
|
3566
3577
|
rb_define_alias(c_native_node, "text_read", "content");
|
|
3578
|
+
rb_define_alias(c_native_node, "name_read", "name");
|
|
3567
3579
|
rb_define_method(c_native_node, "children", nn_children, 0);
|
|
3580
|
+
rb_define_alias(c_native_node, "children_read", "children");
|
|
3568
3581
|
rb_define_method(c_native_node, "element_children", nn_element_children, 0);
|
|
3569
3582
|
rb_define_method(c_native_node, "next_sibling", nn_next_sibling, 0);
|
|
3570
3583
|
rb_define_method(c_native_node, "parent", nn_parent, 0);
|
data/lib/leptris/version.rb
CHANGED
|
@@ -27,6 +27,15 @@ class Leptris::XML::DocumentFragment
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def self.parse(xml, document)
|
|
30
|
+
# leptris#1781: the second argument is the HOST document (the
|
|
31
|
+
# fragment borrows its lifetime). A nil or wrong-typed argument
|
|
32
|
+
# used to surface from the native face as "document has been
|
|
33
|
+
# freed" — misread as a GC/lifecycle bug. Name the contract.
|
|
34
|
+
unless document.is_a?(Leptris::XML::Document)
|
|
35
|
+
raise ArgumentError,
|
|
36
|
+
"second argument must be the host Leptris::XML::Document " \
|
|
37
|
+
"(got #{document.inspect})"
|
|
38
|
+
end
|
|
30
39
|
# TODO.perf/34: the C face answers the fragment address with
|
|
31
40
|
# no status MemoryPointer and no Pointer mint; failures fall
|
|
32
41
|
# back to the FFI pair for the exact error.
|
|
@@ -60,4 +69,33 @@ class Leptris::XML::DocumentFragment
|
|
|
60
69
|
def name
|
|
61
70
|
"#document-fragment"
|
|
62
71
|
end
|
|
72
|
+
|
|
73
|
+
# The serialization face (leptris#1781): a fragment serializes as
|
|
74
|
+
# its children, concatenated — the synthetic "#document-fragment"
|
|
75
|
+
# wrapper never emits (Nokogiri's contract). One C pass over the
|
|
76
|
+
# child chain — the same pass Element#inner_html uses; the FFI
|
|
77
|
+
# fallback walks the children per node.
|
|
78
|
+
def to_html
|
|
79
|
+
ensure_host_alive!
|
|
80
|
+
return Leptris::XML::Native.fast_inner_xml(@c_address) if
|
|
81
|
+
defined?(Leptris::XML::NATIVE_FAST)
|
|
82
|
+
children.map(&:to_html).join
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def to_xml(*, **)
|
|
86
|
+
to_html
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_s
|
|
90
|
+
to_html
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def ensure_host_alive!
|
|
96
|
+
# the document's own convention (UseAfterFreeError) — the
|
|
97
|
+
# fragment borrows the host's lifetime
|
|
98
|
+
raise Leptris::XML::UseAfterFreeError, "document has been freed" if
|
|
99
|
+
@document.freed?
|
|
100
|
+
end
|
|
63
101
|
end
|
data/lib/leptris/xml/native.so
CHANGED
|
Binary file
|
|
Binary file
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.373] - 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **html: the #1771 verdict-parity gate (PRs #1778, #1779, #1780,
|
|
8
|
+
#1782, #1783).** The HTML5 corpus lane now gates on the §13.2.5
|
|
9
|
+
parse-error stream, not just tree shape: html5lib's own #errors
|
|
10
|
+
verdict vs `LEPTRIS_PARSE_COLLECT_HTML_ERRORS` per case. New
|
|
11
|
+
emission sites — the doctype-less initial insertion-mode family
|
|
12
|
+
(chars/start-tag/end-tag/EOF), EOF with open elements, the
|
|
13
|
+
raw-text EOF variants (named vs generic per the corpus's own
|
|
14
|
+
mapping), frameset-after-body-content, and the fenced
|
|
15
|
+
any-other-end-tag walk. Verdict parity 190 → 1394 of 1555 (floor
|
|
16
|
+
pinned); code-set overlap 975/1231, 417 exact; trees
|
|
17
|
+
byte-identical throughout (1555/1555).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **docs: the mission and the mission plan (#1776, #1777).** The
|
|
22
|
+
two-sided bar — conformance suites as the gate, benchmark
|
|
23
|
+
supremacy as the goal — now lives in CLAUDE.md, the README, and
|
|
24
|
+
`docs/MISSION-PLAN.md` (four falsifiable gates, phase
|
|
25
|
+
sequencing, invariants).
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## [1.9.372] - 2026-10-19
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **test: document-context XPath parity pin (leptris-ruby#502).**
|
|
33
|
+
Test-only release: pins the `document()`/context-node parity
|
|
34
|
+
contract with a side-by-side Nokogiri regression spec in
|
|
35
|
+
`test/xpath/test_xpath.cpp` — expressions evaluated with a NULL
|
|
36
|
+
context resolve against the document node, matching Nokogiri.
|
|
37
|
+
No library-code changes; runtime rides re-pin to this engine.
|
|
38
|
+
|
|
39
|
+
|
|
3
40
|
## [1.9.371] - 2026-10-19
|
|
4
41
|
|
|
5
42
|
### Added
|
|
@@ -6,6 +6,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
6
6
|
|
|
7
7
|
**libleptris** — pure C99 XML 1.0 parser, XPath 1.0 engine (W3C-conformant), and SAX parser. Ships as a static/shared library plus a `leptris` CLI. Zero required runtime deps; `utf8proc` (Unicode) and `iconv` (encoding conversion) are optional.
|
|
8
8
|
|
|
9
|
+
**Mission.** Replace the libxml2 era with a clean-room, dependency-free XML stack that is simultaneously more conformant and faster than the incumbents, delivered natively to every language it reaches. The bar is two-sided:
|
|
10
|
+
|
|
11
|
+
- **Conformance suites are the gate.** W3C XPath (438/438), libxslt (205/205), QT3, the WHATWG/html5lib corpus — a feature isn't done until the suite says so.
|
|
12
|
+
- **Benchmark supremacy is the goal.** No published scoreboard row — parse, serialize, XPath, walk+read, build — may trail nokogiri, libxml2, or pugixml.
|
|
13
|
+
|
|
14
|
+
One engine, four runtime bindings — leptris-ruby (Nokogiri-compatible), leptris-py, leptris-ts (WebAssembly), leptris-rs — same engine, same numbers everywhere.
|
|
15
|
+
|
|
9
16
|
## Build & Test
|
|
10
17
|
|
|
11
18
|
CMake-based (CMake ≥ 3.20). The default build is a static lib + CLI + tests:
|
|
@@ -17,6 +17,28 @@ canonicalization (C14N). It is designed for low-latency,
|
|
|
17
17
|
memory-efficient workloads where predictable performance and safe
|
|
18
18
|
memory handling matter.
|
|
19
19
|
|
|
20
|
+
== Mission
|
|
21
|
+
|
|
22
|
+
Replace the libxml2 era with a clean-room, dependency-free XML stack
|
|
23
|
+
that is simultaneously more conformant and faster than the incumbents,
|
|
24
|
+
delivered natively to every language it reaches.
|
|
25
|
+
|
|
26
|
+
The bar is two-sided:
|
|
27
|
+
|
|
28
|
+
* **Conformance suites are the gate.** W3C XPath (438/438), libxslt
|
|
29
|
+
(205/205), QT3, the WHATWG/html5lib corpus — a feature isn't done
|
|
30
|
+
until the suite says so.
|
|
31
|
+
* **Benchmark supremacy is the goal.** No published scoreboard row —
|
|
32
|
+
parse, serialize, XPath, walk+read, build — may trail nokogiri,
|
|
33
|
+
libxml2, or pugixml.
|
|
34
|
+
|
|
35
|
+
One engine, four runtime bindings —
|
|
36
|
+
https://github.com/leptris/leptris-ruby[leptris-ruby]
|
|
37
|
+
(Nokogiri-compatible), https://github.com/leptris/leptris-py[leptris-py],
|
|
38
|
+
https://github.com/leptris/leptris-ts[leptris-ts] (WebAssembly), and
|
|
39
|
+
https://github.com/leptris/leptris-rs[leptris-rs] — same engine, same
|
|
40
|
+
numbers everywhere.
|
|
41
|
+
|
|
20
42
|
== Highlights
|
|
21
43
|
|
|
22
44
|
* **XML 1.0 parsing** with UTF-8 validation and optional encoding
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# The Mission Plan
|
|
2
|
+
|
|
3
|
+
How the mission — *replace the libxml2 era with a clean-room,
|
|
4
|
+
dependency-free XML stack that is simultaneously more conformant and
|
|
5
|
+
faster than the incumbents, delivered natively to every language it
|
|
6
|
+
reaches* — is fully achieved.
|
|
7
|
+
|
|
8
|
+
"Fully achieved" is not a feeling. The mission decomposes into four
|
|
9
|
+
falsifiable gates. Each gate has a measurement command whose output is
|
|
10
|
+
the verdict. Every number below is **last-known (date)** — Phase 0
|
|
11
|
+
re-baselines everything before any work is sequenced against it.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Gate A — Conformance: no suite red anywhere
|
|
16
|
+
|
|
17
|
+
A feature isn't done until the suite says so. Conformance is the
|
|
18
|
+
moat: it is the one axis where "clean-room" must prove it gave up
|
|
19
|
+
nothing to libxml2.
|
|
20
|
+
|
|
21
|
+
| # | Suite | Exit | Last known |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| A1 | W3C XPath 1.0 | 438/438 pinned in CI | **done** — 438/438 |
|
|
24
|
+
| A2 | libxslt (XSLT 1.0) | 205/205 pinned (Linux-authoritative; macOS skips bug-169 iconv-encoding) | **done** — 205/205 (v1.9.19) |
|
|
25
|
+
| A3 | XsltFull | 27/27 + full suite green; XSLT gate (v1.10.0) stays met | **done** — gate MET (#1761) |
|
|
26
|
+
| A4 | QT3 / XQuery 3.1 | 0 unexplained pins; every pin has a recorded disposition | pins ~41–99 by slice (2026-09/10) — **re-pin fresh** |
|
|
27
|
+
| A5 | WHATWG/html5lib (#659) | 0 reds on the tree gate; verdict parity climbing on the #1771 error-verdict gate | **tree lane CLOSED** — 1555/1555, redlist retired (found during Phase 0); verdict gate flipped 2026-10-19: 1312/1555, floor pinned |
|
|
28
|
+
| A6 | XSD conformance lane | suite-complete per the lane's own queue | 769→795 (2026-10-14, queue #1691) |
|
|
29
|
+
| A7 | DTD phase 8 (TODO 91) | ENTITY/ENTITIES attribute resolution, parameter entities, choice-model backtracking | open |
|
|
30
|
+
| A8 | XInclude parse="xml" + xpointer (TODO 92) | spec-complete | open (parse="text" + fallback ship) |
|
|
31
|
+
| A9 | RelaxNG / C14N / SAX | parity maintained, no reds | green |
|
|
32
|
+
|
|
33
|
+
**Working rule (proven across A1–A3):** suite-driven fixes only —
|
|
34
|
+
read the failing case, name the root cause, red-first spec, fix,
|
|
35
|
+
re-run the whole suite. The recurring root causes are already
|
|
36
|
+
documented (RTF binding, foreign-doc patterns, §3.4 ordering,
|
|
37
|
+
xmlXPathFormatNumber, define-body sibling lists, carrier-by-pointer).
|
|
38
|
+
Every engine fix lands with its regression spec before the next case
|
|
39
|
+
is opened.
|
|
40
|
+
|
|
41
|
+
**Exit:** `scripts/validate.sh` green + each suite's command in
|
|
42
|
+
`VALIDATION.md` green + zero pins without a disposition file.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Gate B — Performance: no published row trails
|
|
47
|
+
|
|
48
|
+
The scoreboard is published pairs — CPU-time medians, 31
|
|
49
|
+
interleaved reps, fresh build dirs, identical `CMAKE_BUILD_TYPE`
|
|
50
|
+
on both sides. The discipline *is* the result; phantom wins come
|
|
51
|
+
from stale build dirs, so the harness protocol is part of the gate.
|
|
52
|
+
|
|
53
|
+
| Row | Bar | Last known |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| parse | ≥ nokogiri/libxml2/pugixml | 2.6x — ahead |
|
|
56
|
+
| serialize | ≥ all | 2.6x — ahead |
|
|
57
|
+
| XPath | ≥ all | 18x — ahead |
|
|
58
|
+
| attr listing | ≥ all | 99x — ahead |
|
|
59
|
+
| build | ≥ all | 0.87x — ahead |
|
|
60
|
+
| walk+read (moxml) | ≥ 0.95x nokogiri | **done 2026-10-19** — ~195ns name / ~193ns content, 0.90–0.95x |
|
|
61
|
+
| append / attr-parse (pugixml lane 18) | ≥ parity | **RED 2.0x / 5.9x** — hash-prefilter lever named |
|
|
62
|
+
| DOM mutation rows | ≥ parity | RED — node-layout redesign, owner decision pending |
|
|
63
|
+
| zero-copy text (#1218 lever) | adopt if it wins any row | lever named, unmeasured |
|
|
64
|
+
|
|
65
|
+
**Working rules:** algorithms before struct bytes (the attr-parse and
|
|
66
|
+
append rounds proved it); measure deltas, not absolutes (spill and
|
|
67
|
+
extension-block effects are invisible in means — count events);
|
|
68
|
+
one local build dir at -j4, CI is the gate; macro-benchmarks claim
|
|
69
|
+
nothing without interleaved A/B medians.
|
|
70
|
+
|
|
71
|
+
**Exit:** every published row ≥ 1.00x median-of-runs on the published
|
|
72
|
+
harnesses + chokepoint regression gates in CI (the write-path and
|
|
73
|
+
attr-path chokepoints get the same treatment conformance got).
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Gate C — Delivery: the engine reaches every language, natively
|
|
78
|
+
|
|
79
|
+
The engine is nothing until a consumer can install it. Lockstep law:
|
|
80
|
+
runtime versions are `{engine-semver}.{binding-patch}`; every engine
|
|
81
|
+
release gets its runtime ride; ride branches push only after the tag
|
|
82
|
+
lands (TAG-RACE).
|
|
83
|
+
|
|
84
|
+
| # | Surface | Exit | Last known |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| C1 | leptris-ruby / -py / -ts rides | automated, proven (1.9.372 train: rubygems full matrix, PyPI, npm+provenance) | **done**, keep green |
|
|
87
|
+
| C2 | leptris-rs descriptor crate | `leptris-descriptor` published; safe-wrapper crate rides lockstep | feat/294 in flight, not published |
|
|
88
|
+
| C3 | moxml gem | cut (owner-dispatched) carries the walk+read adoption | merged on main, release pending owner |
|
|
89
|
+
| C4 | Prebuilt binaries everywhere consumers lack compilers | rubygems platform matrix ✓, PyPI wheels ✓, npm WASM ✓ | **done**, keep green |
|
|
90
|
+
| C5 | System packaging | vcpkg ✓; add homebrew / conan / deb formulas so "install libleptris" is one command per platform | open |
|
|
91
|
+
| C6 | Cargo publish lane | OIDC-free trusted publishing (crates-io-auth-action@v1) wired into the rust-release workflow; CARGO_REGISTRY_TOKEN secret set once | workflow shipped; secret/token state to verify in Phase 0 |
|
|
92
|
+
|
|
93
|
+
**Exit:** a consumer on any of the four runtimes installs a prebuilt
|
|
94
|
+
binary with one command; the descriptor ABI crate is consumable from
|
|
95
|
+
Rust with no engine checkout.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Gate D — Adoption & durability: the libxml2 era actually ends
|
|
100
|
+
|
|
101
|
+
Replacing an era is an ecosystem event, not a benchmark. This gate is
|
|
102
|
+
the proof that downstream can and does move.
|
|
103
|
+
|
|
104
|
+
| # | Proof | Exit |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| D1 | Downstream corpus compiles end-to-end on leptris — metanorma/lutaml document sets parse, validate, transform, serialize with libxml2 removed from the path | green corpus runs recorded per release |
|
|
107
|
+
| D2 | Migration surface documented — nokogiri-compat face, per-adapter notes in moxml, a libxml2→libleptris migration guide in `docs/guide/` | guide ships |
|
|
108
|
+
| D3 | Security posture — nightly fuzzing ✓, ASAN CI ✓, ASAN Linux-only leak gate ✓; plus a documented advisory/reporting channel and a CVE-response note | security.md ships |
|
|
109
|
+
| D4 | 1.0 — semver freeze, ABI stability policy (export-surface gate stays green), documented platform matrix (ILP32/arm64/Intel legs, s390x gate) | v1.0.0 tagged through the release workflow, never by hand |
|
|
110
|
+
| D5 | Durability gates in CI — conformance suites and perf chokepoints fail the build on regression, so the mission cannot silently erode | gates wired into test.yml/asan.yml/fuzz-nightly.yml siblings |
|
|
111
|
+
|
|
112
|
+
**Exit:** D1 green + v1.0.0 shipped. That is the day the era ends.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Phase sequencing
|
|
117
|
+
|
|
118
|
+
**Phase 0 — Re-baseline (first, everything depends on it).**
|
|
119
|
+
Run every Gate A command and every Gate B harness fresh. Produce one
|
|
120
|
+
numbered table dated today. Nothing in this plan is sequenced against
|
|
121
|
+
a stale number. (The #659 and QT3 figures above are already stale.)
|
|
122
|
+
|
|
123
|
+
**Phase 1 — Conformance closure (A4–A8).** Largest and longest. Order
|
|
124
|
+
by leverage: A5 (corpus tail — user-visible HTML correctness), A4
|
|
125
|
+
(QT3 pins — the XQuery story), A6 (XSD lane), A7/A8 (DTD/XInclude
|
|
126
|
+
completeness). One PR per fix, red-first spec with every fix,
|
|
127
|
+
suite-wide re-run per landing.
|
|
128
|
+
|
|
129
|
+
**Phase 2 — Performance closure (B reds).** Parallelizable with
|
|
130
|
+
Phase 1's tail: lane-18 hash prefilter first (named lever, biggest
|
|
131
|
+
gap), zero-copy text next, node-layout/mutation rows behind the
|
|
132
|
+
owner decision.
|
|
133
|
+
|
|
134
|
+
**Phase 3 — Delivery completion (C2/C3/C5/C6).** Descriptor crate
|
|
135
|
+
publish, moxml gem cut, packaging formulas. Small, mechanical, can
|
|
136
|
+
ride any release train.
|
|
137
|
+
|
|
138
|
+
**Phase 4 — Durability & adoption (D).** Migration guide, security
|
|
139
|
+
note, CI durability gates, then the 1.0 freeze. 1.0 ships **only
|
|
140
|
+
after** Gates A and B report zero reds — it is the seal, not a date.
|
|
141
|
+
|
|
142
|
+
## Invariants (the operating system)
|
|
143
|
+
|
|
144
|
+
These laws produced every number above; the plan executes inside
|
|
145
|
+
them, never around them:
|
|
146
|
+
|
|
147
|
+
- Falsifiability — red-first specs; a test that passes both sides is
|
|
148
|
+
not a regression test.
|
|
149
|
+
- Lockstep + TAG-RACE — runtime versions ride engine semver; ride
|
|
150
|
+
branches push only after the tag lands.
|
|
151
|
+
- Release workflow owns versions and tags; humans never tag by hand.
|
|
152
|
+
- Benchmark discipline — fresh build dirs, explicit build types,
|
|
153
|
+
medians of interleaved reps, or the number does not exist.
|
|
154
|
+
- One PR per work item; branch → tests → ASAN → benchmark →
|
|
155
|
+
rebase-merge; verify content on main after every merge.
|
|
156
|
+
- Binding repos release only through their own automated flows.
|
|
157
|
+
|
|
158
|
+
## The scoreboard of record
|
|
159
|
+
|
|
160
|
+
One table, updated at every release train: gate, command, last run,
|
|
161
|
+
verdict. When every row of Gate A and Gate B reads "green (dated)"
|
|
162
|
+
and Gate D1 is green, the mission is achieved — and v1.0.0 is the
|
|
163
|
+
tag that says so.
|
|
@@ -3139,6 +3139,8 @@ static uint8_t h_id_p, h_id_li, h_id_dd, h_id_dt, h_id_ol, h_id_ul,
|
|
|
3139
3139
|
h_id_frameset, h_id_button, h_id_applet, h_id_caption,
|
|
3140
3140
|
h_id_colgroup, h_id_marquee, h_id_object, h_id_html, h_id_rb,
|
|
3141
3141
|
h_id_rt, h_id_rp, h_id_rtc, h_id_listing, h_id_plaintext;
|
|
3142
|
+
static uint8_t h_id_textarea, h_id_title, h_id_xmp, h_id_iframe,
|
|
3143
|
+
h_id_noembed, h_id_noframes;
|
|
3142
3144
|
static uint8_t h_heading_lut[256], h_ruby_lut[256], h_p_fence_lut[256],
|
|
3143
3145
|
h_li_fence_lut[256], h_dd_fence_lut[256];
|
|
3144
3146
|
/* #1218 slice 6: the per-name strcmp chains the sampler pinned at
|
|
@@ -3186,6 +3188,12 @@ static void h_id_luts_init(void) {
|
|
|
3186
3188
|
h_id_rtc = h_tag_id("rtc");
|
|
3187
3189
|
h_id_listing = h_tag_id("listing");
|
|
3188
3190
|
h_id_plaintext = h_tag_id("plaintext");
|
|
3191
|
+
h_id_textarea = h_tag_id("textarea");
|
|
3192
|
+
h_id_title = h_tag_id("title");
|
|
3193
|
+
h_id_xmp = h_tag_id("xmp");
|
|
3194
|
+
h_id_iframe = h_tag_id("iframe");
|
|
3195
|
+
h_id_noembed = h_tag_id("noembed");
|
|
3196
|
+
h_id_noframes = h_tag_id("noframes");
|
|
3189
3197
|
h_id_head = h_tag_id("head");
|
|
3190
3198
|
h_id_body = h_tag_id("body");
|
|
3191
3199
|
h_id_noscript = h_tag_id("noscript");
|
|
@@ -5584,6 +5592,22 @@ static void h_diag(HBuilder* b, const char* code, size_t off,
|
|
|
5584
5592
|
code, line, col, "%s", detail);
|
|
5585
5593
|
}
|
|
5586
5594
|
|
|
5595
|
+
/* #1771 verdict slice: a non-whitespace character run while the
|
|
5596
|
+
* doctype-less initial insertion mode is still open is the spec's
|
|
5597
|
+
* expected-doctype-but-got-chars (13.2.6.4.1). Judged once per run
|
|
5598
|
+
* here — ahead of the per-path flush/drop decisions, so dropped or
|
|
5599
|
+
* re-parented text still reports. */
|
|
5600
|
+
static void h_initial_chars_diag(HBuilder* b, const char* from,
|
|
5601
|
+
const char* to) {
|
|
5602
|
+
for (const char* c = from; c < to; c++)
|
|
5603
|
+
if (*c && !h_is_ws(*c)) {
|
|
5604
|
+
h_diag(b, "expected-doctype-but-got-chars",
|
|
5605
|
+
(size_t)(from - b->input_base),
|
|
5606
|
+
"characters in the initial insertion mode");
|
|
5607
|
+
return;
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5587
5611
|
static LeptrisDocument html_parse_shared(
|
|
5588
5612
|
const char* buf, size_t len, LeptrisStatus* status, int whatwg,
|
|
5589
5613
|
LeptrisParseFlags flags) {
|
|
@@ -5653,6 +5677,12 @@ static LeptrisDocument html_parse_shared(
|
|
|
5653
5677
|
if (p + 1 >= end) { p++; continue; }
|
|
5654
5678
|
char k1 = p[1];
|
|
5655
5679
|
|
|
5680
|
+
/* #1771: judge a pending character run against the
|
|
5681
|
+
* doctype-less initial mode before any branch flushes or
|
|
5682
|
+
* drops it (expected-doctype-but-got-chars). */
|
|
5683
|
+
if (b.whatwg && !b.left_initial && !doc->doctype && text < p)
|
|
5684
|
+
h_initial_chars_diag(&b, text, p);
|
|
5685
|
+
|
|
5656
5686
|
if (k1 == '!') {
|
|
5657
5687
|
/* Flush pending text first. */
|
|
5658
5688
|
if (text < p) {
|
|
@@ -6033,6 +6063,10 @@ static LeptrisDocument html_parse_shared(
|
|
|
6033
6063
|
continue;
|
|
6034
6064
|
}
|
|
6035
6065
|
/* End tag: ends the initial insertion mode too. */
|
|
6066
|
+
if (b.whatwg && !b.left_initial && !doc->doctype)
|
|
6067
|
+
h_diag(&b, "expected-doctype-but-got-end-tag",
|
|
6068
|
+
(size_t)(p - b.input_base),
|
|
6069
|
+
"end tag in the initial insertion mode");
|
|
6036
6070
|
if (b.whatwg && !b.left_initial && text < p) {
|
|
6037
6071
|
int ws_only = 1;
|
|
6038
6072
|
for (const char* c = text; c < p; c++)
|
|
@@ -6559,7 +6593,21 @@ static LeptrisDocument html_parse_shared(
|
|
|
6559
6593
|
(h_ieq_raw(on, "ul") ||
|
|
6560
6594
|
h_ieq_raw(on, "ol")));
|
|
6561
6595
|
}
|
|
6562
|
-
if (fenced)
|
|
6596
|
+
if (fenced) {
|
|
6597
|
+
/* #1771: html5lib's walk reports
|
|
6598
|
+
* unexpected-end-tag when a special
|
|
6599
|
+
* element stops the search before
|
|
6600
|
+
* the target is found (endTagOther);
|
|
6601
|
+
* the list-scope fences map to its
|
|
6602
|
+
* endTagListItem not-in-scope
|
|
6603
|
+
* error. */
|
|
6604
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6605
|
+
(size_t)(p - b.input_base),
|
|
6606
|
+
"</%s> ignored: a special "
|
|
6607
|
+
"element fences the target",
|
|
6608
|
+
lname);
|
|
6609
|
+
break;
|
|
6610
|
+
}
|
|
6563
6611
|
}
|
|
6564
6612
|
if (on && tag_match) {
|
|
6565
6613
|
/* Scope guard (WHATWG): a foreign
|
|
@@ -6816,6 +6864,13 @@ static LeptrisDocument html_parse_shared(
|
|
|
6816
6864
|
if (*c != 0 && !h_is_ws(*c)) { ws_only = 0; break; }
|
|
6817
6865
|
if (ws_only) text = p;
|
|
6818
6866
|
}
|
|
6867
|
+
/* #1771: a start tag in the doctype-less initial insertion
|
|
6868
|
+
* mode is expected-doctype-but-got-start-tag (13.2.6.4.1) —
|
|
6869
|
+
* even when the tag itself is a dropped structural one. */
|
|
6870
|
+
if (b.whatwg && !b.left_initial && !doc->doctype)
|
|
6871
|
+
h_diag(&b, "expected-doctype-but-got-start-tag",
|
|
6872
|
+
(size_t)(p - b.input_base),
|
|
6873
|
+
"start tag in the initial insertion mode");
|
|
6819
6874
|
b.left_initial = 1;
|
|
6820
6875
|
/* 13.2.5.4.4: non-whitespace body text (NUL ignored — an
|
|
6821
6876
|
* ignored token cannot clear the flag) clears frameset-ok;
|
|
@@ -7022,6 +7077,18 @@ static LeptrisDocument html_parse_shared(
|
|
|
7022
7077
|
/* frameset is NOT in the 13.2.6.5 breakout list: inside
|
|
7023
7078
|
* foreign content it is a plain foreign element
|
|
7024
7079
|
* (plain-text-unsafe:16-23). */
|
|
7080
|
+
/* #1771: html5lib's verdict — a <frameset> token that
|
|
7081
|
+
* reaches the body phase after body content is
|
|
7082
|
+
* unexpected-start-tag whether it then converts or is
|
|
7083
|
+
* ignored (webkit01:51); head-phase converters and
|
|
7084
|
+
* in-template framesets stay clean. body_seen is not
|
|
7085
|
+
* set by the frameset token itself (it is in the
|
|
7086
|
+
* head-only list), so it reads PRIOR content only. */
|
|
7087
|
+
if (b.body_seen && !b.frameset &&
|
|
7088
|
+
h_template_idx(&b) < 0)
|
|
7089
|
+
h_diag(&b, "unexpected-start-tag",
|
|
7090
|
+
(size_t)(p - b.input_base),
|
|
7091
|
+
"frameset after body content");
|
|
7025
7092
|
/* depth<=1: at most the (explicit or synthesized) html
|
|
7026
7093
|
* element is open; frameset-ok is the spec gate and the
|
|
7027
7094
|
* body is removed wholesale when it converts. */
|
|
@@ -8121,6 +8188,27 @@ static LeptrisDocument html_parse_shared(
|
|
|
8121
8188
|
cq++;
|
|
8122
8189
|
}
|
|
8123
8190
|
p = (cq < end) ? cq + 1 : end;
|
|
8191
|
+
/* #1771: the element never closed at EOF — either the
|
|
8192
|
+
* raw scan consumed to EOF without a close tag, or the
|
|
8193
|
+
* close tag itself is unterminated (</script foo= —
|
|
8194
|
+
* tests16:11-60). html5lib's named variant for the
|
|
8195
|
+
* strict containers, the generic one for the RCDATA
|
|
8196
|
+
* pair (mapping mined from the corpus: script/style/
|
|
8197
|
+
* xmp/noframes/noembed/iframe -> named; textarea/
|
|
8198
|
+
* title -> generic; plaintext has no close form and
|
|
8199
|
+
* never reports). Judged here, before the pop. */
|
|
8200
|
+
if (b.whatwg && cq >= end &&
|
|
8201
|
+
strcmp(name, "plaintext") != 0) {
|
|
8202
|
+
int named = strcmp(name, "textarea") != 0 &&
|
|
8203
|
+
strcmp(name, "title") != 0;
|
|
8204
|
+
h_diag(&b,
|
|
8205
|
+
named ? "expected-named-closing-tag-but-got-eof"
|
|
8206
|
+
: "expected-closing-tag-but-got-eof",
|
|
8207
|
+
(size_t)(end - b.input_base),
|
|
8208
|
+
"raw-text element <%s> left open at end of "
|
|
8209
|
+
"input",
|
|
8210
|
+
name);
|
|
8211
|
+
}
|
|
8124
8212
|
b.depth--; /* the raw element is complete */
|
|
8125
8213
|
text = p;
|
|
8126
8214
|
continue;
|
|
@@ -8201,6 +8289,11 @@ static LeptrisDocument html_parse_shared(
|
|
|
8201
8289
|
}
|
|
8202
8290
|
|
|
8203
8291
|
/* Trailing text. */
|
|
8292
|
+
/* #1771: a trailing character run in the doctype-less initial
|
|
8293
|
+
* insertion mode is expected-doctype-but-got-chars too — the
|
|
8294
|
+
* loop-internal judge only runs at markup boundaries. */
|
|
8295
|
+
if (b.whatwg && !b.left_initial && !doc->doctype && text < end)
|
|
8296
|
+
h_initial_chars_diag(&b, text, end);
|
|
8204
8297
|
if (text < end) {
|
|
8205
8298
|
LeptrisTextNode* t = h_text_node(&b, text, end);
|
|
8206
8299
|
if (t) {
|
|
@@ -8230,6 +8323,30 @@ done:
|
|
|
8230
8323
|
/* Nothing appended (empty input, stray end tags only, doctype
|
|
8231
8324
|
* only) is NOT an error in lenient HTML mode: the wrapper
|
|
8232
8325
|
* synthesis below yields the empty Nokogiri shape. */
|
|
8326
|
+
/* #1771: elements still open at EOF are
|
|
8327
|
+
* expected-closing-tag-but-got-eof (html5lib's verdict; the
|
|
8328
|
+
* implied html shell does not count). Judged BEFORE the
|
|
8329
|
+
* wrapper synthesis, which tears the open stack down. Our
|
|
8330
|
+
* stack carries no implied html/body, so the html5lib
|
|
8331
|
+
* top-of-stack rule maps to: any real element open, gated on
|
|
8332
|
+
* </body>/</html> never having been seen — after-body EOF is
|
|
8333
|
+
* clean whatever remains (tests1:34). A raw-text container
|
|
8334
|
+
* got its own variant at the raw branch already (that branch
|
|
8335
|
+
* pops the element, so the top here is whatever wraps it). */
|
|
8336
|
+
if (b.whatwg && b.depth > 0) {
|
|
8337
|
+
uint8_t top = b.open_id[b.depth - 1];
|
|
8338
|
+
if (top != h_id_html && top != h_id_head &&
|
|
8339
|
+
top != h_id_body)
|
|
8340
|
+
h_diag(&b,
|
|
8341
|
+
(top == h_id_script || top == h_id_style ||
|
|
8342
|
+
top == h_id_listing || top == h_id_plaintext ||
|
|
8343
|
+
top == h_id_textarea || top == h_id_title ||
|
|
8344
|
+
top == h_id_xmp || top == h_id_iframe ||
|
|
8345
|
+
top == h_id_noembed || top == h_id_noframes)
|
|
8346
|
+
? "expected-named-closing-tag-but-got-eof"
|
|
8347
|
+
: "expected-closing-tag-but-got-eof",
|
|
8348
|
+
b.input_len, "elements left open at end of input");
|
|
8349
|
+
}
|
|
8233
8350
|
/* Nokogiri document shape (#659): with no explicit <html>,
|
|
8234
8351
|
* synthesize <html><head></head><body>content</body></html> —
|
|
8235
8352
|
* the document model is single-rooted, and libxml2's
|
|
@@ -8361,6 +8478,12 @@ done:
|
|
|
8361
8478
|
b.body_attr_n);
|
|
8362
8479
|
}
|
|
8363
8480
|
}
|
|
8481
|
+
/* #1771: EOF in the doctype-less initial insertion mode is
|
|
8482
|
+
* expected-doctype-but-got-eof (13.2.6.4.1) — empty and
|
|
8483
|
+
* whitespace-only documents included. */
|
|
8484
|
+
if (b.whatwg && !b.left_initial && !doc->doctype)
|
|
8485
|
+
h_diag(&b, "expected-doctype-but-got-eof", b.input_len,
|
|
8486
|
+
"document ended without a DOCTYPE");
|
|
8364
8487
|
doc->new_dom_root = b.root;
|
|
8365
8488
|
leptris_root_doc_register(b.root, doc);
|
|
8366
8489
|
if (b.prolog_head) {
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
#include <gtest/gtest.h>
|
|
22
22
|
extern "C" {
|
|
23
23
|
#include "leptris.h"
|
|
24
|
+
#include "leptris/html.h"
|
|
25
|
+
#include "leptris/error.h"
|
|
24
26
|
/* Internal (non-exported) accessor needed for the ns comparison;
|
|
25
27
|
* the public surface exposes attribute/namespace-node URIs only. */
|
|
26
28
|
const char* leptris_element_get_namespace_uri(LeptrisElement elem);
|
|
@@ -50,6 +52,10 @@ struct XCase {
|
|
|
50
52
|
bool skip = false; /* fragment pairs / script-on */
|
|
51
53
|
std::string skip_why;
|
|
52
54
|
XNode doc; /* synthesized document level */
|
|
55
|
+
/* the #errors section, verbatim "(line,col): code" entries —
|
|
56
|
+
* the html5lib suite's own parse-error verdict (leptris#1771
|
|
57
|
+
* verdict-parity gate) */
|
|
58
|
+
std::vector<std::string> errors;
|
|
53
59
|
XCase() { doc.kind = XNode::DOC; }
|
|
54
60
|
};
|
|
55
61
|
|
|
@@ -327,7 +333,11 @@ std::vector<XCase> ScanFile(const std::string& path, const std::string& fname,
|
|
|
327
333
|
} else if (l.compare(0, 7, "#errors") == 0) {
|
|
328
334
|
i++;
|
|
329
335
|
while (i < lines.size() &&
|
|
330
|
-
!(lines[i].size() && lines[i][0] == '#'))
|
|
336
|
+
!(lines[i].size() && lines[i][0] == '#')) {
|
|
337
|
+
if (!lines[i].empty())
|
|
338
|
+
c.errors.push_back(lines[i]);
|
|
339
|
+
i++;
|
|
340
|
+
}
|
|
331
341
|
} else if (l.compare(0, 18, "#document-fragment") == 0) {
|
|
332
342
|
c.skip = true;
|
|
333
343
|
c.skip_why = "fragment mode";
|
|
@@ -829,3 +839,147 @@ TEST(Html5LibCorpus, TreeConstruction) {
|
|
|
829
839
|
EXPECT_GE(ppassed, (size_t)784);
|
|
830
840
|
}
|
|
831
841
|
}
|
|
842
|
+
|
|
843
|
+
/* ---- #1771 VERDICT PARITY (leptris#1771) ----
|
|
844
|
+
*
|
|
845
|
+
* The tree gate proves WHAT we build; this gate proves we JUDGE the
|
|
846
|
+
* same inputs the same way. The corpus's #errors section is
|
|
847
|
+
* html5lib's own parse-error verdict for every case; ours is the
|
|
848
|
+
* §13.2.5 token stream recorded through
|
|
849
|
+
* LEPTRIS_PARSE_COLLECT_HTML_ERRORS (leptris_parse_html_string_flags).
|
|
850
|
+
* The gate is VERDICT parity — did we error where they errored —
|
|
851
|
+
* per case; the code sets are compared too and dumped to the
|
|
852
|
+
* red-list for the tail. Positions are not compared (offset
|
|
853
|
+
* conventions differ legitimately). */
|
|
854
|
+
TEST(Html5LibCorpus, ErrorVerdictParity) {
|
|
855
|
+
const char* srcdir = getenv("HTML5LIB_SRC");
|
|
856
|
+
std::string dir = srcdir && *srcdir
|
|
857
|
+
? srcdir
|
|
858
|
+
: LEPTRIS_HTML5LIB_DIR;
|
|
859
|
+
std::vector<std::string> files(kHtml5libDatFiles,
|
|
860
|
+
kHtml5libDatFiles +
|
|
861
|
+
sizeof(kHtml5libDatFiles) /
|
|
862
|
+
sizeof(kHtml5libDatFiles[0]));
|
|
863
|
+
size_t total = 0, parity = 0, skipped = 0, vfail = 0;
|
|
864
|
+
size_t code_both = 0, code_overlap = 0, code_equal = 0;
|
|
865
|
+
std::vector<std::string> failures;
|
|
866
|
+
std::string err;
|
|
867
|
+
for (const std::string& fn : files) {
|
|
868
|
+
std::vector<XCase> cs = ScanFile(dir + "/" + fn, fn, &err, 0, 1);
|
|
869
|
+
if (!err.empty() && cs.empty()) {
|
|
870
|
+
ADD_FAILURE() << fn << ": " << err;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
for (auto& c : cs) {
|
|
874
|
+
total++;
|
|
875
|
+
if (c.skip) { skipped++; continue; }
|
|
876
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
877
|
+
LeptrisDocument d = leptris_parse_html_string_flags(
|
|
878
|
+
c.data.c_str(), c.data.size(),
|
|
879
|
+
LEPTRIS_PARSE_COLLECT_HTML_ERRORS, &st);
|
|
880
|
+
if (!d) {
|
|
881
|
+
vfail++;
|
|
882
|
+
failures.push_back(c.id + ": parse failed");
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
size_t n = leptris_document_parse_diag_count(d);
|
|
886
|
+
bool ours_err = n > 0;
|
|
887
|
+
bool want_err = !c.errors.empty();
|
|
888
|
+
std::string ours_codes, want_codes;
|
|
889
|
+
size_t shown = 0;
|
|
890
|
+
for (size_t i = 0; i < n && shown < 6; i++) {
|
|
891
|
+
char code[64] = {0};
|
|
892
|
+
if (leptris_document_parse_diag_code(d, i, code,
|
|
893
|
+
sizeof(code),
|
|
894
|
+
nullptr, nullptr) &&
|
|
895
|
+
code[0]) {
|
|
896
|
+
if (shown++) ours_codes += ",";
|
|
897
|
+
ours_codes += code;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
for (size_t i = 0; i < c.errors.size(); i++) {
|
|
901
|
+
const std::string& e = c.errors[i];
|
|
902
|
+
/* corpus formats: "(l,c): code", "(l,c) code",
|
|
903
|
+
* "(l:c) code" — strip the "(...)" position. */
|
|
904
|
+
std::string code = e;
|
|
905
|
+
if (e.size() > 1 && e[0] == '(') {
|
|
906
|
+
size_t close = e.find(')');
|
|
907
|
+
if (close != std::string::npos) {
|
|
908
|
+
size_t cs = close + 1;
|
|
909
|
+
while (cs < e.size() &&
|
|
910
|
+
(e[cs] == ':' || e[cs] == ' '))
|
|
911
|
+
cs++;
|
|
912
|
+
code = e.substr(cs);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (!want_codes.empty()) want_codes += ",";
|
|
916
|
+
want_codes += code;
|
|
917
|
+
}
|
|
918
|
+
leptris_document_free(d);
|
|
919
|
+
|
|
920
|
+
if (ours_err == want_err) {
|
|
921
|
+
parity++;
|
|
922
|
+
if (ours_err) {
|
|
923
|
+
/* secondary: code-set comparison (reported,
|
|
924
|
+
* not gated — the verdict is the flip) */
|
|
925
|
+
code_both++;
|
|
926
|
+
bool overlap = false, equal = false;
|
|
927
|
+
/* both sides are small comma lists; exact-set
|
|
928
|
+
* comparison on the shown prefixes */
|
|
929
|
+
equal = ours_codes == want_codes;
|
|
930
|
+
for (size_t a = 0, b = a; !overlap &&
|
|
931
|
+
a < ours_codes.size(); ) {
|
|
932
|
+
size_t ca = ours_codes.find(',', a);
|
|
933
|
+
std::string oc = ours_codes.substr(
|
|
934
|
+
a, ca == std::string::npos
|
|
935
|
+
? std::string::npos : ca - a);
|
|
936
|
+
for (b = 0; b < want_codes.size(); ) {
|
|
937
|
+
size_t cb = want_codes.find(',', b);
|
|
938
|
+
std::string wc = want_codes.substr(
|
|
939
|
+
b, cb == std::string::npos
|
|
940
|
+
? std::string::npos : cb - b);
|
|
941
|
+
if (oc == wc) { overlap = true; break; }
|
|
942
|
+
if (cb == std::string::npos) break;
|
|
943
|
+
b = cb + 1;
|
|
944
|
+
}
|
|
945
|
+
if (ca == std::string::npos) break;
|
|
946
|
+
a = ca + 1;
|
|
947
|
+
}
|
|
948
|
+
if (overlap) code_overlap++;
|
|
949
|
+
if (equal) code_equal++;
|
|
950
|
+
if (!overlap)
|
|
951
|
+
failures.push_back(c.id + ": code-set divergence want["
|
|
952
|
+
+ want_codes + "] ours["
|
|
953
|
+
+ ours_codes + "]");
|
|
954
|
+
}
|
|
955
|
+
} else {
|
|
956
|
+
vfail++;
|
|
957
|
+
failures.push_back(
|
|
958
|
+
c.id + ": verdict mismatch want"
|
|
959
|
+
+ (want_err ? "=errors[" + want_codes + "]"
|
|
960
|
+
: "=clean")
|
|
961
|
+
+ " ours"
|
|
962
|
+
+ (ours_err ? "=errors[" + ours_codes + "]"
|
|
963
|
+
: "=clean"));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
FILE* rl = fopen("html5lib-verdict-redlist.txt", "w");
|
|
968
|
+
if (rl) {
|
|
969
|
+
for (auto& f : failures) fprintf(rl, "%s\n", f.c_str());
|
|
970
|
+
fclose(rl);
|
|
971
|
+
}
|
|
972
|
+
printf("[verdict-parity] total=%zu parity=%zu verdict-fail=%zu "
|
|
973
|
+
"skip=%zu | both-error=%zu code-overlap=%zu code-equal=%zu "
|
|
974
|
+
"(red-list: html5lib-verdict-redlist.txt)\n",
|
|
975
|
+
total, parity, vfail, skipped,
|
|
976
|
+
code_both, code_overlap, code_equal);
|
|
977
|
+
for (size_t i = 0; i < failures.size() && i < 20; i++)
|
|
978
|
+
printf(" VERDICT %s\n", failures[i].c_str());
|
|
979
|
+
EXPECT_GT(total, (size_t)1500);
|
|
980
|
+
/* Verdict-parity floor — set at the slice-2 baseline
|
|
981
|
+
* (1394/1555; was 190 before the #1771 emission slices —
|
|
982
|
+
* the doctype-less initial-mode family, trailing chars, EOF with
|
|
983
|
+
* open elements; slice 3 adds the raw-text EOF variants). Only raises. */
|
|
984
|
+
EXPECT_GE(parity, (size_t)1394);
|
|
985
|
+
}
|
|
@@ -70,10 +70,16 @@ TEST(HtmlParseErrors, EofInTagCarriesSpecCode) {
|
|
|
70
70
|
xml, strlen(xml), LEPTRIS_PARSE_COLLECT_HTML_ERRORS, &st);
|
|
71
71
|
ASSERT_NE(d, nullptr);
|
|
72
72
|
auto diags = drain(d);
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
/* The doctype-less initial mode fires first (the <a> token),
|
|
74
|
+
* then the unterminated tag at EOF — html5lib's verdict for
|
|
75
|
+
* this input is both codes, in token order. */
|
|
76
|
+
ASSERT_GE(diags.size(), 2u);
|
|
77
|
+
EXPECT_EQ(diags[0].code, "expected-doctype-but-got-start-tag");
|
|
75
78
|
EXPECT_EQ(diags[0].line, 1);
|
|
76
|
-
EXPECT_EQ(diags[0].col,
|
|
79
|
+
EXPECT_EQ(diags[0].col, 1);
|
|
80
|
+
EXPECT_EQ(diags[1].code, "eof-in-tag");
|
|
81
|
+
EXPECT_EQ(diags[1].line, 1);
|
|
82
|
+
EXPECT_EQ(diags[1].col, 8);
|
|
77
83
|
leptris_document_free(d);
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -84,8 +90,11 @@ TEST(HtmlParseErrors, DoctypeOutsideInitialIgnored) {
|
|
|
84
90
|
xml, strlen(xml), LEPTRIS_PARSE_COLLECT_HTML_ERRORS, &st);
|
|
85
91
|
ASSERT_NE(d, nullptr);
|
|
86
92
|
auto diags = drain(d);
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
/* The <div> opens the doctype-less initial mode; the DOCTYPE
|
|
94
|
+
* token inside the body is unexpected-doctype (13.2.6.4.73). */
|
|
95
|
+
ASSERT_GE(diags.size(), 2u);
|
|
96
|
+
EXPECT_EQ(diags[0].code, "expected-doctype-but-got-start-tag");
|
|
97
|
+
EXPECT_EQ(diags[1].code, "unexpected-doctype");
|
|
89
98
|
leptris_document_free(d);
|
|
90
99
|
}
|
|
91
100
|
|
|
@@ -96,7 +105,15 @@ TEST(HtmlParseErrors, LineColumnTrackRealLines) {
|
|
|
96
105
|
xml, strlen(xml), LEPTRIS_PARSE_COLLECT_HTML_ERRORS, &st);
|
|
97
106
|
ASSERT_NE(d, nullptr);
|
|
98
107
|
auto diags = drain(d);
|
|
99
|
-
ASSERT_GE(diags.size(),
|
|
100
|
-
EXPECT_EQ(diags[0].
|
|
108
|
+
ASSERT_GE(diags.size(), 2u);
|
|
109
|
+
EXPECT_EQ(diags[0].code, "expected-doctype-but-got-start-tag");
|
|
110
|
+
EXPECT_EQ(diags[0].line, 1);
|
|
111
|
+
bool dup_found = false;
|
|
112
|
+
for (auto& dd : diags)
|
|
113
|
+
if (dd.code == "duplicate-attribute") {
|
|
114
|
+
dup_found = true;
|
|
115
|
+
EXPECT_EQ(dd.line, 3);
|
|
116
|
+
}
|
|
117
|
+
EXPECT_TRUE(dup_found);
|
|
101
118
|
leptris_document_free(d);
|
|
102
119
|
}
|
|
@@ -22,6 +22,53 @@ LeptrisDocument ParseWith(const char* xml) {
|
|
|
22
22
|
return doc;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/* leptris-ruby#502 parity pin: a document-context relative path
|
|
26
|
+
* selects the DOCUMENT's children (XPath 1.0 §2) — libxml2/Nokogiri
|
|
27
|
+
* measured identical: doc.xpath("bibdata/status/stage") with root
|
|
28
|
+
* <metanorma> returns [] (bibdata is a child of the ROOT ELEMENT,
|
|
29
|
+
* one level down); doc.root.xpath(...) finds it. The classic
|
|
30
|
+
* "root-relative" behavior was the OLD moxml adapter's substitution,
|
|
31
|
+
* not Nokogiri semantics. */
|
|
32
|
+
TEST(XPathDocContext, RelativePathFromDocumentContext) {
|
|
33
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
34
|
+
const char* xml = "<metanorma><bibdata><status>"
|
|
35
|
+
"<stage>published</stage></status></bibdata>"
|
|
36
|
+
"</metanorma>";
|
|
37
|
+
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
38
|
+
ASSERT_NE(d, nullptr);
|
|
39
|
+
|
|
40
|
+
/* Nokogiri parity: a relative step naming a NON-child of the
|
|
41
|
+
* document returns empty */
|
|
42
|
+
LeptrisXPathResult r =
|
|
43
|
+
leptris_xpath_eval(d, NULL, "bibdata/status/stage");
|
|
44
|
+
ASSERT_NE(r, nullptr);
|
|
45
|
+
EXPECT_EQ(leptris_xpath_result_count(r), 0u);
|
|
46
|
+
leptris_xpath_result_free(r);
|
|
47
|
+
|
|
48
|
+
/* the root ELEMENT is a child of the document */
|
|
49
|
+
LeptrisXPathResult rn =
|
|
50
|
+
leptris_xpath_eval(d, NULL, "metanorma/bibdata/status/stage");
|
|
51
|
+
ASSERT_NE(rn, nullptr);
|
|
52
|
+
ASSERT_EQ(leptris_xpath_result_count(rn), 1u);
|
|
53
|
+
LeptrisElement e = leptris_xpath_result_get(rn, 0);
|
|
54
|
+
ASSERT_NE(e, nullptr);
|
|
55
|
+
EXPECT_STREQ(leptris_element_name(e), "stage");
|
|
56
|
+
EXPECT_STREQ(leptris_element_text(e), "published");
|
|
57
|
+
leptris_xpath_result_free(rn);
|
|
58
|
+
|
|
59
|
+
/* absolute shapes unchanged */
|
|
60
|
+
LeptrisXPathResult a = leptris_xpath_eval(d, NULL, "/bibdata");
|
|
61
|
+
ASSERT_NE(a, nullptr);
|
|
62
|
+
EXPECT_EQ(leptris_xpath_result_count(a), 0u);
|
|
63
|
+
leptris_xpath_result_free(a);
|
|
64
|
+
LeptrisXPathResult am =
|
|
65
|
+
leptris_xpath_eval(d, NULL, "/metanorma/bibdata/status/stage");
|
|
66
|
+
ASSERT_NE(am, nullptr);
|
|
67
|
+
EXPECT_EQ(leptris_xpath_result_count(am), 1u);
|
|
68
|
+
leptris_xpath_result_free(am);
|
|
69
|
+
leptris_document_free(d);
|
|
70
|
+
}
|
|
71
|
+
|
|
25
72
|
TEST(XPathAxes, DescendantOrSelfFindsAll) {
|
|
26
73
|
LeptrisDocument doc = ParseWith(kBasic);
|
|
27
74
|
ASSERT_NE(doc, nullptr);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
|
3
3
|
"name": "leptris",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.373",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|
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.
|
|
4
|
+
version: 1.9.373.0
|
|
5
5
|
platform: ppc64le-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -530,6 +530,7 @@ files:
|
|
|
530
530
|
- vendor-src/libleptris/docs/ARCHITECTURE_REVIEW.md
|
|
531
531
|
- vendor-src/libleptris/docs/Doxyfile
|
|
532
532
|
- vendor-src/libleptris/docs/FFI.md
|
|
533
|
+
- vendor-src/libleptris/docs/MISSION-PLAN.md
|
|
533
534
|
- vendor-src/libleptris/docs/README.md
|
|
534
535
|
- vendor-src/libleptris/docs/ROADMAP-SAXON-PARITY.md
|
|
535
536
|
- vendor-src/libleptris/docs/guide/building.md
|