leptris 1.9.376.0-aarch64-linux → 1.9.378.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 +18 -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 +75 -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 +367 -22
- data/vendor-src/libleptris/test/CMakeLists.txt +22 -7
- data/vendor-src/libleptris/test/html/test_html5lib.cpp +22 -2
- data/vendor-src/libleptris/test/xpath/test_xpath.cpp +105 -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: 2ba79bde3af1d4d761b8ef2c76181e70bb301c495580df031c9ec1367e3b8162
|
|
4
|
+
data.tar.gz: 8a8a3cf775405cde5b8ee9afa1f86f442cf1c1bf06bcc2f7c527a3acf401ffa0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dd19893f0914c2360defb6cfac75de98d375533c6f677b89a711857a84bbc56dfe28f70a7dc5464db8b5084b1b96864e09b5fd071f0ab5a6b2c027406b9b2a5e
|
|
7
|
+
data.tar.gz: 4403b111e2d1eee3bbcab572b49ee159cd51c8dd92841d88d656efe766e3e8d82f1e806483ef04d9ec89192d561f3f1669094153122ef1d25e8ac1e5135e908d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## [1.9.378.0] - 2026-10-21
|
|
2
|
+
|
|
3
|
+
### Fixed — engine sync (libleptris 1.9.378)
|
|
4
|
+
|
|
5
|
+
- **Node-set XPath after sibling insertion (leptris-ruby#519,
|
|
6
|
+
leptris/leptris#1802)**: the lazy element index was invalidated
|
|
7
|
+
by `add_child` but not by the sibling/prepend splice paths, so
|
|
8
|
+
`xpath("//name")` after `add_next_sibling` answered from the
|
|
9
|
+
pre-insert tree until an unrelated child insert rebuilt it.
|
|
10
|
+
`insert_before`, `insert_after` and `prepend_child` now
|
|
11
|
+
invalidate the document index.
|
|
12
|
+
|
|
13
|
+
### Changed — engine sync
|
|
14
|
+
|
|
15
|
+
- **WHATWG HTML verdict parity complete (leptris/leptris#1803)**:
|
|
16
|
+
1555/1555 corpus documents now report parse errors exactly when
|
|
17
|
+
html5lib does, with tree parity unchanged at 1555/1555.
|
|
18
|
+
|
|
1
19
|
## [1.9.356.0] - 2026-10-14
|
|
2
20
|
|
|
3
21
|
### 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.378"
|
|
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,80 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.378] - 2026-10-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **SECURITY.md: private reporting channel and CVE-response note
|
|
8
|
+
(PR #1804).** Reports go through GitHub Security Advisories, with
|
|
9
|
+
an acknowledgment target, coordinated disclosure, and CVE
|
|
10
|
+
assignment when warranted. The supported-version table follows
|
|
11
|
+
the lockstep rule: a fix is delivered only when the binding
|
|
12
|
+
release carries the fixed engine. The hardening inventory records
|
|
13
|
+
what actually ships: zero required runtime dependencies,
|
|
14
|
+
ASAN+leak and macOS `leaks` lanes, nightly libFuzzer on the
|
|
15
|
+
parser, explicit depth/size limits, and the conformance suites.
|
|
16
|
+
- **Test suite runs in parallel without changing any test (PR
|
|
17
|
+
#1799).** `gtest_discover_tests` now bakes the case list at build
|
|
18
|
+
time for native builds (`POST_BUILD`; cross builds keep
|
|
19
|
+
`PRE_TEST`), so `ctest --parallel` expands real cases instead of
|
|
20
|
+
`_NOT_BUILT` placeholders. CI runs the native and matrix lanes at
|
|
21
|
+
`--parallel 4` (qemu s390x and ILP32 stay serial). The CLI test
|
|
22
|
+
helper passed `WORKING_DIRECTORY` inside `PROPERTIES`, which
|
|
23
|
+
silently dropped the following `RUN_SERIAL`; passing it as a
|
|
24
|
+
keyword restores the serialization. Same 2074 cases, all green:
|
|
25
|
+
66.0 s serial → 31.0 s `-j4` locally; the export check alone
|
|
26
|
+
20.7-23.3 s → 8.7-9.3 s.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **dom: node-set XPath no longer answers from a stale element
|
|
31
|
+
index after sibling insertion (leptris-ruby#519, PR #1802).** The
|
|
32
|
+
lazy TODO 132 index was invalidated by `append_child` but not by
|
|
33
|
+
`insert_before`, `insert_after` or `prepend_child`, so a
|
|
34
|
+
`//name` query after `add_next_sibling` missed the new element
|
|
35
|
+
until an unrelated child insert rebuilt the index. All three
|
|
36
|
+
splice paths now invalidate the document index; the regression
|
|
37
|
+
spec covers each path plus the compiled-handle FFI face the
|
|
38
|
+
bindings evaluate with.
|
|
39
|
+
- **html: WHATWG verdict parity is complete — 1555/1555, zero
|
|
40
|
+
misses (PR #1803).** The remaining 20 corpus documents now report
|
|
41
|
+
at their html5lib source sites: `adoption-agency-1.3` for
|
|
42
|
+
formatting end tags whose element is in scope but not the current
|
|
43
|
+
node; the `</menuitem>` endTagOther pair (stray, and found with
|
|
44
|
+
non-implied open elements above); tokenizer
|
|
45
|
+
`expected-dashes-or-doctype`,
|
|
46
|
+
`unexpected-character-after-solidus-in-tag` and
|
|
47
|
+
`attributes-in-end-tag`; AfterAfterFrameset trailing characters,
|
|
48
|
+
BeforeHead `end-tag-after-implied-root`, BeforeHtml
|
|
49
|
+
`unexpected-end-tag-before-html`, and foreign-content end-tag
|
|
50
|
+
mismatch; `unexpected-form-in-table` for both the insertion and
|
|
51
|
+
the ignored duplicate plus the `</form>` table-voodoo pair;
|
|
52
|
+
template `foster-parenting-character`; and
|
|
53
|
+
`two-heads-are-not-better-than-one`. Tree parity stays
|
|
54
|
+
1555/1555; the verdict floor is pinned at the full corpus count.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## [1.9.377] - 2026-10-20
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **html: verdict parity 1508 to 1532 of 1555 (PR #1797).** Five
|
|
62
|
+
emission slices, each grounded in html5lib's parser/tokenizer
|
|
63
|
+
source and gated by the #1771 corpus harness with zero want=clean
|
|
64
|
+
regressions: misplaced in-body caption/col/…/tr and `<frame>`
|
|
65
|
+
(unexpected-start-tag-ignored) plus the stray `</p>` phase codes;
|
|
66
|
+
after-html/after-body/after-frameset character codes
|
|
67
|
+
(expected-eof-but-got-char, unexpected-char-after-body,
|
|
68
|
+
unexpected-char-after-frameset); unterminated-comment tokenizer
|
|
69
|
+
codes (eof-in-comment, eof-in-comment-double-dash);
|
|
70
|
+
plaintext-at-EOF and the `</` tokenizer EOF codes; and a
|
|
71
|
+
phase-aware depth-0 stray-end-tag branch covering raw-text names,
|
|
72
|
+
void elements and menuitem (end-tag-after-implied-root before
|
|
73
|
+
html, after-body/-frameset/after-html variants, otherwise
|
|
74
|
+
unexpected-end-tag). Tree parity stays 1555/1555; the verdict
|
|
75
|
+
floor rose from 1508 to 1532.
|
|
76
|
+
|
|
77
|
+
|
|
3
78
|
## [1.9.376] - 2026-10-20
|
|
4
79
|
|
|
5
80
|
### Fixed
|
|
@@ -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,6 +4035,16 @@ 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/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
|
|
4045
|
+
h_diag(b, "unexpected-char-after-frameset",
|
|
4046
|
+
b->err_line_memo_off,
|
|
4047
|
+
"characters after </frameset>");
|
|
4038
4048
|
/* Every whitespace character inserts, every
|
|
4039
4049
|
* non-whitespace one drops (tests2:7: " te st" ->
|
|
4040
4050
|
* " "). Filter in place, like after-frameset. */
|
|
@@ -4057,7 +4067,23 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4057
4067
|
for (const char* q = tt; *q; q++)
|
|
4058
4068
|
if (*q != ' ' && *q != '\t' && *q != '\n' &&
|
|
4059
4069
|
*q != '\r') { nonws = 1; break; }
|
|
4060
|
-
if (nonws)
|
|
4070
|
+
if (nonws) {
|
|
4071
|
+
b->after_body_done = 1;
|
|
4072
|
+
/* AfterAfterBody/AfterAfterFrameset processCharacters
|
|
4073
|
+
* report expected-eof-but-got-char; AfterBody reports
|
|
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) {
|
|
4079
|
+
h_diag(b, "expected-eof-but-got-char",
|
|
4080
|
+
b->err_line_memo_off,
|
|
4081
|
+
"characters after </html>");
|
|
4082
|
+
} else
|
|
4083
|
+
h_diag(b, "unexpected-char-after-body",
|
|
4084
|
+
b->err_line_memo_off,
|
|
4085
|
+
"characters after </body>");
|
|
4086
|
+
}
|
|
4061
4087
|
}
|
|
4062
4088
|
/* Doc-start mixed run: the ws prefix is "before html"
|
|
4063
4089
|
* whitespace and drops (doctype01:31, tests19:79). Tightest
|
|
@@ -4194,6 +4220,31 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4194
4220
|
b->err_line_memo_off,
|
|
4195
4221
|
"hidden input inserted in table context");
|
|
4196
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 =
|
|
4232
|
+
leptris_text_node_get_content((LeptrisTextNode*)n);
|
|
4233
|
+
int nonws = 0;
|
|
4234
|
+
if (tc)
|
|
4235
|
+
for (const char* w = tc; *w; w++)
|
|
4236
|
+
if (*w != ' ' && *w != '\t' && *w != '\n' &&
|
|
4237
|
+
*w != '\r') { nonws = 1; break; }
|
|
4238
|
+
LeptrisNodeRef last = leptris_node_last_child_internal(
|
|
4239
|
+
(LeptrisNode*)b->open[b->depth - 1]);
|
|
4240
|
+
if (nonws && last &&
|
|
4241
|
+
leptris_node_get_type(last) ==
|
|
4242
|
+
LEPTRIS_NODE_TYPE_ELEMENT &&
|
|
4243
|
+
h_is_table_context((LeptrisElement)last))
|
|
4244
|
+
h_diag(b, "foster-parenting-character",
|
|
4245
|
+
b->err_line_memo_off,
|
|
4246
|
+
"text fostered in template table content");
|
|
4247
|
+
}
|
|
4197
4248
|
/* #659 foster (WHATWG only): text/elements in table context
|
|
4198
4249
|
* go before the nearest open table in ITS parent. */
|
|
4199
4250
|
if (b->whatwg_foster && tbl_ctx && h_fosterable(b, n)) {
|
|
@@ -4632,7 +4683,7 @@ static void h_reconstruct(HBuilder* b) {
|
|
|
4632
4683
|
/* The adoption agency proper. Returns 1 when the end tag is fully
|
|
4633
4684
|
* consumed; 0 means no formatting entry matched and the caller
|
|
4634
4685
|
* runs its generic (any-other-end-tag) handling. */
|
|
4635
|
-
static int h_afe_end(HBuilder* b, const char* subject) {
|
|
4686
|
+
static int h_afe_end(HBuilder* b, const char* subject, size_t off) {
|
|
4636
4687
|
if (b->depth > 0) {
|
|
4637
4688
|
LeptrisElement cur = b->open[b->depth - 1];
|
|
4638
4689
|
const char* cn = leptris_element_name(cur);
|
|
@@ -4683,6 +4734,14 @@ static int h_afe_end(HBuilder* b, const char* subject) {
|
|
|
4683
4734
|
return 1;
|
|
4684
4735
|
}
|
|
4685
4736
|
}
|
|
4737
|
+
/* html5lib adoption-agency-1.3: the formatting element is
|
|
4738
|
+
* in the stack and in scope, but open elements sit above
|
|
4739
|
+
* it (it is not the current node). Parsing proceeds. */
|
|
4740
|
+
if (b->whatwg_adopt && b->depth != (size_t)si + 1) {
|
|
4741
|
+
h_diag(b, "adoption-agency-1.3", off,
|
|
4742
|
+
"</%s> formatting element is not the current node",
|
|
4743
|
+
subject);
|
|
4744
|
+
}
|
|
4686
4745
|
int fbi = -1;
|
|
4687
4746
|
for (size_t k = (size_t)si + 1; k < b->depth; k++) {
|
|
4688
4747
|
/* Only HTML-namespace elements are furthest-block
|
|
@@ -5797,12 +5856,25 @@ static LeptrisDocument html_parse_shared(
|
|
|
5797
5856
|
size_t clen = cclose
|
|
5798
5857
|
? (size_t)(ce - cs)
|
|
5799
5858
|
: (size_t)(end - cs);
|
|
5800
|
-
if (!cclose)
|
|
5859
|
+
if (!cclose) {
|
|
5860
|
+
/* Tokenizer EOF states: a trailing "--" is
|
|
5861
|
+
* comment-end-double-dash (tests2:59), anything
|
|
5862
|
+
* else is a plain eof-in-comment (tests2:35/43). */
|
|
5863
|
+
if (b.whatwg) {
|
|
5864
|
+
int dd = clen >= 2 && cs[clen - 1] == '-' &&
|
|
5865
|
+
cs[clen - 2] == '-';
|
|
5866
|
+
h_diag(&b,
|
|
5867
|
+
dd ? "eof-in-comment-double-dash"
|
|
5868
|
+
: "eof-in-comment",
|
|
5869
|
+
(size_t)(end - b.input_base),
|
|
5870
|
+
"comment reaches end of input");
|
|
5871
|
+
}
|
|
5801
5872
|
/* Unterminated at EOF: html5lib drops the
|
|
5802
5873
|
* trailing dash run (tests2:59: <!--x-- keeps
|
|
5803
5874
|
* data "x"). */
|
|
5804
5875
|
while (clen > 0 && cs[clen - 1] == '-')
|
|
5805
5876
|
clen--;
|
|
5877
|
+
}
|
|
5806
5878
|
p = cclose ? ce + cclose : end;
|
|
5807
5879
|
LeptrisCommentNode* c = leptris_comment_create(
|
|
5808
5880
|
cs, clen, b.pool);
|
|
@@ -6074,6 +6146,15 @@ static LeptrisDocument html_parse_shared(
|
|
|
6074
6146
|
}
|
|
6075
6147
|
p = (ce + 3 <= end) ? ce + 3 : end;
|
|
6076
6148
|
} else {
|
|
6149
|
+
/* Tokenizer markup-declaration-open: "<!" that
|
|
6150
|
+
* is neither a comment nor DOCTYPE (and, in
|
|
6151
|
+
* HTML content, not CDATA) reports
|
|
6152
|
+
* expected-dashes-or-doctype (tests10:2:
|
|
6153
|
+
* <![CDATA[a]]> outside foreign content). */
|
|
6154
|
+
if (b.whatwg)
|
|
6155
|
+
h_diag(&b, "expected-dashes-or-doctype",
|
|
6156
|
+
(size_t)(p - b.input_base),
|
|
6157
|
+
"<! markup declaration");
|
|
6077
6158
|
/* CDATA-ish bogus: skip to '>'. WHATWG makes
|
|
6078
6159
|
* it a bogus comment (tests1:42-49) — except
|
|
6079
6160
|
* doctype-shaped constructs, which the
|
|
@@ -6111,6 +6192,18 @@ static LeptrisDocument html_parse_shared(
|
|
|
6111
6192
|
{
|
|
6112
6193
|
const char* ns2 = p + 2;
|
|
6113
6194
|
if (b.whatwg && ns2 >= end) {
|
|
6195
|
+
/* Tokenizer: EOF in the tag-open state emits
|
|
6196
|
+
* expected-closing-tag-but-got-eof and the two
|
|
6197
|
+
* characters as text (tests1:38); in the
|
|
6198
|
+
* initial mode that text also reports the
|
|
6199
|
+
* missing doctype. */
|
|
6200
|
+
h_diag(&b, "expected-closing-tag-but-got-eof",
|
|
6201
|
+
(size_t)(p - b.input_base),
|
|
6202
|
+
"end tag at end of input");
|
|
6203
|
+
if (!b.left_initial && !doc->doctype)
|
|
6204
|
+
h_diag(&b, "expected-doctype-but-got-chars",
|
|
6205
|
+
(size_t)(p - b.input_base),
|
|
6206
|
+
"characters in the initial mode");
|
|
6114
6207
|
if (text < p) {
|
|
6115
6208
|
LeptrisTextNode* t =
|
|
6116
6209
|
h_text_node(&b, text, p);
|
|
@@ -6172,6 +6265,20 @@ static LeptrisDocument html_parse_shared(
|
|
|
6172
6265
|
const char* q = ns;
|
|
6173
6266
|
while (q < end && !h_is_ws(*q) && *q != '>' && *q != '/') q++;
|
|
6174
6267
|
size_t nlen = (size_t)(q - ns);
|
|
6268
|
+
/* Tokenizer: an end tag may carry attributes
|
|
6269
|
+
* syntactically (they are ignored at tree time, but
|
|
6270
|
+
* the token reports attributes-in-end-tag;
|
|
6271
|
+
* tests2:18's </b test<b ...>). A lone "/" before
|
|
6272
|
+
* ">" is the self-closing form, not an attribute. */
|
|
6273
|
+
{
|
|
6274
|
+
const char* tq = q;
|
|
6275
|
+
while (tq < end && h_is_ws(*tq)) tq++;
|
|
6276
|
+
if (tq < end && *tq != '>' &&
|
|
6277
|
+
!(*tq == '/' && tq + 1 < end && tq[1] == '>'))
|
|
6278
|
+
h_diag(&b, "attributes-in-end-tag",
|
|
6279
|
+
(size_t)(tq - b.input_base),
|
|
6280
|
+
"attributes in an end tag");
|
|
6281
|
+
}
|
|
6175
6282
|
while (q < end && *q != '>') q++;
|
|
6176
6283
|
if (q < end) q++;
|
|
6177
6284
|
/* Flush pending text before closing. A
|
|
@@ -6209,6 +6316,17 @@ static LeptrisDocument html_parse_shared(
|
|
|
6209
6316
|
for (size_t i = 0; i < cl0; i++)
|
|
6210
6317
|
lname0[i] = h_lower(ns[i]);
|
|
6211
6318
|
lname0[cl0] = 0;
|
|
6319
|
+
/* html5lib InForeignContent.processEndTag: an end
|
|
6320
|
+
* tag that does not name the current foreign node
|
|
6321
|
+
* reports before the walk (tests19:82: <math></p>). */
|
|
6322
|
+
{
|
|
6323
|
+
const char* topn = leptris_element_name(
|
|
6324
|
+
b.open[b.depth - 1]);
|
|
6325
|
+
if (topn && !h_ieq_raw(topn, lname0))
|
|
6326
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6327
|
+
(size_t)(p - b.input_base),
|
|
6328
|
+
"</%s> in foreign content", lname0);
|
|
6329
|
+
}
|
|
6212
6330
|
int match_foreign = 0, crossed_ip = 0;
|
|
6213
6331
|
for (size_t d = b.depth; d > 0; d--) {
|
|
6214
6332
|
if (b.open_ns[d - 1] == H_NS_HTML) break;
|
|
@@ -6259,27 +6377,91 @@ static LeptrisDocument html_parse_shared(
|
|
|
6259
6377
|
text = p;
|
|
6260
6378
|
continue;
|
|
6261
6379
|
}
|
|
6262
|
-
/*
|
|
6263
|
-
*
|
|
6264
|
-
*
|
|
6265
|
-
*
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6380
|
+
/* AfterAfterBody/AfterAfterFrameset processEndTag: ANY
|
|
6381
|
+
* end tag is a phase error, and the token is then
|
|
6382
|
+
* reprocessed in body — report once and fall through
|
|
6383
|
+
* with the phase flags cleared (tests19:43). */
|
|
6384
|
+
if (b.whatwg && b.after_html) {
|
|
6385
|
+
h_diag(&b, "expected-eof-but-got-end-tag",
|
|
6386
|
+
(size_t)(p - b.input_base),
|
|
6387
|
+
"end tag after </html>");
|
|
6388
|
+
}
|
|
6389
|
+
/* Stray end tags with no open stack. The raw-text family
|
|
6390
|
+
* completes during start-tag handling; void elements and
|
|
6391
|
+
* menuitem have no in-body end handler. html5lib's
|
|
6392
|
+
* endTagOther reports by phase: before-html is
|
|
6393
|
+
* end-tag-after-implied-root; AfterBody/AfterFrameset
|
|
6394
|
+
* and AfterAfter* have their own codes; InHead/InBody/
|
|
6395
|
+
* AfterHead report unexpected-end-tag. */
|
|
6396
|
+
if (b.whatwg && nlen && b.depth == 0) {
|
|
6270
6397
|
char rn[16];
|
|
6271
6398
|
size_t rl = nlen < sizeof(rn) - 1 ? nlen : sizeof(rn) - 1;
|
|
6272
6399
|
for (size_t i = 0; i < rl; i++) rn[i] = h_lower(ns[i]);
|
|
6273
6400
|
rn[rl] = 0;
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6401
|
+
int stray = h_is_rawtext_name(rn) ||
|
|
6402
|
+
(h_is_void(rn) && strcmp(rn, "br") != 0) ||
|
|
6403
|
+
strcmp(rn, "menuitem") == 0;
|
|
6404
|
+
/* BeforeHtml endTagOther: every end tag other than
|
|
6405
|
+
* head/body/html/br reports here (html5lib
|
|
6406
|
+
* BeforeHtmlPhase; tests2:18's </b ...>). */
|
|
6407
|
+
int before_html = !b.top_tail && !b.head_tag_seen &&
|
|
6408
|
+
!b.body_seen && !b.body_tag_seen;
|
|
6409
|
+
int bb_exempt = strcmp(rn, "head") == 0 ||
|
|
6410
|
+
strcmp(rn, "body") == 0 ||
|
|
6411
|
+
strcmp(rn, "html") == 0 ||
|
|
6412
|
+
strcmp(rn, "br") == 0;
|
|
6413
|
+
if (stray || (before_html && !bb_exempt)) {
|
|
6414
|
+
const char* code = "unexpected-end-tag";
|
|
6415
|
+
if (b.after_html)
|
|
6416
|
+
code = "expected-eof-but-got-end-tag";
|
|
6417
|
+
else if (b.after_body)
|
|
6418
|
+
code = "unexpected-end-tag-after-body";
|
|
6419
|
+
else if (b.frameset && b.after_frameset)
|
|
6420
|
+
code = "unexpected-end-tag-after-frameset";
|
|
6421
|
+
else if (b.frameset)
|
|
6422
|
+
code = "unexpected-end-tag-in-frameset";
|
|
6423
|
+
else if (before_html)
|
|
6424
|
+
code = "unexpected-end-tag-before-html";
|
|
6425
|
+
h_diag(&b, code, (size_t)(p - b.input_base),
|
|
6277
6426
|
"</%s> has no matching open element", rn);
|
|
6278
6427
|
p = q;
|
|
6279
6428
|
text = p;
|
|
6280
6429
|
continue;
|
|
6281
6430
|
}
|
|
6282
6431
|
}
|
|
6432
|
+
/* InHead/AfterHead endTagOther: a </p> before body
|
|
6433
|
+
* content is dropped with unexpected-end-tag and
|
|
6434
|
+
* inserts no empty p (tests19:3). Depth-0 only — the
|
|
6435
|
+
* in-body stray-p rule below owns the body phase. */
|
|
6436
|
+
if (b.whatwg && nlen == 1 && h_lower(ns[0]) == 'p' &&
|
|
6437
|
+
b.depth == 0 && !b.body_seen &&
|
|
6438
|
+
(b.head_tag_seen || b.head_end_seen) &&
|
|
6439
|
+
!b.frameset && !b.after_body && !b.after_html)
|
|
6440
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6441
|
+
(size_t)(p - b.input_base),
|
|
6442
|
+
"</p> outside the body phase");
|
|
6443
|
+
/* BeforeHead endTagOther: before the head phase starts
|
|
6444
|
+
* (only the html element open), any end tag other than
|
|
6445
|
+
* head/body/html/br is end-tag-after-implied-root
|
|
6446
|
+
* (tests19:2: <html></p><!--foo-->). */
|
|
6447
|
+
if (b.whatwg && nlen && b.depth == 1 &&
|
|
6448
|
+
b.open_id[0] == h_id_html &&
|
|
6449
|
+
!b.head_tag_seen && !b.head_end_seen &&
|
|
6450
|
+
!b.body_seen && !b.body_tag_seen &&
|
|
6451
|
+
!b.frameset && !b.after_body && !b.after_html) {
|
|
6452
|
+
char* ph = h_pooled_lower(b.pool, ns, nlen);
|
|
6453
|
+
if (ph && strcmp(ph, "head") != 0 &&
|
|
6454
|
+
strcmp(ph, "body") != 0 &&
|
|
6455
|
+
strcmp(ph, "html") != 0 &&
|
|
6456
|
+
strcmp(ph, "br") != 0) {
|
|
6457
|
+
h_diag(&b, "end-tag-after-implied-root",
|
|
6458
|
+
(size_t)(p - b.input_base),
|
|
6459
|
+
"</%s> before the head phase", ph);
|
|
6460
|
+
p = q;
|
|
6461
|
+
text = p;
|
|
6462
|
+
continue;
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6283
6465
|
if (nlen && (b.depth > 0 ||
|
|
6284
6466
|
(b.whatwg &&
|
|
6285
6467
|
((nlen == 4 &&
|
|
@@ -6295,6 +6477,19 @@ static LeptrisDocument html_parse_shared(
|
|
|
6295
6477
|
for (size_t i = 0; i < cl; i++)
|
|
6296
6478
|
lname[i] = h_lower(ns[i]);
|
|
6297
6479
|
lname[cl] = 0;
|
|
6480
|
+
/* InBody endTagOther: a VOID element has no end
|
|
6481
|
+
* handler, so a stray end tag reports
|
|
6482
|
+
* unexpected-end-tag (tests25:15-20: </keygen>).
|
|
6483
|
+
* </br> is endTagBr and acts as <br> above. */
|
|
6484
|
+
if (b.whatwg && h_is_void(lname) &&
|
|
6485
|
+
strcmp(lname, "br") != 0) {
|
|
6486
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6487
|
+
(size_t)(p - b.input_base),
|
|
6488
|
+
"</%s> is a void element", lname);
|
|
6489
|
+
p = q;
|
|
6490
|
+
text = p;
|
|
6491
|
+
continue;
|
|
6492
|
+
}
|
|
6298
6493
|
if (!h_is_void(lname)) {
|
|
6299
6494
|
if (b.whatwg &&
|
|
6300
6495
|
strcmp(lname, "menuitem") == 0) {
|
|
@@ -6304,7 +6499,7 @@ static LeptrisDocument html_parse_shared(
|
|
|
6304
6499
|
* at body); a SPECIAL element crossed
|
|
6305
6500
|
* ignores the tag (menuitem:8: x stays in
|
|
6306
6501
|
* the p). */
|
|
6307
|
-
int msp = 0;
|
|
6502
|
+
int msp = 0, implied_ok = 1;
|
|
6308
6503
|
for (size_t d2 = b.depth; d2 > 0; d2--) {
|
|
6309
6504
|
const char* on2 =
|
|
6310
6505
|
leptris_element_name(b.open[d2 - 1]);
|
|
@@ -6315,13 +6510,39 @@ static LeptrisDocument html_parse_shared(
|
|
|
6315
6510
|
break;
|
|
6316
6511
|
}
|
|
6317
6512
|
if (h_is_special_ww(on2)) break;
|
|
6513
|
+
if (!h_is_implied_end_name(on2))
|
|
6514
|
+
implied_ok = 0;
|
|
6318
6515
|
}
|
|
6319
|
-
|
|
6516
|
+
/* html5lib endTagOther reports in both
|
|
6517
|
+
* shapes: the walk found the element but
|
|
6518
|
+
* non-implied nodes sit above it, or the
|
|
6519
|
+
* walk stopped at a special (stray). */
|
|
6520
|
+
if (!msp)
|
|
6521
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6522
|
+
(size_t)(p - b.input_base),
|
|
6523
|
+
"</menuitem> is a stray end tag");
|
|
6524
|
+
else if (!implied_ok)
|
|
6525
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6526
|
+
(size_t)(p - b.input_base),
|
|
6527
|
+
"</menuitem> with open elements above");
|
|
6320
6528
|
p = q;
|
|
6321
6529
|
text = p;
|
|
6322
6530
|
continue;
|
|
6323
6531
|
}
|
|
6324
6532
|
if (b.whatwg && strcmp(lname, "form") == 0) {
|
|
6533
|
+
/* InTable endTagOther runs the body rule
|
|
6534
|
+
* under table voodoo: both codes report
|
|
6535
|
+
* (tests16:197: <form><table></form>). */
|
|
6536
|
+
if (b.depth > 0 &&
|
|
6537
|
+
h_id_table_ctx(b.open_id[b.depth - 1])) {
|
|
6538
|
+
h_diag(&b,
|
|
6539
|
+
"unexpected-end-tag-implies-table-voodoo",
|
|
6540
|
+
(size_t)(p - b.input_base),
|
|
6541
|
+
"</form> inside a table");
|
|
6542
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6543
|
+
(size_t)(p - b.input_base),
|
|
6544
|
+
"</form> inside a table");
|
|
6545
|
+
}
|
|
6325
6546
|
/* Vendored rule: </form> closes implied
|
|
6326
6547
|
* end-tag layers (option/optgroup/li/dd/
|
|
6327
6548
|
* dt/p), then pops the form ONLY when it
|
|
@@ -6433,6 +6654,16 @@ static LeptrisDocument html_parse_shared(
|
|
|
6433
6654
|
(size_t)(p - b.input_base),
|
|
6434
6655
|
"</%s> with no open heading", lname);
|
|
6435
6656
|
}
|
|
6657
|
+
/* InHead/AfterHead endTagOther: a </p> before
|
|
6658
|
+
* body content is dropped with unexpected-end-tag
|
|
6659
|
+
* and inserts nothing (tests19:3). */
|
|
6660
|
+
if (b.whatwg && strcmp(lname, "p") == 0 &&
|
|
6661
|
+
!b.body_seen && (b.head_tag_seen ||
|
|
6662
|
+
b.head_end_seen) &&
|
|
6663
|
+
!b.frameset && !b.after_body && !b.after_html)
|
|
6664
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6665
|
+
(size_t)(p - b.input_base),
|
|
6666
|
+
"</p> outside the body phase");
|
|
6436
6667
|
/* #659 13.2.6.4.17: </p> with no p in button
|
|
6437
6668
|
* scope inserts an EMPTY <p> and closes it
|
|
6438
6669
|
* (tests1:29) — the stray tag still leaves a
|
|
@@ -6460,6 +6691,13 @@ static LeptrisDocument html_parse_shared(
|
|
|
6460
6691
|
break;
|
|
6461
6692
|
}
|
|
6462
6693
|
if (!p_open) {
|
|
6694
|
+
/* InBody endTagP: no p in button scope
|
|
6695
|
+
* is a parse error even though the
|
|
6696
|
+
* empty p is still inserted
|
|
6697
|
+
* (tests2:60). */
|
|
6698
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6699
|
+
(size_t)(p - b.input_base),
|
|
6700
|
+
"</p> with no p in scope");
|
|
6463
6701
|
LeptrisElement pe =
|
|
6464
6702
|
h_open_element(&b, "p");
|
|
6465
6703
|
(void)pe;
|
|
@@ -6475,7 +6713,8 @@ static LeptrisDocument html_parse_shared(
|
|
|
6475
6713
|
* matched; otherwise the generic path below
|
|
6476
6714
|
* is the any-other-end-tag run. */
|
|
6477
6715
|
if (b.whatwg_adopt && h_is_formatting(lname) &&
|
|
6478
|
-
h_afe_end(&b, lname
|
|
6716
|
+
h_afe_end(&b, lname,
|
|
6717
|
+
(size_t)(p - b.input_base))) {
|
|
6479
6718
|
p = q;
|
|
6480
6719
|
text = p;
|
|
6481
6720
|
continue;
|
|
@@ -7166,9 +7405,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
7166
7405
|
}
|
|
7167
7406
|
if (!name) goto done;
|
|
7168
7407
|
/* #659 (13.2.6.4.7): <image> is renamed <img> and
|
|
7169
|
-
* reprocessed (tests1:90)
|
|
7170
|
-
|
|
7408
|
+
* reprocessed (tests1:90); html5lib reports the legacy
|
|
7409
|
+
* spelling as image-start-tag (tests19:89). */
|
|
7410
|
+
if (b.whatwg && strcmp(name, "image") == 0) {
|
|
7411
|
+
h_diag(&b, "image-start-tag",
|
|
7412
|
+
(size_t)(p - b.input_base),
|
|
7413
|
+
"<image> acts as <img>");
|
|
7171
7414
|
name = h_pooled_lower(b.pool, "img", 3);
|
|
7415
|
+
}
|
|
7172
7416
|
/* #1218: ONE classification per start tag — every
|
|
7173
7417
|
* downstream consumer (frameset-ok, void, open) reads the
|
|
7174
7418
|
* resolved entry instead of re-walking the bucket. */
|
|
@@ -7278,7 +7522,41 @@ static LeptrisDocument html_parse_shared(
|
|
|
7278
7522
|
/* InBody/AfterHead startTagHead: any <head> after
|
|
7279
7523
|
* the head phase started is an unexpected start tag
|
|
7280
7524
|
* (tests6:1: </head> <head>). */
|
|
7281
|
-
|
|
7525
|
+
/* An explicit <head> while the IMPLIED head already
|
|
7526
|
+
* holds head content is html5lib's InHead
|
|
7527
|
+
* startTagHead — two-heads-are-not-better-than-one
|
|
7528
|
+
* (tests3:13: <html><meta><head>). */
|
|
7529
|
+
int head_content_before = 0;
|
|
7530
|
+
if (!b.head_tag_seen && !b.head_end_seen &&
|
|
7531
|
+
b.depth == 1 && b.open[0] &&
|
|
7532
|
+
b.open_id[0] == h_id_html) {
|
|
7533
|
+
LeptrisNodeRef hc =
|
|
7534
|
+
leptris_node_last_child_internal(
|
|
7535
|
+
(LeptrisNode*)b.open[0]);
|
|
7536
|
+
if (hc && leptris_node_get_type(hc) ==
|
|
7537
|
+
LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
7538
|
+
const char* hn2 = leptris_element_name(
|
|
7539
|
+
(LeptrisElement)hc);
|
|
7540
|
+
head_content_before =
|
|
7541
|
+
hn2 && (h_ieq_raw(hn2, "title") ||
|
|
7542
|
+
h_ieq_raw(hn2, "meta") ||
|
|
7543
|
+
h_ieq_raw(hn2, "link") ||
|
|
7544
|
+
h_ieq_raw(hn2, "base") ||
|
|
7545
|
+
h_ieq_raw(hn2, "basefont") ||
|
|
7546
|
+
h_ieq_raw(hn2, "bgsound") ||
|
|
7547
|
+
h_ieq_raw(hn2, "script") ||
|
|
7548
|
+
h_ieq_raw(hn2, "style") ||
|
|
7549
|
+
h_ieq_raw(hn2, "noframes") ||
|
|
7550
|
+
h_ieq_raw(hn2, "noscript") ||
|
|
7551
|
+
h_ieq_raw(hn2, "template"));
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
if (b.whatwg && head_content_before)
|
|
7555
|
+
h_diag(&b,
|
|
7556
|
+
"two-heads-are-not-better-than-one",
|
|
7557
|
+
(size_t)(p - b.input_base),
|
|
7558
|
+
"<head> in the implied head");
|
|
7559
|
+
else if (b.whatwg &&
|
|
7282
7560
|
(b.head_tag_seen || b.head_end_seen ||
|
|
7283
7561
|
b.body_tag_seen || b.body_seen))
|
|
7284
7562
|
h_diag(&b, "unexpected-start-tag",
|
|
@@ -7596,7 +7874,30 @@ static LeptrisDocument html_parse_shared(
|
|
|
7596
7874
|
/* 13.2.6.4.7: a <form> start tag with the form pointer
|
|
7597
7875
|
* already set (a form still open) is IGNORED - <form><form>
|
|
7598
7876
|
* is one form (tests6:13); likewise inside a table
|
|
7599
|
-
* (tests20:46/47). </form> clears the pointer.
|
|
7877
|
+
* (tests20:46/47). </form> clears the pointer.
|
|
7878
|
+
*
|
|
7879
|
+
* html5lib reports both flavors: InTable startTagForm
|
|
7880
|
+
* always says unexpected-form-in-table (even when it
|
|
7881
|
+
* inserts), and InBody reports unexpected-start-tag for a
|
|
7882
|
+
* duplicate (tests20:48: <table><form></table><form>). */
|
|
7883
|
+
if (b.whatwg && strcmp(name, "form") == 0) {
|
|
7884
|
+
int in_tbl = b.depth > 0 &&
|
|
7885
|
+
h_id_table_ctx(b.open_id[b.depth - 1]);
|
|
7886
|
+
if (!in_tbl && b.depth >= 2 &&
|
|
7887
|
+
b.open_ns[b.depth - 1] == H_NS_HTML &&
|
|
7888
|
+
b.open_id[b.depth - 1] == h_id_form &&
|
|
7889
|
+
b.open_ns[b.depth - 2] == H_NS_HTML &&
|
|
7890
|
+
b.open_id[b.depth - 2] == h_id_table)
|
|
7891
|
+
in_tbl = 1; /* table > form wrapper */
|
|
7892
|
+
if (in_tbl)
|
|
7893
|
+
h_diag(&b, "unexpected-form-in-table",
|
|
7894
|
+
(size_t)(p - b.input_base),
|
|
7895
|
+
"<form> in table context");
|
|
7896
|
+
else if (b.form_open)
|
|
7897
|
+
h_diag(&b, "unexpected-start-tag",
|
|
7898
|
+
(size_t)(p - b.input_base),
|
|
7899
|
+
"<form> while a form is open");
|
|
7900
|
+
}
|
|
7600
7901
|
if (b.whatwg && strcmp(name, "form") == 0 && b.form_open) {
|
|
7601
7902
|
/* The generic pre-element flush above already drained
|
|
7602
7903
|
* the pending run. */
|
|
@@ -7694,6 +7995,13 @@ static LeptrisDocument html_parse_shared(
|
|
|
7694
7995
|
}
|
|
7695
7996
|
}
|
|
7696
7997
|
if (!tbl_ctx) {
|
|
7998
|
+
/* InBody startTagMisplaced: caption/col/colgroup/
|
|
7999
|
+
* head/tbody/td/tfoot/th/thead/tr in body is a
|
|
8000
|
+
* parse error and the token is ignored
|
|
8001
|
+
* (tests25:7 <body><col>A). */
|
|
8002
|
+
h_diag(&b, "unexpected-start-tag-ignored",
|
|
8003
|
+
(size_t)(p - b.input_base),
|
|
8004
|
+
"<%s> ignored in the body phase", name);
|
|
7697
8005
|
/* The TOKEN is ignored - surrounding character
|
|
7698
8006
|
* tokens keep flowing into body (the html-merge
|
|
7699
8007
|
* flush pattern). */
|
|
@@ -7712,6 +8020,20 @@ static LeptrisDocument html_parse_shared(
|
|
|
7712
8020
|
* an ignored <frameset> vanishes). */
|
|
7713
8021
|
if (b.whatwg && strcmp(name, "frame") == 0 &&
|
|
7714
8022
|
(!b.frameset || b.after_frameset)) {
|
|
8023
|
+
/* InBody reports the frame as a misplaced start tag
|
|
8024
|
+
* (tests25:10); the after-</frameset> phase names its
|
|
8025
|
+
* own code (the after-</html> phase is reported at the
|
|
8026
|
+
* start-tag dispatch). */
|
|
8027
|
+
if (!b.after_html) {
|
|
8028
|
+
if (!b.frameset)
|
|
8029
|
+
h_diag(&b, "unexpected-start-tag-ignored",
|
|
8030
|
+
(size_t)(p - b.input_base),
|
|
8031
|
+
"<frame> ignored in the body phase");
|
|
8032
|
+
else
|
|
8033
|
+
h_diag(&b, "unexpected-start-tag-after-frameset",
|
|
8034
|
+
(size_t)(p - b.input_base),
|
|
8035
|
+
"<frame> after </frameset>");
|
|
8036
|
+
}
|
|
7715
8037
|
while (q < end && *q != '>') q++;
|
|
7716
8038
|
p = (q < end) ? q + 1 : end;
|
|
7717
8039
|
text = p;
|
|
@@ -8221,7 +8543,7 @@ static LeptrisDocument html_parse_shared(
|
|
|
8221
8543
|
}
|
|
8222
8544
|
}
|
|
8223
8545
|
if (!dup_out_of_scope) {
|
|
8224
|
-
h_afe_end(&b, name);
|
|
8546
|
+
h_afe_end(&b, name, (size_t)(p - b.input_base));
|
|
8225
8547
|
/* The agency's 8-iteration cap can leave the
|
|
8226
8548
|
* entry — the start-tag branch removes it
|
|
8227
8549
|
* unconditionally. */
|
|
@@ -8305,6 +8627,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
8305
8627
|
q += 2;
|
|
8306
8628
|
break;
|
|
8307
8629
|
}
|
|
8630
|
+
/* Tokenizer self-closing-start-tag: a solidus not
|
|
8631
|
+
* followed by '>' reports and reprocesses in
|
|
8632
|
+
* before-attribute-name (tests2:58). */
|
|
8633
|
+
if (b.whatwg)
|
|
8634
|
+
h_diag(&b,
|
|
8635
|
+
"unexpected-character-after-solidus-in-tag",
|
|
8636
|
+
(size_t)(q - b.input_base),
|
|
8637
|
+
"'/' before an attribute name");
|
|
8308
8638
|
q++;
|
|
8309
8639
|
continue;
|
|
8310
8640
|
}
|
|
@@ -8531,6 +8861,13 @@ static LeptrisDocument html_parse_shared(
|
|
|
8531
8861
|
"input",
|
|
8532
8862
|
name);
|
|
8533
8863
|
}
|
|
8864
|
+
/* PLAINTEXT has no close form: it is still open at
|
|
8865
|
+
* EOF, so InBody processEOF reports the unclosed
|
|
8866
|
+
* element (tests18:2-7, tests19:6). */
|
|
8867
|
+
if (b.whatwg && cq >= end && strcmp(name, "plaintext") == 0)
|
|
8868
|
+
h_diag(&b, "expected-closing-tag-but-got-eof",
|
|
8869
|
+
(size_t)(end - b.input_base),
|
|
8870
|
+
"plaintext reaches end of input");
|
|
8534
8871
|
b.depth--; /* the raw element is complete */
|
|
8535
8872
|
text = p;
|
|
8536
8873
|
continue;
|
|
@@ -8632,6 +8969,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
8632
8969
|
drop = 1;
|
|
8633
8970
|
break;
|
|
8634
8971
|
}
|
|
8972
|
+
/* AfterAfterFrameset processCharacters reports
|
|
8973
|
+
* expected-eof-but-got-char for every non-space
|
|
8974
|
+
* character (tests19:41). */
|
|
8975
|
+
if (drop)
|
|
8976
|
+
h_diag(&b, "expected-eof-but-got-char",
|
|
8977
|
+
(size_t)(text - b.input_base),
|
|
8978
|
+
"characters after </html> in the frameset"
|
|
8979
|
+
" phase");
|
|
8635
8980
|
}
|
|
8636
8981
|
if (!drop) {
|
|
8637
8982
|
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()
|
|
@@ -990,6 +990,26 @@ TEST(Html5LibCorpus, ErrorVerdictParity) {
|
|
|
990
990
|
* out-of-my-head tags; the select/table/foreign slice adds
|
|
991
991
|
* unexpected-input-in-select, hidden-input-in-table, and
|
|
992
992
|
* unexpected-html-element-in-foreign-content; the tokenizer
|
|
993
|
-
* slice adds need-space-after-doctype
|
|
994
|
-
|
|
993
|
+
* slice adds need-space-after-doctype; the misplaced-tag slice
|
|
994
|
+
* adds in-body caption/col/…/tr and frame (ignored), plus the
|
|
995
|
+
* stray </p> phase codes; the character-phase slice adds
|
|
996
|
+
* expected-eof-but-got-char and unexpected-char-after-body/
|
|
997
|
+
* -frameset; the tokenizer EOF slice adds eof-in-comment and
|
|
998
|
+
* eof-in-comment-double-dash; the plaintext/EOF slice adds the
|
|
999
|
+
* plaintext open-at-EOF and the "</" tokenizer EOF codes; the
|
|
1000
|
+
* stray-end-tag slice makes the depth-0 branch phase-aware for
|
|
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);
|
|
995
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.378",
|
|
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.378.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -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
|