makiri 0.10.0.rc1-aarch64-linux → 0.11.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/.github/workflows/libfuzzer.yml +1 -1
- data/.github/workflows/release.yml +11 -2
- data/.github/workflows/security.yml +7 -8
- data/CHANGELOG.md +306 -29
- data/NOKOGIRI_DIFFERENCES.md +325 -0
- data/README.md +17 -154
- data/Rakefile +39 -9
- data/lib/makiri/3.2/makiri.so +0 -0
- data/lib/makiri/3.3/makiri.so +0 -0
- data/lib/makiri/3.4/makiri.so +0 -0
- data/lib/makiri/4.0/makiri.so +0 -0
- data/lib/makiri/attr.rb +15 -0
- data/lib/makiri/cdata_section.rb +8 -0
- data/lib/makiri/clone_via_dup.rb +19 -0
- data/lib/makiri/comment.rb +7 -0
- data/lib/makiri/css.rb +5 -4
- data/lib/makiri/document_fragment.rb +5 -2
- data/lib/makiri/document_type.rb +2 -3
- data/lib/makiri/element.rb +9 -0
- data/lib/makiri/error.rb +26 -0
- data/lib/makiri/html/document.rb +19 -14
- data/lib/makiri/html/node_methods.rb +9 -8
- data/lib/makiri/html.rb +3 -3
- data/lib/makiri/node.rb +14 -81
- data/lib/makiri/node_path.rb +95 -0
- data/lib/makiri/node_set.rb +50 -25
- data/lib/makiri/processing_instruction.rb +10 -8
- data/lib/makiri/reader_aliases.rb +28 -0
- data/lib/makiri/text.rb +7 -0
- data/lib/makiri/version.rb +1 -1
- data/lib/makiri/xml/builder.rb +17 -8
- data/lib/makiri/xml/document.rb +23 -3
- data/lib/makiri/xml/namespace.rb +17 -0
- data/lib/makiri/xml/node_methods.rb +26 -25
- data/lib/makiri/xml.rb +4 -3
- data/lib/makiri/xpath.rb +8 -8
- data/lib/makiri/xpath_context.rb +1 -1
- data/lib/makiri/xpath_syntax.rb +43 -0
- data/lib/makiri.rb +14 -7
- data/script/api_manifest.rb +25 -3
- data/script/check_alloc_failures.rb +398 -16
- data/script/check_unsafe_boundaries.rb +480 -66
- data/script/leaks_harness.rb +14 -1
- data/suppressions/ruby.supp +10 -8
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b203c0ffb03e49f10b8e30afd6b070d36fd054867b7b3fd0e1376778b9e4b94
|
|
4
|
+
data.tar.gz: 3a26a99a690546bcc5367fab22645b8c47bd65dec58df75df5a7a93950de3386
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5397148284ec99562ffe74e4f633f903405c420cc7593c996b600d13c65e9f94a48bf69a477a45063cd6e2a369b87f4d55f324bce1bcc0087b7f37cc6d73f5d4
|
|
7
|
+
data.tar.gz: 232787e75468ce0da009f1755538c062cabab7bdadd0a3a0eeb8b9e5f388f59eb0f4159e9f2b8152761f7c7c9e6619a3aa5568955358435d26f8a130e31a472e
|
|
@@ -86,9 +86,18 @@ jobs:
|
|
|
86
86
|
# names the GNU triple; this installs the std that goes with it.
|
|
87
87
|
# Empty elsewhere, so the other runners are unaffected.
|
|
88
88
|
targets: ${{ runner.os == 'Windows' && 'x86_64-pc-windows-gnu' || '' }}
|
|
89
|
-
|
|
89
|
+
# clang/lld/llvm are for the vendored Lexbor's LTO, not for bindgen: on
|
|
90
|
+
# Linux the whole chain has to be LLVM or extconf turns LTO off (gcc
|
|
91
|
+
# emits GIMPLE, which rust-lld cannot read - see the note in
|
|
92
|
+
# extconf.rb). It DETECTS them, so a runner without them still builds,
|
|
93
|
+
# just without LTO - which is what this job used to ship, silently,
|
|
94
|
+
# to every user who installs the precompiled gem (see the measured win
|
|
95
|
+
# in CLAUDE.md's "Vendored Lexbor is built with LTO" section: this is
|
|
96
|
+
# the job that produces those gems, and `ci.yml` installing the chain
|
|
97
|
+
# only exercises the fast path in CI, it does not ship it).
|
|
98
|
+
- name: Install libclang (bindgen) and the LLVM chain (Lexbor LTO)
|
|
90
99
|
if: runner.os == 'Linux'
|
|
91
|
-
run: sudo apt-get update && sudo apt-get install -y libclang-dev
|
|
100
|
+
run: sudo apt-get update && sudo apt-get install -y libclang-dev clang lld llvm
|
|
92
101
|
- uses: ruby/setup-ruby@v1
|
|
93
102
|
with:
|
|
94
103
|
ruby-version: ${{ matrix.ruby }}
|
|
@@ -55,7 +55,7 @@ jobs:
|
|
|
55
55
|
bundler-cache: true
|
|
56
56
|
|
|
57
57
|
- name: Run short fuzz under sanitizers
|
|
58
|
-
run: bundle exec rake fuzz:sanitize
|
|
58
|
+
run: FUZZ_TIME=30 bundle exec rake fuzz:sanitize
|
|
59
59
|
|
|
60
60
|
# macOS-only malloc-leak gate: ASan everywhere runs with detect_leaks=0 (Ruby
|
|
61
61
|
# and Lexbor are uninstrumented), so this is the ONLY automated leak check. It
|
|
@@ -98,9 +98,9 @@ jobs:
|
|
|
98
98
|
run: bundle exec rake leaks
|
|
99
99
|
|
|
100
100
|
# OOM-injection sweep: rebuilds with MAKIRI_ALLOC_INJECT=1 and fails each core
|
|
101
|
-
#
|
|
102
|
-
# clean exception or a baseline-identical result,
|
|
103
|
-
# (see script/check_alloc_failures.rb).
|
|
101
|
+
# Rust allocation site in turn, gating that every OOM branch fails closed - a
|
|
102
|
+
# clean exception or a baseline-identical result, with stateful objects reusable
|
|
103
|
+
# after the failure (see script/check_alloc_failures.rb).
|
|
104
104
|
security-alloc-inject:
|
|
105
105
|
name: OOM-injection sweep
|
|
106
106
|
runs-on: ubuntu-latest
|
|
@@ -137,9 +137,8 @@ jobs:
|
|
|
137
137
|
- name: Run the OOM-injection sweep
|
|
138
138
|
run: bundle exec rake oom
|
|
139
139
|
|
|
140
|
-
# Nightly: fuzz EVERY target
|
|
141
|
-
#
|
|
142
|
-
# documented memory-safety risk, so each gets a full 300s run).
|
|
140
|
+
# Nightly: fuzz EVERY target for a longer run. The PR job already exercises
|
|
141
|
+
# every surface for 30s; nightly gives each target a full 300s run.
|
|
143
142
|
security-fuzz-nightly:
|
|
144
143
|
name: Nightly sanitized fuzz (${{ matrix.target }})
|
|
145
144
|
runs-on: ubuntu-latest
|
|
@@ -178,7 +177,7 @@ jobs:
|
|
|
178
177
|
bundler-cache: true
|
|
179
178
|
|
|
180
179
|
- name: Run nightly fuzz under sanitizers
|
|
181
|
-
run:
|
|
180
|
+
run: FUZZ_ARGS="--target ${{ matrix.target }} --time 300" bundle exec rake fuzz:sanitize
|
|
182
181
|
|
|
183
182
|
# Nightly: the whole spec suite with Lexbor ITSELF built under ASan (mraw
|
|
184
183
|
# poisoning on), catching intra-arena overflows that a plain ASan build cannot
|
data/CHANGELOG.md
CHANGED
|
@@ -1,34 +1,311 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2026-09-30
|
|
4
|
+
|
|
5
|
+
No code changes since 0.11.0.rc2. Coming from 0.10.x, read the rc2 and rc1
|
|
6
|
+
notes below as well.
|
|
7
|
+
|
|
8
|
+
## [0.11.0.rc2] - 2026-09-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* `Makiri::HTML::Document#create_element_ns(namespace_uri, qualified_name)`
|
|
13
|
+
(DOM `createElementNS`). An SVG or MathML element made this way keeps its
|
|
14
|
+
name's case and behaves like a parsed one. An upper-case name in the HTML
|
|
15
|
+
namespace that names a known element (`BR`, `DIV`) raises `Makiri::Error`.
|
|
16
|
+
* `Makiri::XML::Element#set_loose_dom_attribute(qualified_name, value)` (DOM
|
|
17
|
+
`setAttribute`): the attribute is in no namespace and keeps the name as
|
|
18
|
+
given (`xmlns`, `xlink:href`, `v-on:click`). `to_xml` raises while the
|
|
19
|
+
document holds one XML cannot write.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
* A NUL in an element, attribute, doctype or PI target name raises
|
|
24
|
+
`ArgumentError`, like any other invalid name. A namespace that does not fit
|
|
25
|
+
the name still raises `Makiri::Error`.
|
|
26
|
+
* HTML type selectors are case-sensitive on SVG and MathML elements, as in
|
|
27
|
+
browsers: `fegaussianblur` no longer matches `feGaussianBlur`.
|
|
28
|
+
* `set_attribute_ns` accepts the XML namespace with any prefix or none again
|
|
29
|
+
(rc1 raised); `to_xml` writes such an attribute as `xml:name`.
|
|
30
|
+
* XML `set_attribute_ns` accepts a namespace declaration XML forbids, such as
|
|
31
|
+
`xmlns:foo=""`. It binds nothing, and `to_xml` raises while it is present.
|
|
32
|
+
* XML `create_document_type` accepts any public and system id, and any name
|
|
33
|
+
without whitespace, NUL or `>`. `to_xml` raises if the doctype cannot be
|
|
34
|
+
written as XML.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
* `import_node` from XML to HTML keeps an upper-case XHTML element name
|
|
39
|
+
(`Foo`) instead of lower-casing it, and raises for one that names a known
|
|
40
|
+
element (`BR`).
|
|
41
|
+
|
|
42
|
+
## [0.11.0.rc1] - 2026-09-29
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
* HTML `#css` / `#at_css` / `#matches?` now match with Makiri's own Rust
|
|
47
|
+
implementation instead of Lexbor's `lxb_selectors` (selectors are still
|
|
48
|
+
parsed by Lexbor). Where the answers differ:
|
|
49
|
+
* `:nth-child(An+B of S)` / `:nth-last-child(... of S)` count by the CSS
|
|
50
|
+
definition; Lexbor miscounted when `S` was a comma list, held a
|
|
51
|
+
combinator, or carried pseudo-classes such as `:enabled`.
|
|
52
|
+
* `:disabled`, `:enabled` and `:checked` follow the HTML Standard: a
|
|
53
|
+
control inside a `<fieldset disabled>` (outside its first `<legend>`) is
|
|
54
|
+
disabled, `<option>` / `<optgroup>` count, and `:enabled` matches only
|
|
55
|
+
form elements.
|
|
56
|
+
* Attribute selector names are matched by qualified name, and
|
|
57
|
+
case-sensitively on SVG/MathML elements: `[href]` no longer finds
|
|
58
|
+
`xlink:href`, nor `[viewbox]` an SVG `viewBox`.
|
|
59
|
+
* `#id` / `.class` match only the no-namespace `id` / `class` attribute.
|
|
60
|
+
* A selector chain is capped at 64 compounds (as `Makiri::XML` already
|
|
61
|
+
was), and a query at 50M steps of work (XPath's limit); past either,
|
|
62
|
+
`Makiri::Error`.
|
|
63
|
+
* The column combinator `||` raises `Makiri::Error` instead of matching
|
|
64
|
+
nothing.
|
|
65
|
+
|
|
66
|
+
### Removed
|
|
67
|
+
|
|
68
|
+
* `:lexbor-contains()` in HTML `#css` / `#at_css` / `#matches?`: it still
|
|
69
|
+
parses, but raises `Makiri::Error`. `Makiri::XML`'s `#css` keeps it. See
|
|
70
|
+
NOKOGIRI_DIFFERENCES.md.
|
|
71
|
+
* `Node#name=` / `#node_name=`. The DOM cannot rename an element, and renaming
|
|
72
|
+
a Lexbor element in place could crash (`div` to `template`). Create a new
|
|
73
|
+
element and `replace` the old one.
|
|
74
|
+
|
|
75
|
+
### Security
|
|
76
|
+
|
|
77
|
+
* Hardening: every heap block the vendored Lexbor allocates carries 16 bytes
|
|
78
|
+
of slack past its end, so a small overrun cannot reach neighbouring memory
|
|
79
|
+
(not in sanitizer builds).
|
|
80
|
+
* Adding an `id` or `class` attribute beside an existing one
|
|
81
|
+
(`set_attribute_ns(nil, "ID", v)`, a namespaced `id`) no longer frees the
|
|
82
|
+
existing one under a held `Attr`. HTML attribute reads and writes follow the
|
|
83
|
+
DOM: `#[]`, `#key?`, `#[]=` and `#delete` match the qualified name
|
|
84
|
+
(`svg_a["href"]` no longer returns `xlink:href`), copies and imports keep
|
|
85
|
+
every attribute, and a no-namespace name keeps its case.
|
|
86
|
+
* `content=` on an HTML element and `delete(name)` detach the nodes they
|
|
87
|
+
remove instead of freeing them under a live wrapper.
|
|
88
|
+
* `el[name] = value` on an existing attribute no longer leaves a destroyed
|
|
89
|
+
attribute linked when storing the value runs out of memory.
|
|
90
|
+
* A checked argument String is locked while its bytes are in use, so a later
|
|
91
|
+
argument's `#to_s` cannot rewrite it (it raises instead); a receiver frozen
|
|
92
|
+
by an argument's `#to_s` is not edited; and a mutator's argument can no
|
|
93
|
+
longer rebuild the document's indexes in the middle of the edit.
|
|
94
|
+
* HTML element and attribute names follow the DOM's rules:
|
|
95
|
+
`create_element("img src=x onerror=alert(1)")` raises `ArgumentError`
|
|
96
|
+
instead of writing that markup. See NOKOGIRI_DIFFERENCES.md.
|
|
97
|
+
* HTML parsing bounds the tree depth (`max_tree_depth:`, default 400) and the
|
|
98
|
+
`<option>`s one `<select>` receives (10,000), raising `Makiri::Error` past
|
|
99
|
+
either; both made the parse quadratic.
|
|
100
|
+
* `Makiri::Lexbor::CSS.parse_stylesheet` no longer crashes on a
|
|
101
|
+
`:lexbor-contains()` after a string ended by CR, FF or a newline.
|
|
102
|
+
* A CSS query that runs out of memory raises instead of answering with what it
|
|
103
|
+
had collected; a selector-parse OOM is reported as OOM, not
|
|
104
|
+
`CSS::SyntaxError`.
|
|
105
|
+
* Inputs whose cost grew faster than their size are linear or budgeted: many
|
|
106
|
+
namespace bindings, the `preceding` axis and reverse-axis steps, XML
|
|
107
|
+
duplicate-attribute checks, `contains` / `substring-*` / `translate` on long
|
|
108
|
+
strings, XML CSS `:nth-*`, CDATA full of `]]>`, and stylesheets with
|
|
109
|
+
rejected `:lexbor-contains()`.
|
|
110
|
+
* A panic in any Ruby method Makiri defines raises `Makiri::InternalError`
|
|
111
|
+
(rescuable), not `fatal`.
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
* A document that grows by editing (appended nodes, `inner_html=`) reports
|
|
116
|
+
its new size to the GC, so memory pressure from it triggers collections; it
|
|
117
|
+
was reported once, at parse time.
|
|
118
|
+
* A node wrapped while memory runs out raises `Makiri::Error` instead of
|
|
119
|
+
coming back as a second Ruby object for the same node, without the first
|
|
120
|
+
one's `freeze`, instance variables or singleton methods.
|
|
121
|
+
* Insertion follows the DOM's pre-insertion rules in HTML and XML alike: no
|
|
122
|
+
cycles through a template's contents (which hung `dup`), no children on
|
|
123
|
+
Text/Comment/PI/DocumentType/Attr, no Text directly under an XML Document,
|
|
124
|
+
and an XML `DocumentFragment` takes children.
|
|
125
|
+
* `inner_html` / `inner_html=` on a `<template>` target its contents, agreeing
|
|
126
|
+
with `#to_html` and `content_fragment`.
|
|
127
|
+
* HTML `#keys` / `#values` raise on out-of-memory instead of returning a
|
|
128
|
+
truncated Array.
|
|
129
|
+
* `dup`, `clone_node` and `import_node` keep an element's name as written
|
|
130
|
+
(`linearGradient`, `q:Bar`), and a copied XML doctype keeps its PUBLIC id.
|
|
131
|
+
* Namespaces across HTML <-> XML `import_node`: attributes keep their own
|
|
132
|
+
namespace, a prefixed element keeps its prefix and case, names XML cannot
|
|
133
|
+
write are refused where they would be written, and copied `xmlns`
|
|
134
|
+
attributes no longer move elements between namespaces.
|
|
135
|
+
* XML namespaces: `set_attribute_ns` on a detached element survives insertion;
|
|
136
|
+
`[]=` on an existing attribute changes only its value; the mutators refuse
|
|
137
|
+
what the parser refuses (forbidden declarations, duplicate expanded names,
|
|
138
|
+
bad doctype names and PUBLIC ids); `canonicalize` raises instead of writing a
|
|
139
|
+
wrong namespace; and a refused declaration says which rule it broke.
|
|
140
|
+
* `to_xml` output re-parses: CDATA holding `]]>`, a SYSTEM id holding `"`, and
|
|
141
|
+
attributes with a namespace but no prefix.
|
|
142
|
+
* XPath: `string()` of a number follows libxml2 (as Nokogiri does);
|
|
143
|
+
`substring()` rounds per spec; node-set vs boolean comparisons follow §3.4;
|
|
144
|
+
the `xml` prefix is always bound; a comparison no longer raises
|
|
145
|
+
`LimitExceeded` because its values added up past 64 MB.
|
|
146
|
+
* `Node#path` round-trips through `#at_xpath` for CDATA, PIs, and namespaced
|
|
147
|
+
nodes (SVG/MathML, XML namespaces, `xlink:href`, Vue/Word-style names); an
|
|
148
|
+
unreachable node answers `"?"` as in Nokogiri.
|
|
149
|
+
* CSS over XML agrees with the HTML matcher on `.x\ y`, `:root`, empty and
|
|
150
|
+
whitespace attribute values, `$=` with non-ASCII, `:empty` beside a comment,
|
|
151
|
+
and `[|a]`; the `s` modifier is accepted.
|
|
152
|
+
* An HTML document refuses a second root element or a text child; an
|
|
153
|
+
attribute's parent is always its owner element.
|
|
154
|
+
* `Node#line` of a node copied from another document is nil.
|
|
155
|
+
* `Makiri::XML` nodes compare with `<=>` in document order (an attribute with
|
|
156
|
+
itself too), and `XML::Document#dup` / `#clone` return a copy.
|
|
157
|
+
* `clone_node` on a Document raises instead of returning the document.
|
|
158
|
+
* `NodeSet#css` / `#xpath` / `#at_css` / `#at_xpath` pass their extra
|
|
159
|
+
arguments through; `#xpath` of a non-node-set expression raises
|
|
160
|
+
`ArgumentError`, as in Nokogiri.
|
|
161
|
+
* `XML::Builder` no longer answers `respond_to?` for `to_ary` and similar.
|
|
162
|
+
* Namespace Hashes and `register_namespace` arguments are read as a Hash and
|
|
163
|
+
with `String()`, with one length cap and one error message.
|
|
164
|
+
* A rejected stylesheet rule's `selector_text` is sliced by Lexbor's offsets.
|
|
165
|
+
|
|
166
|
+
### Performance
|
|
167
|
+
|
|
168
|
+
* `NodeSet#at_css` / `#at_xpath` stop at the first node with a match.
|
|
169
|
+
* `XML::Node#canonicalize` looks namespaces up by prefix in the scope it keeps:
|
|
170
|
+
a 1000-deep document of declarations went from 0.7 s to 0.02 s.
|
|
171
|
+
|
|
172
|
+
## [0.10.0] - 2026-09-22
|
|
173
|
+
|
|
174
|
+
### Fixed
|
|
175
|
+
|
|
176
|
+
* A frozen node now raises `FrozenError` when it is the ARGUMENT of a tree
|
|
177
|
+
mutation, not only the receiver: `b.add_child(a)` relinks `a` exactly as
|
|
178
|
+
`a.remove` does. Both `Makiri::XML` and `Makiri::HTML`. A fragment argument
|
|
179
|
+
splices its children, which have no wrapper of their own to check.
|
|
180
|
+
* A `Makiri::XML` mutation that exceeds the document's own `max_bytes` /
|
|
181
|
+
`max_nodes` now raises `Makiri::XML::LimitExceeded` instead of reporting the
|
|
182
|
+
refusal as out of memory.
|
|
183
|
+
* Inserting a `DocumentFragment` is all or nothing on `add_child`, `before` and
|
|
184
|
+
`after`, as it already was on `replace`: a child the rules refuse no longer
|
|
185
|
+
leaves the earlier ones linked in a document the caller was told had not
|
|
186
|
+
changed.
|
|
187
|
+
* A rejected `Makiri::XML::Document#fragment` no longer charges the document for
|
|
188
|
+
the nodes it discarded. 100k rejected fragments grew a `<r/>` document to
|
|
189
|
+
77 MB; it is now 516 bytes.
|
|
190
|
+
* A failed `Makiri::XML` parse reports the FIRST failure for all four kinds
|
|
191
|
+
(`syntax` was sticky while `limit` and `unsupported` overwrote each other).
|
|
192
|
+
* `Makiri::XML#to_xml`'s serializer allocates fallibly again, so running out of
|
|
193
|
+
memory raises instead of aborting the process.
|
|
194
|
+
* `:lexbor-contains()` now rejects an argument the bundled CSS parser does not
|
|
195
|
+
take, the way any unknown pseudo-class is rejected: `Makiri::CSS::SyntaxError`
|
|
196
|
+
from `#css` / `#at_css` / `#matches?`, and a `:bad_style` rule from
|
|
197
|
+
`Makiri::Lexbor::CSS.parse_stylesheet`. Well-formed uses are unchanged.
|
|
198
|
+
|
|
199
|
+
### Performance
|
|
200
|
+
|
|
201
|
+
* `Makiri::XML#to_xml` plans namespaces from a binding stack instead of
|
|
202
|
+
re-walking each ancestor's attribute list, which cost O(depth^2 x attributes).
|
|
203
|
+
403 KB of nested prefixed attributes took 4.88s and now takes 0.001s. A
|
|
204
|
+
crafted document fails closed with `Makiri::Error` ("namespace planning
|
|
205
|
+
exceeded its step budget") rather than running on.
|
|
206
|
+
* Setting an attribute on a `Makiri::XML` element walks the attribute list once
|
|
207
|
+
instead of twice (4096 attributes: 47ms -> 27ms).
|
|
208
|
+
|
|
209
|
+
## [0.10.0.rc2] - 2026-09-20
|
|
210
|
+
|
|
211
|
+
### Added
|
|
212
|
+
|
|
213
|
+
* `XPathContext.new` now registers namespace bindings passed via
|
|
214
|
+
`prefix: uri` (previously ignored).
|
|
215
|
+
* `#css`, `#at_css`, and `#matches?` accept `(selector, namespaces = nil)`
|
|
216
|
+
on HTML documents for consistency with XML.
|
|
217
|
+
* Standardized error message format for rejected CSS selectors across XML
|
|
218
|
+
and HTML: `"<reason>: <selector>"`.
|
|
219
|
+
* Unified argument handling for `#xpath` and `#at_xpath` between HTML and XML.
|
|
220
|
+
* HTML supports per-query namespace bindings and keyword prefixes
|
|
221
|
+
(e.g., `s: uri`).
|
|
222
|
+
* XML supports custom-function handlers.
|
|
223
|
+
* XML nodes now support HTML-style node readers and aliases
|
|
224
|
+
(`#first_element_child`, `#next_element`, `#tag_name`, `#attr`,
|
|
225
|
+
`#node_name`, etc.).
|
|
226
|
+
|
|
227
|
+
### Changed
|
|
228
|
+
|
|
229
|
+
* Updated vendored Lexbor to `v3.0.0-66`.
|
|
230
|
+
* Parsed `<?php ... ?>` tags in HTML input as `ProcessingInstruction` nodes
|
|
231
|
+
instead of comments, adhering to the HTML Standard.
|
|
232
|
+
* Vendored Lexbor updated from `v3.0.0-25` to `v3.0.0-66` (`05b5d37`), for an
|
|
233
|
+
out-of-bounds write in `lxb_dom_character_data_replace` reachable through
|
|
234
|
+
`#content=`, UTF-16 surrogate rejection, exact `meta` charset matching and
|
|
235
|
+
buffer-capacity fixes. (v3.0.1 is not usable: it lacks the two CSS-selector
|
|
236
|
+
fixes Makiri upstreamed.)
|
|
237
|
+
* `<?php ... ?>` in HTML input is now a `ProcessingInstruction` node rather
|
|
238
|
+
than a Comment, serializes as `<?target data?>`, and `<?` at end of input is
|
|
239
|
+
ignored - the HTML Standard's processing-instruction tokens, which Lexbor now
|
|
240
|
+
implements. `Nokogiri::HTML5` still answers with a comment.
|
|
241
|
+
* `Makiri::XML#matches?` tests the node locally via tree walking instead of
|
|
242
|
+
performing a full-document search.
|
|
243
|
+
* `Makiri::XML::Namespace` is now an immutable `Data` object defined in Ruby.
|
|
244
|
+
* `XML::Node#value` returns its text content (or attribute value for
|
|
245
|
+
attributes), aligning with HTML node behavior.
|
|
246
|
+
* XPath over HTML now handles element/attribute names case-insensitively
|
|
247
|
+
and properly handles `xmlns` attributes according to browser specs.
|
|
248
|
+
* `Makiri::XML` checks the internal DTD subset for well-formedness and
|
|
249
|
+
raises `Makiri::XML::SyntaxError` if unsupported constructs (e.g., attribute
|
|
250
|
+
defaults, entity references) would alter the document tree.
|
|
251
|
+
* Documents with `version="1.x"` are now accepted and parsed as XML 1.0.
|
|
252
|
+
* Processing instruction (PI) targets containing colons (e.g., `<?a:b?>`)
|
|
253
|
+
are now rejected per Namespaces in XML specs.
|
|
254
|
+
|
|
255
|
+
### Fixed
|
|
256
|
+
|
|
257
|
+
* `Makiri::XML::DocumentType#prefix` now returns `nil` instead of
|
|
258
|
+
the PUBLIC ID.
|
|
259
|
+
* `Makiri::XML#last_element_child` now correctly returns an `Element`
|
|
260
|
+
instead of arbitrary child nodes (like comments or text).
|
|
261
|
+
* `Element#local_name` and `local-name()` in XPath preserve camelCase
|
|
262
|
+
for SVG/MathML elements (e.g., `foreignObject`).
|
|
263
|
+
* `inner_html=` and `outer_html=` are now atomic operations to prevent
|
|
264
|
+
leaving the DOM in a corrupted state on failure.
|
|
265
|
+
* XPath over HTML assigns empty/correct namespaces to attributes instead of
|
|
266
|
+
inheriting from their parent element.
|
|
267
|
+
* Corrected `lang()` evaluation to check the nearest language attribute
|
|
268
|
+
hierarchically.
|
|
269
|
+
* Fixed `namespace_matching: :lax` on XML to be strict, aligning with
|
|
270
|
+
`Nokogiri::XML` / `libxml2` behavior.
|
|
271
|
+
* Moving an HTML node to another document now correctly removes it
|
|
272
|
+
from the source document's indexes.
|
|
273
|
+
* Prevented XPath custom handlers from creating new nodes on the document
|
|
274
|
+
being evaluated.
|
|
275
|
+
* Fixed CSS universal selectors (`p|*`, `|*`) on XML to respect namespace
|
|
276
|
+
boundaries.
|
|
277
|
+
* CSS column combinator (`a || b`) on XML now raises
|
|
278
|
+
`Makiri::CSS::SyntaxError` instead of being misparsed as descendant selector.
|
|
279
|
+
|
|
3
280
|
## [0.10.0.rc1] - 2026-09-19
|
|
4
281
|
|
|
5
282
|
### Changed
|
|
6
283
|
|
|
7
|
-
*
|
|
284
|
+
* The native extension is rewritten in Rust. The C glue, the XPath engine,
|
|
8
285
|
the XML reader and the CSS lowering are now one Rust crate; the only C left is
|
|
9
286
|
the vendored Lexbor, still unpatched. The Ruby API is unchanged, and answers
|
|
10
287
|
were checked against the C build's recorded output as well as the existing
|
|
11
288
|
differential suites against Nokogiri.
|
|
12
289
|
|
|
13
|
-
*
|
|
290
|
+
* Installing from source needs a Rust toolchain. `cargo` (stable) and
|
|
14
291
|
libclang are required alongside CMake, and `rb_sys` becomes a runtime
|
|
15
292
|
dependency of the source gem, because its `extconf.rb` runs at install time.
|
|
16
293
|
The precompiled platform gems need none of this and do not depend on
|
|
17
294
|
`rb_sys`.
|
|
18
|
-
*
|
|
295
|
+
* Faster. Makiri now beats both Nokogiri and nokolexbor on every
|
|
19
296
|
`rake bench` row, parse included - previously ~1.25x slower than nokolexbor.
|
|
20
297
|
Source locations are stamped on the first `#line` or mutation rather than
|
|
21
298
|
during every parse, and the vendored Lexbor is built with link-time
|
|
22
299
|
optimization where the linker supports it (macOS; Linux with clang,
|
|
23
300
|
llvm-ar and lld; not Windows).
|
|
24
|
-
*
|
|
301
|
+
* An internal failure is an exception, not a crash. It used to end the
|
|
25
302
|
host process with SIGABRT. Now it unwinds on the thread that ran the call:
|
|
26
303
|
`ensure` blocks run and the process keeps working. On entry points that
|
|
27
304
|
handle input (parse, XPath, CSS, serialization, text) it is the new
|
|
28
|
-
|
|
305
|
+
`Makiri::InternalError`, which descends from `Exception`, not
|
|
29
306
|
`StandardError`, so a bare `rescue => e` does not swallow it; elsewhere it
|
|
30
307
|
is Ruby's `fatal`.
|
|
31
|
-
*
|
|
308
|
+
* An XPath handler may not modify the document being evaluated. Every
|
|
32
309
|
mutator on that document raises `Makiri::Error` while an evaluation with a
|
|
33
310
|
handler runs, because the evaluator holds names and values from it for the
|
|
34
311
|
whole walk.
|
|
@@ -46,14 +323,14 @@
|
|
|
46
323
|
|
|
47
324
|
* `Node#attribute_by_qualified_name(name)` and
|
|
48
325
|
`Node#attribute_value_by_qualified_name(name)`: the attribute whose
|
|
49
|
-
|
|
326
|
+
qualified name is exactly `name` — its node, and its value — or nil.
|
|
50
327
|
`#[]` cannot answer this: it also finds a prefixed attribute by its local
|
|
51
328
|
name (`svg_a["href"]` returns `xlink:href`), and it lower-cases what it looks
|
|
52
329
|
up (`el["DATA-X"]` finds `data-x`). The new match is byte-exact.
|
|
53
330
|
|
|
54
331
|
### Changed
|
|
55
332
|
|
|
56
|
-
*
|
|
333
|
+
* `Makiri::XML` follows the DOM namespace model. A node's namespace URI is
|
|
57
334
|
decided once — by the parser, or by the context it is first inserted into —
|
|
58
335
|
and does not change afterwards; the serializer emits whatever xmlns
|
|
59
336
|
declarations the output needs:
|
|
@@ -71,24 +348,24 @@
|
|
|
71
348
|
Nodes from the factories (`create_element` and friends) still take their
|
|
72
349
|
namespace from the context they are first inserted into.
|
|
73
350
|
|
|
74
|
-
*
|
|
351
|
+
* Inserting a node from another document adopts it. `add_child` / `before` /
|
|
75
352
|
`after` / `replace` bring the node over and remove it from the document it
|
|
76
353
|
came from, instead of copying it (`Makiri::XML`) or raising (`Makiri::HTML`).
|
|
77
354
|
A spliced fragment is left empty; a rejected insert leaves the source
|
|
78
355
|
document untouched.
|
|
79
356
|
|
|
80
|
-
The node handed back is a
|
|
357
|
+
The node handed back is a different object than the one passed in, so use
|
|
81
358
|
the return value afterwards rather than the argument. `Document#import_node`
|
|
82
359
|
is unchanged: it copies and leaves the source alone.
|
|
83
360
|
|
|
84
|
-
*
|
|
361
|
+
* XML serialization is capped at the nesting depth the parser accepts.
|
|
85
362
|
`#to_xml` and `#canonicalize` raise `Makiri::Error` past it, rather than
|
|
86
363
|
emitting XML that Makiri could not read back. A deeper tree is still fine to
|
|
87
364
|
hold, walk and query.
|
|
88
365
|
|
|
89
366
|
### Fixed
|
|
90
367
|
|
|
91
|
-
* XPath axes from an
|
|
368
|
+
* XPath axes from an attribute context node on the XML backend now follow
|
|
92
369
|
XPath 1.0 §2.2: `following-sibling` and `preceding-sibling` are empty, and
|
|
93
370
|
`following` / `preceding` exclude attribute nodes. They used to return the
|
|
94
371
|
element's later attributes.
|
|
@@ -294,7 +571,7 @@
|
|
|
294
571
|
|
|
295
572
|
### Added
|
|
296
573
|
|
|
297
|
-
*
|
|
574
|
+
* Native XML 1.0 reader + in-place editor - `Makiri::XML::Document.parse(source)`
|
|
298
575
|
/ `Makiri::XML(source)`. No libxml2: a strict, fail-closed parser builds its own
|
|
299
576
|
node arena (case- and namespace-preserving), queried by the native XPath engine.
|
|
300
577
|
* Strict & secure: fail-closed decode (bad UTF-8 / NUL -> `XML::SyntaxError`),
|
|
@@ -304,9 +581,9 @@
|
|
|
304
581
|
encoding is a fatal error, not a silent mis-decode.
|
|
305
582
|
* DoS-bounded by a single arena byte ceiling (default 256 MiB; raise per parse
|
|
306
583
|
with `max_bytes:`).
|
|
307
|
-
* `<!DOCTYPE>` recognized but
|
|
308
|
-
`XML::DocumentType`); zero entity/DTD I/O, so
|
|
309
|
-
structurally impossible
|
|
584
|
+
* `<!DOCTYPE>` recognized but not processed (`#internal_subset` ->
|
|
585
|
+
`XML::DocumentType`); zero entity/DTD I/O, so XXE and billion-laughs are
|
|
586
|
+
structurally impossible. Kept off the tree, as in libxml2.
|
|
310
587
|
* Read API mirrors Nokogiri: `#xpath` / `#at_xpath` (`{prefix => uri}`),
|
|
311
588
|
name/namespace readers, `#text`, `#[]`, traversal, and namespace introspection
|
|
312
589
|
(`Makiri::XML::Namespace`); `XPathContext` works over XML nodes too.
|
|
@@ -334,7 +611,7 @@
|
|
|
334
611
|
* `NodeSet#[]` accepts a `Range` or `start, length` (like `Array#[]`).
|
|
335
612
|
* `Node` / `NodeSet` / `Document` `#dup` / `#clone` now return real independent
|
|
336
613
|
copies (`#dup(0)` shallow; `#clone(freeze:)` honoured).
|
|
337
|
-
* A
|
|
614
|
+
* A frozen node is genuinely immutable - every mutator raises `FrozenError`.
|
|
338
615
|
|
|
339
616
|
### Changed
|
|
340
617
|
|
|
@@ -346,7 +623,7 @@
|
|
|
346
623
|
added `Node#cdata?`.
|
|
347
624
|
* Text-index range table uses `uint32` bounds (24 -> 16 B/entry; ~27% less retained
|
|
348
625
|
index, byte-identical text).
|
|
349
|
-
* Parsing
|
|
626
|
+
* Parsing honours the input String's encoding - Shift_JIS / EUC-JP / ... are now
|
|
350
627
|
transcoded to UTF-8 instead of mangled.
|
|
351
628
|
* Parsing skips its UTF-8 validation scan when the String's coderange already proves
|
|
352
629
|
it valid.
|
|
@@ -355,7 +632,7 @@
|
|
|
355
632
|
|
|
356
633
|
### Fixed
|
|
357
634
|
|
|
358
|
-
*
|
|
635
|
+
* Hardened the HTML/XML representation boundary. HTML (Lexbor) and XML (arena)
|
|
359
636
|
nodes are now distinct TypedData types, so the wrong representation raises
|
|
360
637
|
`TypeError` instead of corrupting memory:
|
|
361
638
|
* `Node#==` / `XPathContext#node=` with an XML `Document` no longer aborts the
|
|
@@ -434,12 +711,12 @@
|
|
|
434
711
|
## [0.1.0] - 2026-06-02
|
|
435
712
|
|
|
436
713
|
First public release. An HTML5 parser, a native XPath 1.0 query engine, and CSS
|
|
437
|
-
selectors for Ruby - built on vendored [Lexbor](https://lexbor.com/) with
|
|
438
|
-
libxml2 / libxslt dependency at any layer
|
|
714
|
+
selectors for Ruby - built on vendored [Lexbor](https://lexbor.com/) with no
|
|
715
|
+
libxml2 / libxslt dependency at any layer.
|
|
439
716
|
|
|
440
717
|
### Added
|
|
441
718
|
|
|
442
|
-
|
|
719
|
+
Parsing & DOM
|
|
443
720
|
|
|
444
721
|
* `Makiri::HTML` / `Makiri.parse` - HTML5 parsing via vendored, unpatched Lexbor,
|
|
445
722
|
with browser-compatible UTF-8 decoding (invalid bytes → U+FFFD; parsing never
|
|
@@ -454,7 +731,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
454
731
|
quirks_mode,internal_subset,errors}` and `Makiri::DocumentType#{public_id,
|
|
455
732
|
system_id,external_id}`.
|
|
456
733
|
|
|
457
|
-
|
|
734
|
+
XPath
|
|
458
735
|
|
|
459
736
|
* Native XPath 1.0 query engine (no libxml2/libxslt): `Node#{xpath,at_xpath}`
|
|
460
737
|
and `Makiri::XPathContext` (`evaluate`, namespace/variable binding, custom
|
|
@@ -462,7 +739,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
462
739
|
built-in functions with spec-faithful semantics (XML NCNames including
|
|
463
740
|
non-ASCII, node-set vs node-set comparisons per §3.4, document order per §5.1,
|
|
464
741
|
Unicode-aware `translate`/`substring`).
|
|
465
|
-
* Namespace matching is
|
|
742
|
+
* Namespace matching is strict by default (HTML5/WHATWG-faithful, like
|
|
466
743
|
browsers' `document.evaluate` and `Nokogiri::HTML5`); pass
|
|
467
744
|
`namespace_matching: :lax` for the namespace-agnostic, `Nokogiri::HTML`-style
|
|
468
745
|
match.
|
|
@@ -470,12 +747,12 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
470
747
|
(operation / recursion-depth / node-set / string-byte caps) that raise
|
|
471
748
|
`Makiri::XPath::LimitExceeded` on overrun.
|
|
472
749
|
|
|
473
|
-
|
|
750
|
+
CSS
|
|
474
751
|
|
|
475
752
|
* `Node#{css,at_css,matches?}` via Lexbor's selector engine (descendant-only,
|
|
476
753
|
document order). Malformed selectors raise `Makiri::CSS::SyntaxError`.
|
|
477
754
|
|
|
478
|
-
|
|
755
|
+
Mutation & serialization
|
|
479
756
|
|
|
480
757
|
* DOM mutation: `add_child`/`<<`, `add_previous_sibling`/`before`,
|
|
481
758
|
`add_next_sibling`/`after`, `remove`/`unlink`, `replace`; attribute `[]=` and
|
|
@@ -494,7 +771,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
494
771
|
`NodeSet#{|,+,&,-,css,xpath,search,at,last,remove}`, and `Element.new` /
|
|
495
772
|
`Text.new`.
|
|
496
773
|
|
|
497
|
-
|
|
774
|
+
Safety & concurrency
|
|
498
775
|
|
|
499
776
|
* UTF-8 text-input contract: HTML and fragment parsing are lenient (invalid
|
|
500
777
|
bytes → U+FFFD, never reject), while strings passed to the XPath / CSS /
|
|
@@ -505,7 +782,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
505
782
|
context across threads cannot corrupt memory. Fail-closed string caps and
|
|
506
783
|
iterative (non-recursive) tree walks resist stack-exhaustion DoS.
|
|
507
784
|
|
|
508
|
-
|
|
785
|
+
Performance (`rake bench`, vs Nokogiri/libxml2)
|
|
509
786
|
|
|
510
787
|
* Meets or beats Nokogiri on every benchmarked operation: parse ~3×, css ~12×,
|
|
511
788
|
at_css ~1000×, serialize ~4×, `//tag` ~3.4×, `[@attr='v']` predicate ~1.5×,
|
|
@@ -513,7 +790,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
513
790
|
a document element index (for `//tag`), a direct-attribute predicate fast
|
|
514
791
|
path, and a hashed per-evaluate string-value cache.
|
|
515
792
|
|
|
516
|
-
|
|
793
|
+
Tooling
|
|
517
794
|
|
|
518
795
|
* Vendored Lexbor as a git submodule (pinned v3.0.0, applied without patches).
|
|
519
796
|
Build hardening flags; AddressSanitizer+UBSan build (`rake sanitize`);
|