leptris 1.9.377.0-aarch64-linux → 1.9.379.0-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 +28 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +69 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/SECURITY.md +94 -0
- data/vendor-src/libleptris/TODO.SOTA.md +25 -3
- data/vendor-src/libleptris/docs/MISSION-PLAN.md +12 -0
- data/vendor-src/libleptris/scripts/check_shared_exports.sh +11 -7
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +21 -0
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +247 -20
- data/vendor-src/libleptris/test/CMakeLists.txt +22 -7
- data/vendor-src/libleptris/test/html/test_html5lib.cpp +14 -2
- data/vendor-src/libleptris/test/xpath/test_xpath.cpp +105 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d5cfe3fd3564aa35377028c58353ed339938e02b525c9dd9471b18dc6780ec45
|
|
4
|
+
data.tar.gz: 2a9eed275ca88114eacab8c56e44dab4640b9e807cf4faf306ea4e00ac8e3d20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 010c6fb45f54bc1cadf338e6de1f4620b887ccce685b8dc4e7bcb64ff8efc8f4ba5c57dd8f8785db8938014c18fb62544f219bee9f724de902b5ee8fb376a8de
|
|
7
|
+
data.tar.gz: f9dc42f6752f4c64857b0edf29d735e62f78b4427af6a9001558b6dc909528fd6719598e1bed1768095a01a965d3629b49e3568b8796047cc80fd2fcc11bf251
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## [1.9.379.0] - 2026-10-21
|
|
2
|
+
|
|
3
|
+
### Fixed — engine sync (libleptris 1.9.379)
|
|
4
|
+
|
|
5
|
+
- **Source builds under GCC 14 (leptris/leptris#1806)**: the
|
|
6
|
+
template-foster diagnostic passed a cast node where the accessor
|
|
7
|
+
takes a `LeptrisNodeRef`; GCC 14 treats incompatible pointer
|
|
8
|
+
types as an error, so `rake compile` from source failed. The
|
|
9
|
+
cast is gone. Prebuilt gems are unaffected in behavior.
|
|
10
|
+
|
|
11
|
+
## [1.9.378.0] - 2026-10-21
|
|
12
|
+
|
|
13
|
+
### Fixed — engine sync (libleptris 1.9.378)
|
|
14
|
+
|
|
15
|
+
- **Node-set XPath after sibling insertion (leptris-ruby#519,
|
|
16
|
+
leptris/leptris#1802)**: the lazy element index was invalidated
|
|
17
|
+
by `add_child` but not by the sibling/prepend splice paths, so
|
|
18
|
+
`xpath("//name")` after `add_next_sibling` answered from the
|
|
19
|
+
pre-insert tree until an unrelated child insert rebuilt it.
|
|
20
|
+
`insert_before`, `insert_after` and `prepend_child` now
|
|
21
|
+
invalidate the document index.
|
|
22
|
+
|
|
23
|
+
### Changed — engine sync
|
|
24
|
+
|
|
25
|
+
- **WHATWG HTML verdict parity complete (leptris/leptris#1803)**:
|
|
26
|
+
1555/1555 corpus documents now report parse errors exactly when
|
|
27
|
+
html5lib does, with tree parity unchanged at 1555/1555.
|
|
28
|
+
|
|
1
29
|
## [1.9.356.0] - 2026-10-14
|
|
2
30
|
|
|
3
31
|
### Fixed — engine sync (libleptris 1.9.356)
|
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.379"
|
|
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/lib/leptris/version.rb
CHANGED
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.379] - 2026-10-21
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **html: build under GCC 14 and the wasm32 zero-warning gate (PR
|
|
8
|
+
#1806).** The template-foster diagnostic added in #1803 passed
|
|
9
|
+
`(LeptrisTextNode*)n` to `leptris_text_node_get_content`, whose
|
|
10
|
+
parameter is `LeptrisNodeRef`. GCC 14 (manylinux) treats
|
|
11
|
+
incompatible pointer types as a hard error and emcc flags them as
|
|
12
|
+
warnings — which broke the 1.9.378 wheel builds (leptris-py) and
|
|
13
|
+
failed the leptris-ts zero-warning gate on every lane. The
|
|
14
|
+
accessor takes the node handle directly; no behavior change.
|
|
15
|
+
Full suite 2075/2075.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## [1.9.378] - 2026-10-21
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **SECURITY.md: private reporting channel and CVE-response note
|
|
23
|
+
(PR #1804).** Reports go through GitHub Security Advisories, with
|
|
24
|
+
an acknowledgment target, coordinated disclosure, and CVE
|
|
25
|
+
assignment when warranted. The supported-version table follows
|
|
26
|
+
the lockstep rule: a fix is delivered only when the binding
|
|
27
|
+
release carries the fixed engine. The hardening inventory records
|
|
28
|
+
what actually ships: zero required runtime dependencies,
|
|
29
|
+
ASAN+leak and macOS `leaks` lanes, nightly libFuzzer on the
|
|
30
|
+
parser, explicit depth/size limits, and the conformance suites.
|
|
31
|
+
- **Test suite runs in parallel without changing any test (PR
|
|
32
|
+
#1799).** `gtest_discover_tests` now bakes the case list at build
|
|
33
|
+
time for native builds (`POST_BUILD`; cross builds keep
|
|
34
|
+
`PRE_TEST`), so `ctest --parallel` expands real cases instead of
|
|
35
|
+
`_NOT_BUILT` placeholders. CI runs the native and matrix lanes at
|
|
36
|
+
`--parallel 4` (qemu s390x and ILP32 stay serial). The CLI test
|
|
37
|
+
helper passed `WORKING_DIRECTORY` inside `PROPERTIES`, which
|
|
38
|
+
silently dropped the following `RUN_SERIAL`; passing it as a
|
|
39
|
+
keyword restores the serialization. Same 2074 cases, all green:
|
|
40
|
+
66.0 s serial → 31.0 s `-j4` locally; the export check alone
|
|
41
|
+
20.7-23.3 s → 8.7-9.3 s.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **dom: node-set XPath no longer answers from a stale element
|
|
46
|
+
index after sibling insertion (leptris-ruby#519, PR #1802).** The
|
|
47
|
+
lazy TODO 132 index was invalidated by `append_child` but not by
|
|
48
|
+
`insert_before`, `insert_after` or `prepend_child`, so a
|
|
49
|
+
`//name` query after `add_next_sibling` missed the new element
|
|
50
|
+
until an unrelated child insert rebuilt the index. All three
|
|
51
|
+
splice paths now invalidate the document index; the regression
|
|
52
|
+
spec covers each path plus the compiled-handle FFI face the
|
|
53
|
+
bindings evaluate with.
|
|
54
|
+
- **html: WHATWG verdict parity is complete — 1555/1555, zero
|
|
55
|
+
misses (PR #1803).** The remaining 20 corpus documents now report
|
|
56
|
+
at their html5lib source sites: `adoption-agency-1.3` for
|
|
57
|
+
formatting end tags whose element is in scope but not the current
|
|
58
|
+
node; the `</menuitem>` endTagOther pair (stray, and found with
|
|
59
|
+
non-implied open elements above); tokenizer
|
|
60
|
+
`expected-dashes-or-doctype`,
|
|
61
|
+
`unexpected-character-after-solidus-in-tag` and
|
|
62
|
+
`attributes-in-end-tag`; AfterAfterFrameset trailing characters,
|
|
63
|
+
BeforeHead `end-tag-after-implied-root`, BeforeHtml
|
|
64
|
+
`unexpected-end-tag-before-html`, and foreign-content end-tag
|
|
65
|
+
mismatch; `unexpected-form-in-table` for both the insertion and
|
|
66
|
+
the ignored duplicate plus the `</form>` table-voodoo pair;
|
|
67
|
+
template `foster-parenting-character`; and
|
|
68
|
+
`two-heads-are-not-better-than-one`. Tree parity stays
|
|
69
|
+
1555/1555; the verdict floor is pinned at the full corpus count.
|
|
70
|
+
|
|
71
|
+
|
|
3
72
|
## [1.9.377] - 2026-10-20
|
|
4
73
|
|
|
5
74
|
### Added
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
libleptris is a parser and transformation engine: it reads untrusted
|
|
4
|
+
documents. Memory safety and predictable resource behavior on hostile
|
|
5
|
+
input are part of the product, not an afterthought.
|
|
6
|
+
|
|
7
|
+
## Supported versions
|
|
8
|
+
|
|
9
|
+
Security fixes land on the latest release of the current major line and
|
|
10
|
+
are shipped in the next patch release. The engine follows the lockstep
|
|
11
|
+
runtime rule — binding versions are `{engine-semver}.{binding-patch}` —
|
|
12
|
+
so a fix is considered delivered only when the corresponding binding
|
|
13
|
+
release carries the fixed engine.
|
|
14
|
+
|
|
15
|
+
| Version | Supported |
|
|
16
|
+
|---|---|
|
|
17
|
+
| latest `1.x` release | yes |
|
|
18
|
+
| any earlier release/tag | no (upgrade to the latest patch) |
|
|
19
|
+
|
|
20
|
+
## Reporting a vulnerability
|
|
21
|
+
|
|
22
|
+
**Do not open a public issue for a security report.**
|
|
23
|
+
|
|
24
|
+
Use GitHub's private vulnerability reporting on this repository:
|
|
25
|
+
**Security → Advisories → Report a vulnerability** (direct link:
|
|
26
|
+
<https://github.com/leptris/leptris/security/advisories/new>).
|
|
27
|
+
|
|
28
|
+
Please include, when available:
|
|
29
|
+
|
|
30
|
+
- the engine version (or binding version and its pinned engine),
|
|
31
|
+
- the input document/expression that triggers the problem,
|
|
32
|
+
- the build configuration (sanitizer output, platform, compiler),
|
|
33
|
+
- the observed impact (crash, out-of-bounds access, infinite loop,
|
|
34
|
+
excessive memory, code execution, information disclosure).
|
|
35
|
+
|
|
36
|
+
If you report a bug that affects a binding, include both the binding
|
|
37
|
+
version and the engine version it pins; the engine is the shared
|
|
38
|
+
implementation and the fix belongs there first.
|
|
39
|
+
|
|
40
|
+
## What to expect
|
|
41
|
+
|
|
42
|
+
- **Acknowledgment** within 5 business days.
|
|
43
|
+
- **Triage and reproduction** in the private advisory thread. We may
|
|
44
|
+
ask for the exact input and a minimized reproducer.
|
|
45
|
+
- **Fix and coordinated disclosure.** The fix lands on a private or
|
|
46
|
+
public branch as circumstances require, is released through the
|
|
47
|
+
normal release workflow, and the advisory is published with credit
|
|
48
|
+
(unless you prefer to remain anonymous).
|
|
49
|
+
- **CVE assignment** is requested through the GitHub advisory when the
|
|
50
|
+
issue warrants one.
|
|
51
|
+
|
|
52
|
+
Please give us a reasonable window to ship a patch before public
|
|
53
|
+
disclosure. We will keep you informed of the timeline in the advisory
|
|
54
|
+
thread.
|
|
55
|
+
|
|
56
|
+
## In scope
|
|
57
|
+
|
|
58
|
+
- The C engine: XML/HTML parsing, DTD validation, XPath, XSLT,
|
|
59
|
+
XInclude, serialization, encoding conversion, SAX and pull APIs.
|
|
60
|
+
- The shipped CLI (`leptris`) and public C API surface.
|
|
61
|
+
- The FFI layer and descriptor surface consumed by the language
|
|
62
|
+
bindings (leptris-ruby, leptris-py, leptris-ts, leptris-rs).
|
|
63
|
+
- Resource-exhaustion behavior on untrusted input (unbounded
|
|
64
|
+
recursion, allocation amplification, algorithmic complexity) when it
|
|
65
|
+
is reachable without a misconfiguration.
|
|
66
|
+
|
|
67
|
+
## Out of scope
|
|
68
|
+
|
|
69
|
+
- Vulnerabilities in third-party dependencies of the language
|
|
70
|
+
bindings, unless the engine's handling of their output is unsafe.
|
|
71
|
+
- Builds with sanitizers or assertions disabled by the reporter.
|
|
72
|
+
- Reports that require a modified engine binary.
|
|
73
|
+
- Denial of service by simply feeding a very large document when the
|
|
74
|
+
configured limits are explicitly disabled by the caller.
|
|
75
|
+
|
|
76
|
+
## Hardening in the repository
|
|
77
|
+
|
|
78
|
+
- **Zero required runtime dependencies** by default; `utf8proc` and
|
|
79
|
+
`iconv` are optional compile-time features.
|
|
80
|
+
- **Sanitizers in CI**: AddressSanitizer with leak detection on Linux
|
|
81
|
+
(`asan.yml`), plus the macOS `leaks` lane.
|
|
82
|
+
- **Fuzzing**: nightly libFuzzer runs over the parser
|
|
83
|
+
(`fuzz-nightly.yml`), with the crash corpus triaged into regression
|
|
84
|
+
specs; the XPath and mutation harnesses live alongside it in
|
|
85
|
+
`test/fuzz/`.
|
|
86
|
+
- **Depth and size limits** enforced by the parser; all limits are
|
|
87
|
+
explicit and documented in the public headers.
|
|
88
|
+
- **Conformance suites** (W3C XPath, libxslt, WHATWG html5lib, XSD,
|
|
89
|
+
DTD) run on every change, so hardening fixes cannot regress silently.
|
|
90
|
+
|
|
91
|
+
## Recognition
|
|
92
|
+
|
|
93
|
+
We are happy to credit reporters in the published advisory and in the
|
|
94
|
+
release notes. Thank you for helping keep document parsing safe.
|
|
@@ -28,9 +28,23 @@ docs/MISSION-PLAN.md and is updated at every release.
|
|
|
28
28
|
## Gate A — Conformance: zero reds everywhere
|
|
29
29
|
|
|
30
30
|
### A1 WHATWG verdict parity: 1394/1555 → 1555/1555
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
**Measured 2026-10-21 (main after #1800): 1535/1555 verdict,
|
|
32
|
+
1555/1555 tree, zero false positives; floor pinned at 1535.** The
|
|
33
|
+
remaining 20 verdict misses: adoption-agency (tests2:10, tests19:98,
|
|
34
|
+
tests19:99, tests26:1, tests26:9), menuitem strays (menuitem:10/12),
|
|
35
|
+
table/caption singletons (tests9:16, tests10:15, tests16:197,
|
|
36
|
+
pending-spec-changes:2, tests20:48), tokenizer singletons (tests10:2,
|
|
37
|
+
tests2:18, tests2:58, tests19:41, tests19:82) and two-heads
|
|
38
|
+
(tests3:13). The remaining ~270 code-set divergences are prose-dialect
|
|
39
|
+
labels from older corpus datasets; the verdict gate is presence-only.
|
|
40
|
+
The slices already shipped: initial mode, raw-text EOF, unterminated
|
|
41
|
+
close, frameset, fenced walk, InBody EOF allowed-set, in-select,
|
|
42
|
+
foster-by-kind, unknown-doctype (#1778-#1791), raw-text strays,
|
|
43
|
+
non-html-root, end-tag-too-early, after-body/frameset phases,
|
|
44
|
+
start-tag phases, select/table/foreign, need-space-after-doctype
|
|
45
|
+
(#1794), misplaced/stray-p, after-html codes, image (#1797, #1800).
|
|
46
|
+
Ground every further slice in html5lib's actual source
|
|
47
|
+
(~/src/external/html5lib):
|
|
34
48
|
- [ ] escaped-state EOF codes (`unexpected-eof-in-text-mode`,
|
|
35
49
|
`eof-in-script-in-script`) — the script-data state machine's
|
|
36
50
|
EOF classification
|
|
@@ -76,6 +90,14 @@ actual source (~/src/external/html5lib):
|
|
|
76
90
|
## Gate B — Performance: every published row ≥1.00x
|
|
77
91
|
|
|
78
92
|
### B1 build row: ~2.9x → ≤1.0x (the #242 campaign)
|
|
93
|
+
**Measured 2026-10-21: 1.86-1.92x** (moxml `sota/04-performance`
|
|
94
|
+
branch + leptris 1.9.376.0; harness `wt/460-harness/build_bench.rb`,
|
|
95
|
+
31 interleaved reps: 4.08ms vs Nokogiri 2.20ms for 250 records).
|
|
96
|
+
Was 2.95x at the 1.9.363 era; the add_child alias and same-doc fast
|
|
97
|
+
path landed since. StackProf attribution: `NativeNode.create_element`
|
|
98
|
+
14.6%, GC mark+sweep 19%, `prime_contract` 7.7%, attach path 45% of
|
|
99
|
+
tree; the `invalidate_namespace_cache!` line attribution is a
|
|
100
|
+
sampling artifact (deleting both attach invalidations gains ~5%).
|
|
79
101
|
Harness: wt/460-harness/build_bench.rb. Decomposition on record.
|
|
80
102
|
- [ ] per-element object budget: one Ruby object per created node
|
|
81
103
|
(drop the shell entirely post-create-priming; the mint cache
|
|
@@ -161,3 +161,15 @@ One table, updated at every release train: gate, command, last run,
|
|
|
161
161
|
verdict. When every row of Gate A and Gate B reads "green (dated)"
|
|
162
162
|
and Gate D1 is green, the mission is achieved — and v1.0.0 is the
|
|
163
163
|
tag that says so.
|
|
164
|
+
|
|
165
|
+
| Gate | Command | Last run | Verdict |
|
|
166
|
+
|------|---------|----------|---------|
|
|
167
|
+
| A1 WHATWG tree parity | `test_html5lib --gtest_filter=Html5LibCorpus.*` | 2026-10-21 | green, 1555/1555 |
|
|
168
|
+
| A1 WHATWG verdict parity | `Html5LibCorpus.ErrorVerdictParity` | 2026-10-21 | 1535/1555 (floor 1535; **red**) |
|
|
169
|
+
| A7 libxslt | `LibxsltSuite` | 2026-10-20 | 205/205 (204 + 1 macOS skip) |
|
|
170
|
+
| B1 build row | `wt/460-harness/build_bench.rb` | 2026-10-21 | 1.86-1.92x (**red**, sota/04 branch) |
|
|
171
|
+
| B2 walk row | `wt/460-harness/walk_read_bench.rb` | 2026-10-20 | 0.88x (green) |
|
|
172
|
+
| Test-suite runtime (fidelity) | `ctest`, 2074 cases | 2026-10-21 | 66.0 s serial → 31.0 s `-j4` (same 2074/2074; PR #1799) |
|
|
173
|
+
| Rides 1.9.377 | registries | 2026-10-21 | rubygems/PyPI 1.9.377.0, npm 0.1.21 |
|
|
174
|
+
| Issues | all repos | 2026-10-21 | 0 open (ruby#514/#515 closed with gem verification) |
|
|
175
|
+
|
|
@@ -26,7 +26,7 @@ cmake -B "$BUILD" -S "$ROOT" \
|
|
|
26
26
|
-DLEPTRIS_BUILD_BENCHMARKS=OFF \
|
|
27
27
|
> /dev/null
|
|
28
28
|
|
|
29
|
-
cmake --build "$BUILD" --target leptris_shared > /dev/null
|
|
29
|
+
cmake --build "$BUILD" --target leptris_shared --parallel > /dev/null
|
|
30
30
|
|
|
31
31
|
LIB=$(find "$BUILD" -name 'libleptris.*.dylib' -o -name 'libleptris.so*' \
|
|
32
32
|
2>/dev/null | grep -v '[0-9]\.dylib$' | head -1)
|
|
@@ -98,13 +98,17 @@ REQUIRED=(
|
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
rc=0
|
|
101
|
+
# One nm pass instead of one per symbol: the export table is a few KB
|
|
102
|
+
# but nm re-reads the whole dylib each time (~55 spawns + reads).
|
|
103
|
+
# macOS: nm -gU; Linux: nm -D --defined-only. The macOS spelling
|
|
104
|
+
# prefixes symbols with '_', hence the optional underscore.
|
|
105
|
+
EXPORTS=$(mktemp)
|
|
106
|
+
trap 'rm -f "$EXPORTS"' EXIT
|
|
107
|
+
if ! nm -gU "$LIB" > "$EXPORTS" 2>/dev/null; then
|
|
108
|
+
nm -D --defined-only "$LIB" > "$EXPORTS" 2>/dev/null || true
|
|
109
|
+
fi
|
|
101
110
|
for sym in "${REQUIRED[@]}"; do
|
|
102
|
-
|
|
103
|
-
# macOS: nm -gU <lib>
|
|
104
|
-
# Linux: nm -D --defined-only <lib>
|
|
105
|
-
if nm -gU "$LIB" 2>/dev/null | grep -qE " _?${sym}\$"; then
|
|
106
|
-
echo " OK $sym"
|
|
107
|
-
elif nm -D --defined-only "$LIB" 2>/dev/null | grep -qE " ${sym}\$"; then
|
|
111
|
+
if grep -qE "[[:space:]]_?${sym}\$" "$EXPORTS"; then
|
|
108
112
|
echo " OK $sym"
|
|
109
113
|
else
|
|
110
114
|
echo " MISS $sym"
|
|
@@ -543,6 +543,13 @@ LeptrisStatus leptris_element_prepend_child(LeptrisElement parent, LeptrisElemen
|
|
|
543
543
|
/* Call internal void function, assume success */
|
|
544
544
|
leptris_element_prepend_child_internal(parent, (LeptrisNode*)child);
|
|
545
545
|
leptris_element_invalidate_child_cache(parent);
|
|
546
|
+
|
|
547
|
+
/* TODO 132: the child splice changed the element set — a cached
|
|
548
|
+
* index would describe the pre-insert tree (leptris-ruby #519). */
|
|
549
|
+
if (leptris_element_get_document(parent)) {
|
|
550
|
+
leptris_element_index_invalidate(
|
|
551
|
+
leptris_element_get_document(parent));
|
|
552
|
+
}
|
|
546
553
|
return LEPTRIS_OK;
|
|
547
554
|
}
|
|
548
555
|
|
|
@@ -671,6 +678,13 @@ LeptrisStatus leptris_element_insert_before(LeptrisElement sibling, LeptrisEleme
|
|
|
671
678
|
|
|
672
679
|
leptris_node_increment_version(LEPTRIS_ELEMENT_AS_NODE(parent));
|
|
673
680
|
leptris_element_invalidate_child_cache(parent);
|
|
681
|
+
|
|
682
|
+
/* TODO 132: the sibling splice changed the element set — a cached
|
|
683
|
+
* index would describe the pre-insert tree (leptris-ruby #519). */
|
|
684
|
+
if (leptris_element_get_document(parent)) {
|
|
685
|
+
leptris_element_index_invalidate(
|
|
686
|
+
leptris_element_get_document(parent));
|
|
687
|
+
}
|
|
674
688
|
return LEPTRIS_OK;
|
|
675
689
|
}
|
|
676
690
|
|
|
@@ -785,6 +799,13 @@ LeptrisStatus leptris_element_insert_after(LeptrisElement sibling, LeptrisElemen
|
|
|
785
799
|
|
|
786
800
|
leptris_node_increment_version(LEPTRIS_ELEMENT_AS_NODE(parent));
|
|
787
801
|
leptris_element_invalidate_child_cache(parent);
|
|
802
|
+
|
|
803
|
+
/* TODO 132: the sibling splice changed the element set — a cached
|
|
804
|
+
* index would describe the pre-insert tree (leptris-ruby #519). */
|
|
805
|
+
if (leptris_element_get_document(parent)) {
|
|
806
|
+
leptris_element_index_invalidate(
|
|
807
|
+
leptris_element_get_document(parent));
|
|
808
|
+
}
|
|
788
809
|
return LEPTRIS_OK;
|
|
789
810
|
}
|
|
790
811
|
|
|
@@ -4035,10 +4035,13 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4035
4035
|
if (*q != ' ' && *q != '\t' && *q != '\n' &&
|
|
4036
4036
|
*q != '\r') { ws = 0; break; }
|
|
4037
4037
|
if (!ws) {
|
|
4038
|
-
/* AfterFrameset processCharacters
|
|
4039
|
-
*
|
|
4040
|
-
|
|
4041
|
-
|
|
4038
|
+
/* AfterFrameset/AfterAfterFrameset processCharacters
|
|
4039
|
+
* both report and DROP the run (no phase switch). */
|
|
4040
|
+
if (b->after_html)
|
|
4041
|
+
h_diag(b, "expected-eof-but-got-char",
|
|
4042
|
+
b->err_line_memo_off,
|
|
4043
|
+
"characters after </html> in the frameset phase");
|
|
4044
|
+
else
|
|
4042
4045
|
h_diag(b, "unexpected-char-after-frameset",
|
|
4043
4046
|
b->err_line_memo_off,
|
|
4044
4047
|
"characters after </frameset>");
|
|
@@ -4068,12 +4071,15 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4068
4071
|
b->after_body_done = 1;
|
|
4069
4072
|
/* AfterAfterBody/AfterAfterFrameset processCharacters
|
|
4070
4073
|
* report expected-eof-but-got-char; AfterBody reports
|
|
4071
|
-
* unexpected-char-after-body (tests2:55, tests19:41).
|
|
4072
|
-
|
|
4074
|
+
* unexpected-char-after-body (tests2:55, tests19:41).
|
|
4075
|
+
* AfterAfterBody then switches to in-body and inserts
|
|
4076
|
+
* the run — clear the phase flags so later comments are
|
|
4077
|
+
* body content, not document epilog. */
|
|
4078
|
+
if (b->after_html) {
|
|
4073
4079
|
h_diag(b, "expected-eof-but-got-char",
|
|
4074
4080
|
b->err_line_memo_off,
|
|
4075
4081
|
"characters after </html>");
|
|
4076
|
-
else
|
|
4082
|
+
} else
|
|
4077
4083
|
h_diag(b, "unexpected-char-after-body",
|
|
4078
4084
|
b->err_line_memo_off,
|
|
4079
4085
|
"characters after </body>");
|
|
@@ -4214,6 +4220,30 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4214
4220
|
b->err_line_memo_off,
|
|
4215
4221
|
"hidden input inserted in table context");
|
|
4216
4222
|
}
|
|
4223
|
+
/* Template content whose last node is a table-context
|
|
4224
|
+
* element inserts text as that node's sibling (the template
|
|
4225
|
+
* flavor of foster parenting). html5lib still reports the
|
|
4226
|
+
* foster (template.dat:112: <template><tr>…</tr>a). */
|
|
4227
|
+
if (b->whatwg && b->whatwg_foster &&
|
|
4228
|
+
leptris_node_get_type(n) == LEPTRIS_NODE_TYPE_TEXT &&
|
|
4229
|
+
b->depth > 0 &&
|
|
4230
|
+
b->open_id[b->depth - 1] == h_id_template) {
|
|
4231
|
+
const char* tc = leptris_text_node_get_content(n);
|
|
4232
|
+
int nonws = 0;
|
|
4233
|
+
if (tc)
|
|
4234
|
+
for (const char* w = tc; *w; w++)
|
|
4235
|
+
if (*w != ' ' && *w != '\t' && *w != '\n' &&
|
|
4236
|
+
*w != '\r') { nonws = 1; break; }
|
|
4237
|
+
LeptrisNodeRef last = leptris_node_last_child_internal(
|
|
4238
|
+
(LeptrisNode*)b->open[b->depth - 1]);
|
|
4239
|
+
if (nonws && last &&
|
|
4240
|
+
leptris_node_get_type(last) ==
|
|
4241
|
+
LEPTRIS_NODE_TYPE_ELEMENT &&
|
|
4242
|
+
h_is_table_context((LeptrisElement)last))
|
|
4243
|
+
h_diag(b, "foster-parenting-character",
|
|
4244
|
+
b->err_line_memo_off,
|
|
4245
|
+
"text fostered in template table content");
|
|
4246
|
+
}
|
|
4217
4247
|
/* #659 foster (WHATWG only): text/elements in table context
|
|
4218
4248
|
* go before the nearest open table in ITS parent. */
|
|
4219
4249
|
if (b->whatwg_foster && tbl_ctx && h_fosterable(b, n)) {
|
|
@@ -4652,7 +4682,7 @@ static void h_reconstruct(HBuilder* b) {
|
|
|
4652
4682
|
/* The adoption agency proper. Returns 1 when the end tag is fully
|
|
4653
4683
|
* consumed; 0 means no formatting entry matched and the caller
|
|
4654
4684
|
* runs its generic (any-other-end-tag) handling. */
|
|
4655
|
-
static int h_afe_end(HBuilder* b, const char* subject) {
|
|
4685
|
+
static int h_afe_end(HBuilder* b, const char* subject, size_t off) {
|
|
4656
4686
|
if (b->depth > 0) {
|
|
4657
4687
|
LeptrisElement cur = b->open[b->depth - 1];
|
|
4658
4688
|
const char* cn = leptris_element_name(cur);
|
|
@@ -4703,6 +4733,14 @@ static int h_afe_end(HBuilder* b, const char* subject) {
|
|
|
4703
4733
|
return 1;
|
|
4704
4734
|
}
|
|
4705
4735
|
}
|
|
4736
|
+
/* html5lib adoption-agency-1.3: the formatting element is
|
|
4737
|
+
* in the stack and in scope, but open elements sit above
|
|
4738
|
+
* it (it is not the current node). Parsing proceeds. */
|
|
4739
|
+
if (b->whatwg_adopt && b->depth != (size_t)si + 1) {
|
|
4740
|
+
h_diag(b, "adoption-agency-1.3", off,
|
|
4741
|
+
"</%s> formatting element is not the current node",
|
|
4742
|
+
subject);
|
|
4743
|
+
}
|
|
4706
4744
|
int fbi = -1;
|
|
4707
4745
|
for (size_t k = (size_t)si + 1; k < b->depth; k++) {
|
|
4708
4746
|
/* Only HTML-namespace elements are furthest-block
|
|
@@ -6107,6 +6145,15 @@ static LeptrisDocument html_parse_shared(
|
|
|
6107
6145
|
}
|
|
6108
6146
|
p = (ce + 3 <= end) ? ce + 3 : end;
|
|
6109
6147
|
} else {
|
|
6148
|
+
/* Tokenizer markup-declaration-open: "<!" that
|
|
6149
|
+
* is neither a comment nor DOCTYPE (and, in
|
|
6150
|
+
* HTML content, not CDATA) reports
|
|
6151
|
+
* expected-dashes-or-doctype (tests10:2:
|
|
6152
|
+
* <![CDATA[a]]> outside foreign content). */
|
|
6153
|
+
if (b.whatwg)
|
|
6154
|
+
h_diag(&b, "expected-dashes-or-doctype",
|
|
6155
|
+
(size_t)(p - b.input_base),
|
|
6156
|
+
"<! markup declaration");
|
|
6110
6157
|
/* CDATA-ish bogus: skip to '>'. WHATWG makes
|
|
6111
6158
|
* it a bogus comment (tests1:42-49) — except
|
|
6112
6159
|
* doctype-shaped constructs, which the
|
|
@@ -6217,6 +6264,20 @@ static LeptrisDocument html_parse_shared(
|
|
|
6217
6264
|
const char* q = ns;
|
|
6218
6265
|
while (q < end && !h_is_ws(*q) && *q != '>' && *q != '/') q++;
|
|
6219
6266
|
size_t nlen = (size_t)(q - ns);
|
|
6267
|
+
/* Tokenizer: an end tag may carry attributes
|
|
6268
|
+
* syntactically (they are ignored at tree time, but
|
|
6269
|
+
* the token reports attributes-in-end-tag;
|
|
6270
|
+
* tests2:18's </b test<b ...>). A lone "/" before
|
|
6271
|
+
* ">" is the self-closing form, not an attribute. */
|
|
6272
|
+
{
|
|
6273
|
+
const char* tq = q;
|
|
6274
|
+
while (tq < end && h_is_ws(*tq)) tq++;
|
|
6275
|
+
if (tq < end && *tq != '>' &&
|
|
6276
|
+
!(*tq == '/' && tq + 1 < end && tq[1] == '>'))
|
|
6277
|
+
h_diag(&b, "attributes-in-end-tag",
|
|
6278
|
+
(size_t)(tq - b.input_base),
|
|
6279
|
+
"attributes in an end tag");
|
|
6280
|
+
}
|
|
6220
6281
|
while (q < end && *q != '>') q++;
|
|
6221
6282
|
if (q < end) q++;
|
|
6222
6283
|
/* Flush pending text before closing. A
|
|
@@ -6254,6 +6315,17 @@ static LeptrisDocument html_parse_shared(
|
|
|
6254
6315
|
for (size_t i = 0; i < cl0; i++)
|
|
6255
6316
|
lname0[i] = h_lower(ns[i]);
|
|
6256
6317
|
lname0[cl0] = 0;
|
|
6318
|
+
/* html5lib InForeignContent.processEndTag: an end
|
|
6319
|
+
* tag that does not name the current foreign node
|
|
6320
|
+
* reports before the walk (tests19:82: <math></p>). */
|
|
6321
|
+
{
|
|
6322
|
+
const char* topn = leptris_element_name(
|
|
6323
|
+
b.open[b.depth - 1]);
|
|
6324
|
+
if (topn && !h_ieq_raw(topn, lname0))
|
|
6325
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6326
|
+
(size_t)(p - b.input_base),
|
|
6327
|
+
"</%s> in foreign content", lname0);
|
|
6328
|
+
}
|
|
6257
6329
|
int match_foreign = 0, crossed_ip = 0;
|
|
6258
6330
|
for (size_t d = b.depth; d > 0; d--) {
|
|
6259
6331
|
if (b.open_ns[d - 1] == H_NS_HTML) break;
|
|
@@ -6304,6 +6376,15 @@ static LeptrisDocument html_parse_shared(
|
|
|
6304
6376
|
text = p;
|
|
6305
6377
|
continue;
|
|
6306
6378
|
}
|
|
6379
|
+
/* AfterAfterBody/AfterAfterFrameset processEndTag: ANY
|
|
6380
|
+
* end tag is a phase error, and the token is then
|
|
6381
|
+
* reprocessed in body — report once and fall through
|
|
6382
|
+
* with the phase flags cleared (tests19:43). */
|
|
6383
|
+
if (b.whatwg && b.after_html) {
|
|
6384
|
+
h_diag(&b, "expected-eof-but-got-end-tag",
|
|
6385
|
+
(size_t)(p - b.input_base),
|
|
6386
|
+
"end tag after </html>");
|
|
6387
|
+
}
|
|
6307
6388
|
/* Stray end tags with no open stack. The raw-text family
|
|
6308
6389
|
* completes during start-tag handling; void elements and
|
|
6309
6390
|
* menuitem have no in-body end handler. html5lib's
|
|
@@ -6319,7 +6400,16 @@ static LeptrisDocument html_parse_shared(
|
|
|
6319
6400
|
int stray = h_is_rawtext_name(rn) ||
|
|
6320
6401
|
(h_is_void(rn) && strcmp(rn, "br") != 0) ||
|
|
6321
6402
|
strcmp(rn, "menuitem") == 0;
|
|
6322
|
-
|
|
6403
|
+
/* BeforeHtml endTagOther: every end tag other than
|
|
6404
|
+
* head/body/html/br reports here (html5lib
|
|
6405
|
+
* BeforeHtmlPhase; tests2:18's </b ...>). */
|
|
6406
|
+
int before_html = !b.top_tail && !b.head_tag_seen &&
|
|
6407
|
+
!b.body_seen && !b.body_tag_seen;
|
|
6408
|
+
int bb_exempt = strcmp(rn, "head") == 0 ||
|
|
6409
|
+
strcmp(rn, "body") == 0 ||
|
|
6410
|
+
strcmp(rn, "html") == 0 ||
|
|
6411
|
+
strcmp(rn, "br") == 0;
|
|
6412
|
+
if (stray || (before_html && !bb_exempt)) {
|
|
6323
6413
|
const char* code = "unexpected-end-tag";
|
|
6324
6414
|
if (b.after_html)
|
|
6325
6415
|
code = "expected-eof-but-got-end-tag";
|
|
@@ -6329,9 +6419,8 @@ static LeptrisDocument html_parse_shared(
|
|
|
6329
6419
|
code = "unexpected-end-tag-after-frameset";
|
|
6330
6420
|
else if (b.frameset)
|
|
6331
6421
|
code = "unexpected-end-tag-in-frameset";
|
|
6332
|
-
else if (
|
|
6333
|
-
|
|
6334
|
-
code = "end-tag-after-implied-root";
|
|
6422
|
+
else if (before_html)
|
|
6423
|
+
code = "unexpected-end-tag-before-html";
|
|
6335
6424
|
h_diag(&b, code, (size_t)(p - b.input_base),
|
|
6336
6425
|
"</%s> has no matching open element", rn);
|
|
6337
6426
|
p = q;
|
|
@@ -6339,6 +6428,39 @@ static LeptrisDocument html_parse_shared(
|
|
|
6339
6428
|
continue;
|
|
6340
6429
|
}
|
|
6341
6430
|
}
|
|
6431
|
+
/* InHead/AfterHead endTagOther: a </p> before body
|
|
6432
|
+
* content is dropped with unexpected-end-tag and
|
|
6433
|
+
* inserts no empty p (tests19:3). Depth-0 only — the
|
|
6434
|
+
* in-body stray-p rule below owns the body phase. */
|
|
6435
|
+
if (b.whatwg && nlen == 1 && h_lower(ns[0]) == 'p' &&
|
|
6436
|
+
b.depth == 0 && !b.body_seen &&
|
|
6437
|
+
(b.head_tag_seen || b.head_end_seen) &&
|
|
6438
|
+
!b.frameset && !b.after_body && !b.after_html)
|
|
6439
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6440
|
+
(size_t)(p - b.input_base),
|
|
6441
|
+
"</p> outside the body phase");
|
|
6442
|
+
/* BeforeHead endTagOther: before the head phase starts
|
|
6443
|
+
* (only the html element open), any end tag other than
|
|
6444
|
+
* head/body/html/br is end-tag-after-implied-root
|
|
6445
|
+
* (tests19:2: <html></p><!--foo-->). */
|
|
6446
|
+
if (b.whatwg && nlen && b.depth == 1 &&
|
|
6447
|
+
b.open_id[0] == h_id_html &&
|
|
6448
|
+
!b.head_tag_seen && !b.head_end_seen &&
|
|
6449
|
+
!b.body_seen && !b.body_tag_seen &&
|
|
6450
|
+
!b.frameset && !b.after_body && !b.after_html) {
|
|
6451
|
+
char* ph = h_pooled_lower(b.pool, ns, nlen);
|
|
6452
|
+
if (ph && strcmp(ph, "head") != 0 &&
|
|
6453
|
+
strcmp(ph, "body") != 0 &&
|
|
6454
|
+
strcmp(ph, "html") != 0 &&
|
|
6455
|
+
strcmp(ph, "br") != 0) {
|
|
6456
|
+
h_diag(&b, "end-tag-after-implied-root",
|
|
6457
|
+
(size_t)(p - b.input_base),
|
|
6458
|
+
"</%s> before the head phase", ph);
|
|
6459
|
+
p = q;
|
|
6460
|
+
text = p;
|
|
6461
|
+
continue;
|
|
6462
|
+
}
|
|
6463
|
+
}
|
|
6342
6464
|
if (nlen && (b.depth > 0 ||
|
|
6343
6465
|
(b.whatwg &&
|
|
6344
6466
|
((nlen == 4 &&
|
|
@@ -6376,7 +6498,7 @@ static LeptrisDocument html_parse_shared(
|
|
|
6376
6498
|
* at body); a SPECIAL element crossed
|
|
6377
6499
|
* ignores the tag (menuitem:8: x stays in
|
|
6378
6500
|
* the p). */
|
|
6379
|
-
int msp = 0;
|
|
6501
|
+
int msp = 0, implied_ok = 1;
|
|
6380
6502
|
for (size_t d2 = b.depth; d2 > 0; d2--) {
|
|
6381
6503
|
const char* on2 =
|
|
6382
6504
|
leptris_element_name(b.open[d2 - 1]);
|
|
@@ -6387,13 +6509,39 @@ static LeptrisDocument html_parse_shared(
|
|
|
6387
6509
|
break;
|
|
6388
6510
|
}
|
|
6389
6511
|
if (h_is_special_ww(on2)) break;
|
|
6512
|
+
if (!h_is_implied_end_name(on2))
|
|
6513
|
+
implied_ok = 0;
|
|
6390
6514
|
}
|
|
6391
|
-
|
|
6515
|
+
/* html5lib endTagOther reports in both
|
|
6516
|
+
* shapes: the walk found the element but
|
|
6517
|
+
* non-implied nodes sit above it, or the
|
|
6518
|
+
* walk stopped at a special (stray). */
|
|
6519
|
+
if (!msp)
|
|
6520
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6521
|
+
(size_t)(p - b.input_base),
|
|
6522
|
+
"</menuitem> is a stray end tag");
|
|
6523
|
+
else if (!implied_ok)
|
|
6524
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6525
|
+
(size_t)(p - b.input_base),
|
|
6526
|
+
"</menuitem> with open elements above");
|
|
6392
6527
|
p = q;
|
|
6393
6528
|
text = p;
|
|
6394
6529
|
continue;
|
|
6395
6530
|
}
|
|
6396
6531
|
if (b.whatwg && strcmp(lname, "form") == 0) {
|
|
6532
|
+
/* InTable endTagOther runs the body rule
|
|
6533
|
+
* under table voodoo: both codes report
|
|
6534
|
+
* (tests16:197: <form><table></form>). */
|
|
6535
|
+
if (b.depth > 0 &&
|
|
6536
|
+
h_id_table_ctx(b.open_id[b.depth - 1])) {
|
|
6537
|
+
h_diag(&b,
|
|
6538
|
+
"unexpected-end-tag-implies-table-voodoo",
|
|
6539
|
+
(size_t)(p - b.input_base),
|
|
6540
|
+
"</form> inside a table");
|
|
6541
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6542
|
+
(size_t)(p - b.input_base),
|
|
6543
|
+
"</form> inside a table");
|
|
6544
|
+
}
|
|
6397
6545
|
/* Vendored rule: </form> closes implied
|
|
6398
6546
|
* end-tag layers (option/optgroup/li/dd/
|
|
6399
6547
|
* dt/p), then pops the form ONLY when it
|
|
@@ -6564,7 +6712,8 @@ static LeptrisDocument html_parse_shared(
|
|
|
6564
6712
|
* matched; otherwise the generic path below
|
|
6565
6713
|
* is the any-other-end-tag run. */
|
|
6566
6714
|
if (b.whatwg_adopt && h_is_formatting(lname) &&
|
|
6567
|
-
h_afe_end(&b, lname
|
|
6715
|
+
h_afe_end(&b, lname,
|
|
6716
|
+
(size_t)(p - b.input_base))) {
|
|
6568
6717
|
p = q;
|
|
6569
6718
|
text = p;
|
|
6570
6719
|
continue;
|
|
@@ -7255,9 +7404,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
7255
7404
|
}
|
|
7256
7405
|
if (!name) goto done;
|
|
7257
7406
|
/* #659 (13.2.6.4.7): <image> is renamed <img> and
|
|
7258
|
-
* reprocessed (tests1:90)
|
|
7259
|
-
|
|
7407
|
+
* reprocessed (tests1:90); html5lib reports the legacy
|
|
7408
|
+
* spelling as image-start-tag (tests19:89). */
|
|
7409
|
+
if (b.whatwg && strcmp(name, "image") == 0) {
|
|
7410
|
+
h_diag(&b, "image-start-tag",
|
|
7411
|
+
(size_t)(p - b.input_base),
|
|
7412
|
+
"<image> acts as <img>");
|
|
7260
7413
|
name = h_pooled_lower(b.pool, "img", 3);
|
|
7414
|
+
}
|
|
7261
7415
|
/* #1218: ONE classification per start tag — every
|
|
7262
7416
|
* downstream consumer (frameset-ok, void, open) reads the
|
|
7263
7417
|
* resolved entry instead of re-walking the bucket. */
|
|
@@ -7367,7 +7521,41 @@ static LeptrisDocument html_parse_shared(
|
|
|
7367
7521
|
/* InBody/AfterHead startTagHead: any <head> after
|
|
7368
7522
|
* the head phase started is an unexpected start tag
|
|
7369
7523
|
* (tests6:1: </head> <head>). */
|
|
7370
|
-
|
|
7524
|
+
/* An explicit <head> while the IMPLIED head already
|
|
7525
|
+
* holds head content is html5lib's InHead
|
|
7526
|
+
* startTagHead — two-heads-are-not-better-than-one
|
|
7527
|
+
* (tests3:13: <html><meta><head>). */
|
|
7528
|
+
int head_content_before = 0;
|
|
7529
|
+
if (!b.head_tag_seen && !b.head_end_seen &&
|
|
7530
|
+
b.depth == 1 && b.open[0] &&
|
|
7531
|
+
b.open_id[0] == h_id_html) {
|
|
7532
|
+
LeptrisNodeRef hc =
|
|
7533
|
+
leptris_node_last_child_internal(
|
|
7534
|
+
(LeptrisNode*)b.open[0]);
|
|
7535
|
+
if (hc && leptris_node_get_type(hc) ==
|
|
7536
|
+
LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
7537
|
+
const char* hn2 = leptris_element_name(
|
|
7538
|
+
(LeptrisElement)hc);
|
|
7539
|
+
head_content_before =
|
|
7540
|
+
hn2 && (h_ieq_raw(hn2, "title") ||
|
|
7541
|
+
h_ieq_raw(hn2, "meta") ||
|
|
7542
|
+
h_ieq_raw(hn2, "link") ||
|
|
7543
|
+
h_ieq_raw(hn2, "base") ||
|
|
7544
|
+
h_ieq_raw(hn2, "basefont") ||
|
|
7545
|
+
h_ieq_raw(hn2, "bgsound") ||
|
|
7546
|
+
h_ieq_raw(hn2, "script") ||
|
|
7547
|
+
h_ieq_raw(hn2, "style") ||
|
|
7548
|
+
h_ieq_raw(hn2, "noframes") ||
|
|
7549
|
+
h_ieq_raw(hn2, "noscript") ||
|
|
7550
|
+
h_ieq_raw(hn2, "template"));
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
if (b.whatwg && head_content_before)
|
|
7554
|
+
h_diag(&b,
|
|
7555
|
+
"two-heads-are-not-better-than-one",
|
|
7556
|
+
(size_t)(p - b.input_base),
|
|
7557
|
+
"<head> in the implied head");
|
|
7558
|
+
else if (b.whatwg &&
|
|
7371
7559
|
(b.head_tag_seen || b.head_end_seen ||
|
|
7372
7560
|
b.body_tag_seen || b.body_seen))
|
|
7373
7561
|
h_diag(&b, "unexpected-start-tag",
|
|
@@ -7685,7 +7873,30 @@ static LeptrisDocument html_parse_shared(
|
|
|
7685
7873
|
/* 13.2.6.4.7: a <form> start tag with the form pointer
|
|
7686
7874
|
* already set (a form still open) is IGNORED - <form><form>
|
|
7687
7875
|
* is one form (tests6:13); likewise inside a table
|
|
7688
|
-
* (tests20:46/47). </form> clears the pointer.
|
|
7876
|
+
* (tests20:46/47). </form> clears the pointer.
|
|
7877
|
+
*
|
|
7878
|
+
* html5lib reports both flavors: InTable startTagForm
|
|
7879
|
+
* always says unexpected-form-in-table (even when it
|
|
7880
|
+
* inserts), and InBody reports unexpected-start-tag for a
|
|
7881
|
+
* duplicate (tests20:48: <table><form></table><form>). */
|
|
7882
|
+
if (b.whatwg && strcmp(name, "form") == 0) {
|
|
7883
|
+
int in_tbl = b.depth > 0 &&
|
|
7884
|
+
h_id_table_ctx(b.open_id[b.depth - 1]);
|
|
7885
|
+
if (!in_tbl && b.depth >= 2 &&
|
|
7886
|
+
b.open_ns[b.depth - 1] == H_NS_HTML &&
|
|
7887
|
+
b.open_id[b.depth - 1] == h_id_form &&
|
|
7888
|
+
b.open_ns[b.depth - 2] == H_NS_HTML &&
|
|
7889
|
+
b.open_id[b.depth - 2] == h_id_table)
|
|
7890
|
+
in_tbl = 1; /* table > form wrapper */
|
|
7891
|
+
if (in_tbl)
|
|
7892
|
+
h_diag(&b, "unexpected-form-in-table",
|
|
7893
|
+
(size_t)(p - b.input_base),
|
|
7894
|
+
"<form> in table context");
|
|
7895
|
+
else if (b.form_open)
|
|
7896
|
+
h_diag(&b, "unexpected-start-tag",
|
|
7897
|
+
(size_t)(p - b.input_base),
|
|
7898
|
+
"<form> while a form is open");
|
|
7899
|
+
}
|
|
7689
7900
|
if (b.whatwg && strcmp(name, "form") == 0 && b.form_open) {
|
|
7690
7901
|
/* The generic pre-element flush above already drained
|
|
7691
7902
|
* the pending run. */
|
|
@@ -8331,7 +8542,7 @@ static LeptrisDocument html_parse_shared(
|
|
|
8331
8542
|
}
|
|
8332
8543
|
}
|
|
8333
8544
|
if (!dup_out_of_scope) {
|
|
8334
|
-
h_afe_end(&b, name);
|
|
8545
|
+
h_afe_end(&b, name, (size_t)(p - b.input_base));
|
|
8335
8546
|
/* The agency's 8-iteration cap can leave the
|
|
8336
8547
|
* entry — the start-tag branch removes it
|
|
8337
8548
|
* unconditionally. */
|
|
@@ -8415,6 +8626,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
8415
8626
|
q += 2;
|
|
8416
8627
|
break;
|
|
8417
8628
|
}
|
|
8629
|
+
/* Tokenizer self-closing-start-tag: a solidus not
|
|
8630
|
+
* followed by '>' reports and reprocesses in
|
|
8631
|
+
* before-attribute-name (tests2:58). */
|
|
8632
|
+
if (b.whatwg)
|
|
8633
|
+
h_diag(&b,
|
|
8634
|
+
"unexpected-character-after-solidus-in-tag",
|
|
8635
|
+
(size_t)(q - b.input_base),
|
|
8636
|
+
"'/' before an attribute name");
|
|
8418
8637
|
q++;
|
|
8419
8638
|
continue;
|
|
8420
8639
|
}
|
|
@@ -8749,6 +8968,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
8749
8968
|
drop = 1;
|
|
8750
8969
|
break;
|
|
8751
8970
|
}
|
|
8971
|
+
/* AfterAfterFrameset processCharacters reports
|
|
8972
|
+
* expected-eof-but-got-char for every non-space
|
|
8973
|
+
* character (tests19:41). */
|
|
8974
|
+
if (drop)
|
|
8975
|
+
h_diag(&b, "expected-eof-but-got-char",
|
|
8976
|
+
(size_t)(text - b.input_base),
|
|
8977
|
+
"characters after </html> in the frameset"
|
|
8978
|
+
" phase");
|
|
8752
8979
|
}
|
|
8753
8980
|
if (!drop) {
|
|
8754
8981
|
if (!b.post_table_text) h_reconstruct(&b);
|
|
@@ -19,6 +19,17 @@ endif()
|
|
|
19
19
|
|
|
20
20
|
include(GoogleTest)
|
|
21
21
|
|
|
22
|
+
# Native builds discover the gtest cases at BUILD time so the list is
|
|
23
|
+
# baked into CTestTestfile and `ctest --parallel` can schedule the
|
|
24
|
+
# whole suite. PRE_TEST expands the list while ctest runs, which only
|
|
25
|
+
# works serially — under -j the _NOT_BUILT placeholders execute. Cross
|
|
26
|
+
# builds must keep PRE_TEST: their binaries cannot run on the host.
|
|
27
|
+
if(CMAKE_CROSSCOMPILING)
|
|
28
|
+
set(LEPTRIS_GTEST_DISCOVERY_MODE PRE_TEST)
|
|
29
|
+
else()
|
|
30
|
+
set(LEPTRIS_GTEST_DISCOVERY_MODE POST_BUILD)
|
|
31
|
+
endif()
|
|
32
|
+
|
|
22
33
|
# Convenience aggregate target — `cmake --build build --target check_all`.
|
|
23
34
|
add_custom_target(check_all
|
|
24
35
|
COMMENT "Running all Leptris specs")
|
|
@@ -52,10 +63,12 @@ function(leptris_add_test name)
|
|
|
52
63
|
# Timing specs measure ratios; concurrent processes steal cache
|
|
53
64
|
# and skew short measurements — run test_perf's cases serially.
|
|
54
65
|
if(name STREQUAL "test_perf")
|
|
55
|
-
gtest_discover_tests(${name}
|
|
66
|
+
gtest_discover_tests(${name}
|
|
67
|
+
DISCOVERY_MODE ${LEPTRIS_GTEST_DISCOVERY_MODE}
|
|
56
68
|
PROPERTIES RUN_SERIAL TRUE)
|
|
57
69
|
else()
|
|
58
|
-
gtest_discover_tests(${name}
|
|
70
|
+
gtest_discover_tests(${name}
|
|
71
|
+
DISCOVERY_MODE ${LEPTRIS_GTEST_DISCOVERY_MODE})
|
|
59
72
|
endif()
|
|
60
73
|
add_dependencies(check_all ${name})
|
|
61
74
|
endfunction()
|
|
@@ -69,13 +82,15 @@ function(leptris_add_cli_test name)
|
|
|
69
82
|
target_compile_definitions(${name} PRIVATE
|
|
70
83
|
LEPTRIS_CLI_BIN="${CMAKE_BINARY_DIR}/cli/leptris"
|
|
71
84
|
LEPTRIS_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
|
|
85
|
+
# WORKING_DIRECTORY is a first-class keyword in the modern
|
|
86
|
+
# gtest_discover_tests signature. Passing it inside PROPERTIES
|
|
87
|
+
# made the parser eat RUN_SERIAL as an unparsed argument, so the
|
|
88
|
+
# CLI cases were never actually serialized (ctest -j4 then races
|
|
89
|
+
# the shared fixture cwd — the schematron/rng flakes).
|
|
72
90
|
gtest_discover_tests(${name}
|
|
73
|
-
DISCOVERY_MODE
|
|
74
|
-
PROPERTIES
|
|
91
|
+
DISCOVERY_MODE ${LEPTRIS_GTEST_DISCOVERY_MODE}
|
|
75
92
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
|
76
|
-
|
|
77
|
-
# parallel ctest flakes them — run serially.
|
|
78
|
-
RUN_SERIAL TRUE
|
|
93
|
+
PROPERTIES RUN_SERIAL TRUE
|
|
79
94
|
)
|
|
80
95
|
add_dependencies(check_all ${name} leptris-cli)
|
|
81
96
|
endfunction()
|
|
@@ -998,6 +998,18 @@ TEST(Html5LibCorpus, ErrorVerdictParity) {
|
|
|
998
998
|
* eof-in-comment-double-dash; the plaintext/EOF slice adds the
|
|
999
999
|
* plaintext open-at-EOF and the "</" tokenizer EOF codes; the
|
|
1000
1000
|
* stray-end-tag slice makes the depth-0 branch phase-aware for
|
|
1001
|
-
* raw-text, void and menuitem names
|
|
1002
|
-
|
|
1001
|
+
* raw-text, void and menuitem names; this batch adds the
|
|
1002
|
+
* depth-0 </p> phase code, the after-</html> end-tag code, the
|
|
1003
|
+
* frameset after-html character code, and image-start-tag; the
|
|
1004
|
+
* agency slice adds adoption-agency-1.3 for formatting end tags
|
|
1005
|
+
* whose element is in scope but not the current node; the
|
|
1006
|
+
* menuitem slice adds the endTagOther pair for </menuitem>
|
|
1007
|
+
* (stray before a special, or found with non-implied open
|
|
1008
|
+
* elements above); the closing slice adds the frameset
|
|
1009
|
+
* trailing-character code, BeforeHead end-tag-after-implied-
|
|
1010
|
+
* root, foreign-content end-tag mismatch, markup-declaration
|
|
1011
|
+
* (expected-dashes-or-doctype), solidus-in-tag, attributes-in-
|
|
1012
|
+
* end-tag, before-html end tags, form-in-table, template foster
|
|
1013
|
+
* characters and two-heads. Only raises. */
|
|
1014
|
+
EXPECT_GE(parity, (size_t)1555);
|
|
1003
1015
|
}
|
|
@@ -94,6 +94,111 @@ TEST(XPathFunctions, CountReturnsNodeSetSize) {
|
|
|
94
94
|
leptris_document_free(doc);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/* leptris-ruby #519: a node-set XPath run primes the TODO 132 element
|
|
98
|
+
* index. Sibling insertions change the element set but did not
|
|
99
|
+
* invalidate it, so the next //name query answered from the stale
|
|
100
|
+
* index and missed the inserted element. Child insertion (append)
|
|
101
|
+
* always invalidated; every splice path must too. */
|
|
102
|
+
TEST(XPathIndexInvalidation, SiblingInsertionsRefreshDescendantAxis) {
|
|
103
|
+
const char xml[] = "<root><anchor/></root>";
|
|
104
|
+
|
|
105
|
+
auto eval_count = [](LeptrisDocument doc, const char* expr) -> int {
|
|
106
|
+
LeptrisXPathResult r = leptris_xpath_eval(doc, nullptr, expr);
|
|
107
|
+
if (!r) return -1;
|
|
108
|
+
int n = (int)leptris_xpath_result_count(r);
|
|
109
|
+
leptris_xpath_result_free(r);
|
|
110
|
+
return n;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/* TODO 190: the index builds on the SECOND axis query. Prime it so
|
|
114
|
+
* the post-insert query is served from the cached index — the
|
|
115
|
+
* stale-read shape in the report. */
|
|
116
|
+
auto prime = [&eval_count](LeptrisDocument doc) {
|
|
117
|
+
EXPECT_EQ(eval_count(doc, "//anchor"), 1);
|
|
118
|
+
EXPECT_EQ(eval_count(doc, "//anchor"), 1);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/* insert_after — the shape in the report (add_next_sibling). */
|
|
122
|
+
{
|
|
123
|
+
LeptrisDocument doc =
|
|
124
|
+
leptris_parse_string(xml, strlen(xml), nullptr);
|
|
125
|
+
ASSERT_NE(doc, nullptr);
|
|
126
|
+
prime(doc);
|
|
127
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 0);
|
|
128
|
+
LeptrisElement anchor = leptris_element_first_child_any(
|
|
129
|
+
leptris_document_root(doc));
|
|
130
|
+
ASSERT_NE(anchor, nullptr);
|
|
131
|
+
LeptrisElement ext = leptris_element_create(doc, "ext");
|
|
132
|
+
ASSERT_NE(ext, nullptr);
|
|
133
|
+
ASSERT_EQ(leptris_element_insert_after(anchor, ext), LEPTRIS_OK);
|
|
134
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 1);
|
|
135
|
+
leptris_document_free(doc);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* insert_before. */
|
|
139
|
+
{
|
|
140
|
+
LeptrisDocument doc =
|
|
141
|
+
leptris_parse_string(xml, strlen(xml), nullptr);
|
|
142
|
+
ASSERT_NE(doc, nullptr);
|
|
143
|
+
prime(doc);
|
|
144
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 0);
|
|
145
|
+
LeptrisElement anchor = leptris_element_first_child_any(
|
|
146
|
+
leptris_document_root(doc));
|
|
147
|
+
ASSERT_NE(anchor, nullptr);
|
|
148
|
+
LeptrisElement ext = leptris_element_create(doc, "ext");
|
|
149
|
+
ASSERT_NE(ext, nullptr);
|
|
150
|
+
ASSERT_EQ(leptris_element_insert_before(anchor, ext), LEPTRIS_OK);
|
|
151
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 1);
|
|
152
|
+
leptris_document_free(doc);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* prepend_child — the other child splice that skipped invalidation. */
|
|
156
|
+
{
|
|
157
|
+
LeptrisDocument doc =
|
|
158
|
+
leptris_parse_string(xml, strlen(xml), nullptr);
|
|
159
|
+
ASSERT_NE(doc, nullptr);
|
|
160
|
+
prime(doc);
|
|
161
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 0);
|
|
162
|
+
LeptrisElement root = leptris_document_root(doc);
|
|
163
|
+
ASSERT_NE(root, nullptr);
|
|
164
|
+
LeptrisElement ext = leptris_element_create(doc, "ext");
|
|
165
|
+
ASSERT_NE(ext, nullptr);
|
|
166
|
+
ASSERT_EQ(leptris_element_prepend_child(root, ext), LEPTRIS_OK);
|
|
167
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 1);
|
|
168
|
+
leptris_document_free(doc);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Compiled handle — the raw FFI face the binding evaluates with. */
|
|
172
|
+
{
|
|
173
|
+
LeptrisDocument doc =
|
|
174
|
+
leptris_parse_string(xml, strlen(xml), nullptr);
|
|
175
|
+
ASSERT_NE(doc, nullptr);
|
|
176
|
+
prime(doc);
|
|
177
|
+
LeptrisXPathCompiled compiled = leptris_xpath_compile("//ext");
|
|
178
|
+
ASSERT_NE(compiled, nullptr);
|
|
179
|
+
LeptrisXPathResult first =
|
|
180
|
+
leptris_xpath_compiled_eval(compiled, doc, nullptr);
|
|
181
|
+
ASSERT_NE(first, nullptr);
|
|
182
|
+
EXPECT_EQ(leptris_xpath_result_count(first), 0u);
|
|
183
|
+
leptris_xpath_result_free(first);
|
|
184
|
+
|
|
185
|
+
LeptrisElement anchor = leptris_element_first_child_any(
|
|
186
|
+
leptris_document_root(doc));
|
|
187
|
+
ASSERT_NE(anchor, nullptr);
|
|
188
|
+
LeptrisElement ext = leptris_element_create(doc, "ext");
|
|
189
|
+
ASSERT_NE(ext, nullptr);
|
|
190
|
+
ASSERT_EQ(leptris_element_insert_after(anchor, ext), LEPTRIS_OK);
|
|
191
|
+
|
|
192
|
+
LeptrisXPathResult second =
|
|
193
|
+
leptris_xpath_compiled_eval(compiled, doc, nullptr);
|
|
194
|
+
ASSERT_NE(second, nullptr);
|
|
195
|
+
EXPECT_EQ(leptris_xpath_result_count(second), 1u);
|
|
196
|
+
leptris_xpath_result_free(second);
|
|
197
|
+
leptris_xpath_compiled_free(compiled);
|
|
198
|
+
leptris_document_free(doc);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
97
202
|
/* Lane 07 tail (#692-B): function items report a distinct public
|
|
98
203
|
* result type at the boundary — closures and named references. */
|
|
99
204
|
TEST(XPathResultTypes, FunctionItemsReportFunctionType) {
|
|
@@ -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.379",
|
|
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,14 +1,14 @@
|
|
|
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.379.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -206,6 +206,7 @@ files:
|
|
|
206
206
|
- vendor-src/libleptris/CONTEXT.md
|
|
207
207
|
- vendor-src/libleptris/LICENSE.md
|
|
208
208
|
- vendor-src/libleptris/README.adoc
|
|
209
|
+
- vendor-src/libleptris/SECURITY.md
|
|
209
210
|
- vendor-src/libleptris/TODO.SOTA.md
|
|
210
211
|
- vendor-src/libleptris/TODO.assemble/README.md
|
|
211
212
|
- vendor-src/libleptris/TODO.bindings/01-mutation-construction.md
|