makiri 0.10.0.rc1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +137 -29
- data/NOKOGIRI_DIFFERENCES.md +177 -0
- data/README.md +9 -151
- data/Rakefile +14 -0
- data/ext/makiri/rust/build.rs +161 -26
- data/ext/makiri/rust/clippy.toml +26 -11
- data/ext/makiri/rust/fuzz/fuzz_targets/common.rs +1 -1
- data/ext/makiri/rust/fuzz/fuzz_targets/css.rs +11 -6
- data/ext/makiri/rust/fuzz/fuzz_targets/html.rs +1 -1
- data/ext/makiri/rust/fuzz/fuzz_targets/html_xpath.rs +4 -6
- data/ext/makiri/rust/src/bridge/doc.rs +48 -197
- data/ext/makiri/rust/src/bridge/fragment.rs +150 -71
- data/ext/makiri/rust/src/bridge/gvl.rs +25 -3
- data/ext/makiri/rust/src/bridge/html.rs +534 -0
- data/ext/makiri/rust/src/bridge/mod.rs +26 -19
- data/ext/makiri/rust/src/bridge/node_set.rs +235 -398
- data/ext/makiri/rust/src/bridge/ruby.rs +74 -169
- data/ext/makiri/rust/src/bridge/string.rs +162 -150
- data/ext/makiri/rust/src/bridge/typed.rs +279 -20
- data/ext/makiri/rust/src/bridge/wrapper.rs +627 -0
- data/ext/makiri/rust/src/bridge/xml.rs +367 -640
- data/ext/makiri/rust/src/bridge/xml_decode.rs +63 -200
- data/ext/makiri/rust/src/bridge/xpath/context_object.rs +299 -0
- data/ext/makiri/rust/src/bridge/xpath/handler.rs +307 -0
- data/ext/makiri/rust/src/bridge/xpath/mod.rs +307 -0
- data/ext/makiri/rust/src/cbuf.rs +8 -0
- data/ext/makiri/rust/src/css/build.rs +38 -49
- data/ext/makiri/rust/src/css/lower.rs +255 -309
- data/ext/makiri/rust/src/css/mod.rs +62 -40
- data/ext/makiri/rust/src/cutf8.rs +33 -0
- data/ext/makiri/rust/src/falloc/cstr.rs +8 -11
- data/ext/makiri/rust/src/falloc/inject.rs +16 -16
- data/ext/makiri/rust/src/falloc/mod.rs +97 -91
- data/ext/makiri/rust/src/falloc/raw.rs +15 -46
- data/ext/makiri/rust/src/glue/css.rs +21 -0
- data/ext/makiri/rust/src/glue/{doc.rs → html_doc.rs} +51 -32
- data/ext/makiri/rust/src/glue/html_node/css.rs +115 -0
- data/ext/makiri/rust/src/glue/html_node/mod.rs +36 -50
- data/ext/makiri/rust/src/glue/html_node/mutate.rs +296 -11
- data/ext/makiri/rust/src/glue/html_node/read.rs +34 -139
- data/ext/makiri/rust/src/glue/html_node/serialize.rs +80 -0
- data/ext/makiri/rust/src/glue/mod.rs +36 -34
- data/ext/makiri/rust/src/glue/node.rs +5 -10
- data/ext/makiri/rust/src/glue/node_set.rs +166 -8
- data/ext/makiri/rust/src/glue/query.rs +274 -0
- data/ext/makiri/rust/src/glue/stylesheet.rs +170 -0
- data/ext/makiri/rust/src/glue/xml_doc.rs +154 -0
- data/ext/makiri/rust/src/glue/xml_node/css.rs +109 -0
- data/ext/makiri/rust/src/glue/xml_node/mod.rs +78 -103
- data/ext/makiri/rust/src/glue/xml_node/mutate.rs +329 -14
- data/ext/makiri/rust/src/glue/xml_node/ns.rs +76 -177
- data/ext/makiri/rust/src/glue/xml_node/read.rs +188 -133
- data/ext/makiri/rust/src/glue/xml_node/serialize.rs +12 -4
- data/ext/makiri/rust/src/glue/xml_node/strings.rs +25 -0
- data/ext/makiri/rust/src/glue/xpath_context.rs +98 -0
- data/ext/makiri/rust/src/gvl.rs +162 -0
- data/ext/makiri/rust/src/init.rs +18 -25
- data/ext/makiri/rust/src/{lexbor_abi.rs → lexbor/abi.rs} +16 -173
- data/ext/makiri/rust/src/lexbor/adapter/arena_bytes.rs +79 -0
- data/ext/makiri/rust/src/lexbor/adapter/cross_import.rs +110 -243
- data/ext/makiri/rust/src/lexbor/adapter/dom_index.rs +46 -121
- data/ext/makiri/rust/src/lexbor/adapter/html/build.rs +385 -0
- data/ext/makiri/rust/src/lexbor/adapter/html/mod.rs +961 -0
- data/ext/makiri/rust/src/lexbor/adapter/html/mutate.rs +321 -0
- data/ext/makiri/rust/src/lexbor/adapter/mod.rs +8 -5
- data/ext/makiri/rust/src/lexbor/adapter/post_parse.rs +80 -275
- data/ext/makiri/rust/src/lexbor/adapter/source_loc.rs +28 -29
- data/ext/makiri/rust/src/lexbor/adapter/text_index.rs +67 -143
- data/ext/makiri/rust/src/lexbor/adapter/utf8_input.rs +44 -65
- data/ext/makiri/rust/src/lexbor/contains_guard.rs +299 -0
- data/ext/makiri/rust/src/lexbor/css_engine.rs +209 -0
- data/ext/makiri/rust/src/lexbor/css_parser.rs +277 -261
- data/ext/makiri/rust/src/lexbor/fragment.rs +111 -183
- data/ext/makiri/rust/src/lexbor/mod.rs +21 -12
- data/ext/makiri/rust/src/lexbor/selectors.rs +242 -313
- data/ext/makiri/rust/src/lexbor/serialize.rs +24 -39
- data/ext/makiri/rust/src/lexbor/stylesheet.rs +92 -230
- data/ext/makiri/rust/src/lexbor/tests.rs +233 -0
- data/ext/makiri/rust/src/lexbor/xpath.rs +80 -38
- data/ext/makiri/rust/src/lib.rs +16 -8
- data/ext/makiri/rust/src/limits.rs +10 -0
- data/ext/makiri/rust/src/ptr_table.rs +228 -0
- data/ext/makiri/rust/src/rust_tests.rs +45 -45
- data/ext/makiri/rust/src/text.rs +3 -0
- data/ext/makiri/rust/src/token.rs +11 -0
- data/ext/makiri/rust/src/xml/arena.rs +231 -99
- data/ext/makiri/rust/src/xml/chars/expand.rs +159 -0
- data/ext/makiri/rust/src/xml/chars/mod.rs +181 -0
- data/ext/makiri/rust/src/xml/dom_name.rs +84 -0
- data/ext/makiri/rust/src/xml/encoding_sniff.rs +245 -0
- data/ext/makiri/rust/src/xml/index.rs +90 -67
- data/ext/makiri/rust/src/xml/mod.rs +2 -2
- data/ext/makiri/rust/src/xml/model.rs +14 -14
- data/ext/makiri/rust/src/xml/mutate/attr.rs +154 -0
- data/ext/makiri/rust/src/xml/mutate/copy.rs +206 -0
- data/ext/makiri/rust/src/xml/mutate/edit.rs +100 -0
- data/ext/makiri/rust/src/xml/mutate/factory.rs +138 -0
- data/ext/makiri/rust/src/xml/mutate/insert.rs +488 -0
- data/ext/makiri/rust/src/xml/mutate/mod.rs +66 -0
- data/ext/makiri/rust/src/xml/mutate/ns.rs +174 -0
- data/ext/makiri/rust/src/xml/qname.rs +61 -39
- data/ext/makiri/rust/src/xml/selftest.rs +884 -887
- data/ext/makiri/rust/src/xml/serialize/c14n.rs +243 -0
- data/ext/makiri/rust/src/xml/serialize/mod.rs +110 -0
- data/ext/makiri/rust/src/xml/serialize/out.rs +89 -0
- data/ext/makiri/rust/src/xml/serialize/xml.rs +511 -0
- data/ext/makiri/rust/src/xml/tree/cursor.rs +367 -0
- data/ext/makiri/rust/src/xml/tree/decl.rs +102 -0
- data/ext/makiri/rust/src/xml/tree/dtd.rs +442 -0
- data/ext/makiri/rust/src/xml/tree/mod.rs +710 -0
- data/ext/makiri/rust/src/xml/tree/scope.rs +93 -0
- data/ext/makiri/rust/src/xml/xpath.rs +39 -22
- data/ext/makiri/rust/src/xpath/abi.rs +2 -3
- data/ext/makiri/rust/src/xpath/ast_ops.rs +1 -32
- data/ext/makiri/rust/src/xpath/attr_pred.rs +20 -25
- data/ext/makiri/rust/src/xpath/axis.rs +37 -50
- data/ext/makiri/rust/src/xpath/ctx.rs +82 -14
- data/ext/makiri/rust/src/xpath/dom.rs +67 -28
- data/ext/makiri/rust/src/xpath/eval.rs +132 -182
- data/ext/makiri/rust/src/xpath/funcs/ext.rs +115 -0
- data/ext/makiri/rust/src/xpath/{funcs.rs → funcs/mod.rs} +280 -218
- data/ext/makiri/rust/src/xpath/lex.rs +3 -3
- data/ext/makiri/rust/src/xpath/limits.rs +1 -1
- data/ext/makiri/rust/src/xpath/mod.rs +3 -3
- data/ext/makiri/rust/src/xpath/msg.rs +56 -18
- data/ext/makiri/rust/src/xpath/nodetest.rs +155 -159
- data/ext/makiri/rust/src/xpath/number.rs +2 -2
- data/ext/makiri/rust/src/xpath/order.rs +163 -127
- data/ext/makiri/rust/src/xpath/parse.rs +5 -5
- data/ext/makiri/rust/src/xpath/step_index.rs +33 -59
- data/ext/makiri/rust/src/xpath/str_cache.rs +97 -0
- data/ext/makiri/rust/src/xpath/tests.rs +13 -6
- data/ext/makiri/rust/src/xpath/value.rs +68 -110
- data/ext/makiri/rust/src/xpath/verify.rs +2 -3
- data/lib/makiri/node.rb +2 -10
- data/lib/makiri/version.rb +1 -1
- data/lib/makiri/xml/namespace.rb +17 -0
- data/lib/makiri/xml/node_methods.rb +8 -1
- data/lib/makiri.rb +1 -0
- data/script/check_alloc_failures.rb +48 -0
- data/script/check_unsafe_boundaries.rb +170 -60
- data/vendor/lexbor/CMakeLists.txt +23 -16
- data/vendor/lexbor/config.cmake +3 -3
- data/vendor/lexbor/lexbor.pc.in +2 -2
- data/vendor/lexbor/source/lexbor/core/conv.c +13 -8
- data/vendor/lexbor/source/lexbor/css/selectors/pseudo_state.c +3 -0
- data/vendor/lexbor/source/lexbor/css/selectors/selector.c +30 -3
- data/vendor/lexbor/source/lexbor/css/selectors/state.c +34 -5
- data/vendor/lexbor/source/lexbor/css/syntax/parser.c +38 -12
- data/vendor/lexbor/source/lexbor/css/syntax/state.c +100 -115
- data/vendor/lexbor/source/lexbor/dom/interfaces/attr_const.h +74 -39
- data/vendor/lexbor/source/lexbor/dom/interfaces/attr_res.h +150 -34
- data/vendor/lexbor/source/lexbor/dom/interfaces/character_data.c +1 -1
- data/vendor/lexbor/source/lexbor/encoding/encode.c +16 -3
- data/vendor/lexbor/source/lexbor/html/attribute_steps_res.h +4 -2
- data/vendor/lexbor/source/lexbor/html/element_steps_res.h +4 -2
- data/vendor/lexbor/source/lexbor/html/encoding.c +12 -3
- data/vendor/lexbor/source/lexbor/html/interface_res.h +36 -2
- data/vendor/lexbor/source/lexbor/html/serialize.c +1 -1
- data/vendor/lexbor/source/lexbor/html/serialize_ext.c +1 -1
- data/vendor/lexbor/source/lexbor/html/tag_res.h +11 -2
- data/vendor/lexbor/source/lexbor/html/token.c +1 -0
- data/vendor/lexbor/source/lexbor/html/token.h +17 -0
- data/vendor/lexbor/source/lexbor/html/tokenizer/error.c +5 -1
- data/vendor/lexbor/source/lexbor/html/tokenizer/error.h +8 -0
- data/vendor/lexbor/source/lexbor/html/tokenizer/state.c +361 -5
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/after_after_body.c +12 -1
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/after_after_frameset.c +12 -1
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/after_body.c +11 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/after_frameset.c +10 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/after_head.c +10 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/before_head.c +10 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/before_html.c +11 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/foreign_content.c +17 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_body.c +32 -20
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_column_group.c +17 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_frameset.c +10 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_head.c +10 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_head_noscript.c +1 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_table.c +18 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_template.c +1 -0
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/initial.c +11 -0
- data/vendor/lexbor/source/lexbor/html/tree/open_elements_res.h +4 -2
- data/vendor/lexbor/source/lexbor/html/tree.c +42 -0
- data/vendor/lexbor/source/lexbor/html/tree.h +5 -0
- data/vendor/lexbor/source/lexbor/ns/res.h +38 -38
- data/vendor/lexbor/source/lexbor/selectors/selectors.c +83 -2
- data/vendor/lexbor/source/lexbor/style/attribute_steps_res.h +4 -2
- data/vendor/lexbor/source/lexbor/style/dom/interfaces/document.c +4 -0
- data/vendor/lexbor/source/lexbor/style/element_steps_res.h +4 -2
- data/vendor/lexbor/source/lexbor/style/tree/open_elements_res.h +4 -2
- data/vendor/lexbor/source/lexbor/tag/const.h +203 -202
- data/vendor/lexbor/source/lexbor/tag/res.h +136 -134
- data/vendor/lexbor/source/lexbor/unicode/idna.c +19 -9
- data/vendor/lexbor/source/lexbor/url/base.h +1 -1
- data/vendor/lexbor/source/lexbor/url/url.c +147 -52
- data/vendor/lexbor/source/lexbor/url/url.h +122 -0
- data/vendor/lexbor/source/lexbor/utils/http.c +22 -1
- metadata +52 -22
- data/ext/makiri/rust/src/bridge/lexbor.rs +0 -1258
- data/ext/makiri/rust/src/bridge/selectors.rs +0 -166
- data/ext/makiri/rust/src/bridge/serialize.rs +0 -92
- data/ext/makiri/rust/src/bridge/xpath.rs +0 -1154
- data/ext/makiri/rust/src/falloc/calloc.rs +0 -16
- data/ext/makiri/rust/src/glue/xml.rs +0 -506
- data/ext/makiri/rust/src/glue/xml_node/abi.rs +0 -74
- data/ext/makiri/rust/src/glue/xpath.rs +0 -17
- data/ext/makiri/rust/src/lexbor/adapter/html.rs +0 -1535
- data/ext/makiri/rust/src/lexbor/ffi.rs +0 -26
- data/ext/makiri/rust/src/xml/api.rs +0 -18
- data/ext/makiri/rust/src/xml/chars.rs +0 -287
- data/ext/makiri/rust/src/xml/mutate.rs +0 -1120
- data/ext/makiri/rust/src/xml/parse.rs +0 -46
- data/ext/makiri/rust/src/xml/serialize.rs +0 -715
- data/ext/makiri/rust/src/xml/tree.rs +0 -963
- data/ext/makiri/rust/src/xpath/runtime_abi/cache.rs +0 -172
- data/ext/makiri/rust/src/xpath/runtime_abi.rs +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6163794fb07f39d4476e91a5d9db367d82b02888e167bc41435bb12de32de49
|
|
4
|
+
data.tar.gz: 4c075f42cbdf69b39b9783e3ad66bd09acb48f380447e8e80a47402dcdd427e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d6f1d154116c49d5b42e57e08d584781af42c2d0a02a6d3a1d6073659437cd7b0920a30a42451fedc70b173de242c9738a34ea4fd8d582dd3d199094bbfe10ab
|
|
7
|
+
data.tar.gz: 7278778cf2e98166e03ef79364ef9a4b0feec3b410949c2e4889f82f899594da8ba492ac00a6d75de2ee7a553430e5dc69a9af52a41292ab86249af830dea7fe
|
data/CHANGELOG.md
CHANGED
|
@@ -1,34 +1,142 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.0] - 2026-09-22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
* A frozen node now raises `FrozenError` when it is the ARGUMENT of a tree
|
|
8
|
+
mutation, not only the receiver: `b.add_child(a)` relinks `a` exactly as
|
|
9
|
+
`a.remove` does. Both `Makiri::XML` and `Makiri::HTML`. A fragment argument
|
|
10
|
+
splices its children, which have no wrapper of their own to check.
|
|
11
|
+
* A `Makiri::XML` mutation that exceeds the document's own `max_bytes` /
|
|
12
|
+
`max_nodes` now raises `Makiri::XML::LimitExceeded` instead of reporting the
|
|
13
|
+
refusal as out of memory.
|
|
14
|
+
* Inserting a `DocumentFragment` is all or nothing on `add_child`, `before` and
|
|
15
|
+
`after`, as it already was on `replace`: a child the rules refuse no longer
|
|
16
|
+
leaves the earlier ones linked in a document the caller was told had not
|
|
17
|
+
changed.
|
|
18
|
+
* A rejected `Makiri::XML::Document#fragment` no longer charges the document for
|
|
19
|
+
the nodes it discarded. 100k rejected fragments grew a `<r/>` document to
|
|
20
|
+
77 MB; it is now 516 bytes.
|
|
21
|
+
* A failed `Makiri::XML` parse reports the FIRST failure for all four kinds
|
|
22
|
+
(`syntax` was sticky while `limit` and `unsupported` overwrote each other).
|
|
23
|
+
* `Makiri::XML#to_xml`'s serializer allocates fallibly again, so running out of
|
|
24
|
+
memory raises instead of aborting the process.
|
|
25
|
+
* `:lexbor-contains()` now rejects an argument the bundled CSS parser does not
|
|
26
|
+
take, the way any unknown pseudo-class is rejected: `Makiri::CSS::SyntaxError`
|
|
27
|
+
from `#css` / `#at_css` / `#matches?`, and a `:bad_style` rule from
|
|
28
|
+
`Makiri::Lexbor::CSS.parse_stylesheet`. Well-formed uses are unchanged.
|
|
29
|
+
|
|
30
|
+
### Performance
|
|
31
|
+
|
|
32
|
+
* `Makiri::XML#to_xml` plans namespaces from a binding stack instead of
|
|
33
|
+
re-walking each ancestor's attribute list, which cost O(depth^2 x attributes).
|
|
34
|
+
403 KB of nested prefixed attributes took 4.88s and now takes 0.001s. A
|
|
35
|
+
crafted document fails closed with `Makiri::Error` ("namespace planning
|
|
36
|
+
exceeded its step budget") rather than running on.
|
|
37
|
+
* Setting an attribute on a `Makiri::XML` element walks the attribute list once
|
|
38
|
+
instead of twice (4096 attributes: 47ms -> 27ms).
|
|
39
|
+
|
|
40
|
+
## [0.10.0.rc2] - 2026-09-20
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
* `XPathContext.new` now registers namespace bindings passed via
|
|
45
|
+
`prefix: uri` (previously ignored).
|
|
46
|
+
* `#css`, `#at_css`, and `#matches?` accept `(selector, namespaces = nil)`
|
|
47
|
+
on HTML documents for consistency with XML.
|
|
48
|
+
* Standardized error message format for rejected CSS selectors across XML
|
|
49
|
+
and HTML: `"<reason>: <selector>"`.
|
|
50
|
+
* Unified argument handling for `#xpath` and `#at_xpath` between HTML and XML.
|
|
51
|
+
* HTML supports per-query namespace bindings and keyword prefixes
|
|
52
|
+
(e.g., `s: uri`).
|
|
53
|
+
* XML supports custom-function handlers.
|
|
54
|
+
* XML nodes now support HTML-style node readers and aliases
|
|
55
|
+
(`#first_element_child`, `#next_element`, `#tag_name`, `#attr`,
|
|
56
|
+
`#node_name`, etc.).
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
* Updated vendored Lexbor to `v3.0.0-66`.
|
|
61
|
+
* Parsed `<?php ... ?>` tags in HTML input as `ProcessingInstruction` nodes
|
|
62
|
+
instead of comments, adhering to the HTML Standard.
|
|
63
|
+
* Vendored Lexbor updated from `v3.0.0-25` to `v3.0.0-66` (`05b5d37`), for an
|
|
64
|
+
out-of-bounds write in `lxb_dom_character_data_replace` reachable through
|
|
65
|
+
`#content=`, UTF-16 surrogate rejection, exact `meta` charset matching and
|
|
66
|
+
buffer-capacity fixes. (v3.0.1 is not usable: it lacks the two CSS-selector
|
|
67
|
+
fixes Makiri upstreamed.)
|
|
68
|
+
* `<?php ... ?>` in HTML input is now a `ProcessingInstruction` node rather
|
|
69
|
+
than a Comment, serializes as `<?target data?>`, and `<?` at end of input is
|
|
70
|
+
ignored - the HTML Standard's processing-instruction tokens, which Lexbor now
|
|
71
|
+
implements. `Nokogiri::HTML5` still answers with a comment.
|
|
72
|
+
* `Makiri::XML#matches?` tests the node locally via tree walking instead of
|
|
73
|
+
performing a full-document search.
|
|
74
|
+
* `Makiri::XML::Namespace` is now an immutable `Data` object defined in Ruby.
|
|
75
|
+
* `XML::Node#value` returns its text content (or attribute value for
|
|
76
|
+
attributes), aligning with HTML node behavior.
|
|
77
|
+
* XPath over HTML now handles element/attribute names case-insensitively
|
|
78
|
+
and properly handles `xmlns` attributes according to browser specs.
|
|
79
|
+
* `Makiri::XML` checks the internal DTD subset for well-formedness and
|
|
80
|
+
raises `Makiri::XML::SyntaxError` if unsupported constructs (e.g., attribute
|
|
81
|
+
defaults, entity references) would alter the document tree.
|
|
82
|
+
* Documents with `version="1.x"` are now accepted and parsed as XML 1.0.
|
|
83
|
+
* Processing instruction (PI) targets containing colons (e.g., `<?a:b?>`)
|
|
84
|
+
are now rejected per Namespaces in XML specs.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
* `Makiri::XML::DocumentType#prefix` now returns `nil` instead of
|
|
89
|
+
the PUBLIC ID.
|
|
90
|
+
* `Makiri::XML#last_element_child` now correctly returns an `Element`
|
|
91
|
+
instead of arbitrary child nodes (like comments or text).
|
|
92
|
+
* `Element#local_name` and `local-name()` in XPath preserve camelCase
|
|
93
|
+
for SVG/MathML elements (e.g., `foreignObject`).
|
|
94
|
+
* `inner_html=` and `outer_html=` are now atomic operations to prevent
|
|
95
|
+
leaving the DOM in a corrupted state on failure.
|
|
96
|
+
* XPath over HTML assigns empty/correct namespaces to attributes instead of
|
|
97
|
+
inheriting from their parent element.
|
|
98
|
+
* Corrected `lang()` evaluation to check the nearest language attribute
|
|
99
|
+
hierarchically.
|
|
100
|
+
* Fixed `namespace_matching: :lax` on XML to be strict, aligning with
|
|
101
|
+
`Nokogiri::XML` / `libxml2` behavior.
|
|
102
|
+
* Moving an HTML node to another document now correctly removes it
|
|
103
|
+
from the source document's indexes.
|
|
104
|
+
* Prevented XPath custom handlers from creating new nodes on the document
|
|
105
|
+
being evaluated.
|
|
106
|
+
* Fixed CSS universal selectors (`p|*`, `|*`) on XML to respect namespace
|
|
107
|
+
boundaries.
|
|
108
|
+
* CSS column combinator (`a || b`) on XML now raises
|
|
109
|
+
`Makiri::CSS::SyntaxError` instead of being misparsed as descendant selector.
|
|
110
|
+
|
|
3
111
|
## [0.10.0.rc1] - 2026-09-19
|
|
4
112
|
|
|
5
113
|
### Changed
|
|
6
114
|
|
|
7
|
-
*
|
|
115
|
+
* The native extension is rewritten in Rust. The C glue, the XPath engine,
|
|
8
116
|
the XML reader and the CSS lowering are now one Rust crate; the only C left is
|
|
9
117
|
the vendored Lexbor, still unpatched. The Ruby API is unchanged, and answers
|
|
10
118
|
were checked against the C build's recorded output as well as the existing
|
|
11
119
|
differential suites against Nokogiri.
|
|
12
120
|
|
|
13
|
-
*
|
|
121
|
+
* Installing from source needs a Rust toolchain. `cargo` (stable) and
|
|
14
122
|
libclang are required alongside CMake, and `rb_sys` becomes a runtime
|
|
15
123
|
dependency of the source gem, because its `extconf.rb` runs at install time.
|
|
16
124
|
The precompiled platform gems need none of this and do not depend on
|
|
17
125
|
`rb_sys`.
|
|
18
|
-
*
|
|
126
|
+
* Faster. Makiri now beats both Nokogiri and nokolexbor on every
|
|
19
127
|
`rake bench` row, parse included - previously ~1.25x slower than nokolexbor.
|
|
20
128
|
Source locations are stamped on the first `#line` or mutation rather than
|
|
21
129
|
during every parse, and the vendored Lexbor is built with link-time
|
|
22
130
|
optimization where the linker supports it (macOS; Linux with clang,
|
|
23
131
|
llvm-ar and lld; not Windows).
|
|
24
|
-
*
|
|
132
|
+
* An internal failure is an exception, not a crash. It used to end the
|
|
25
133
|
host process with SIGABRT. Now it unwinds on the thread that ran the call:
|
|
26
134
|
`ensure` blocks run and the process keeps working. On entry points that
|
|
27
135
|
handle input (parse, XPath, CSS, serialization, text) it is the new
|
|
28
|
-
|
|
136
|
+
`Makiri::InternalError`, which descends from `Exception`, not
|
|
29
137
|
`StandardError`, so a bare `rescue => e` does not swallow it; elsewhere it
|
|
30
138
|
is Ruby's `fatal`.
|
|
31
|
-
*
|
|
139
|
+
* An XPath handler may not modify the document being evaluated. Every
|
|
32
140
|
mutator on that document raises `Makiri::Error` while an evaluation with a
|
|
33
141
|
handler runs, because the evaluator holds names and values from it for the
|
|
34
142
|
whole walk.
|
|
@@ -46,14 +154,14 @@
|
|
|
46
154
|
|
|
47
155
|
* `Node#attribute_by_qualified_name(name)` and
|
|
48
156
|
`Node#attribute_value_by_qualified_name(name)`: the attribute whose
|
|
49
|
-
|
|
157
|
+
qualified name is exactly `name` — its node, and its value — or nil.
|
|
50
158
|
`#[]` cannot answer this: it also finds a prefixed attribute by its local
|
|
51
159
|
name (`svg_a["href"]` returns `xlink:href`), and it lower-cases what it looks
|
|
52
160
|
up (`el["DATA-X"]` finds `data-x`). The new match is byte-exact.
|
|
53
161
|
|
|
54
162
|
### Changed
|
|
55
163
|
|
|
56
|
-
*
|
|
164
|
+
* `Makiri::XML` follows the DOM namespace model. A node's namespace URI is
|
|
57
165
|
decided once — by the parser, or by the context it is first inserted into —
|
|
58
166
|
and does not change afterwards; the serializer emits whatever xmlns
|
|
59
167
|
declarations the output needs:
|
|
@@ -71,24 +179,24 @@
|
|
|
71
179
|
Nodes from the factories (`create_element` and friends) still take their
|
|
72
180
|
namespace from the context they are first inserted into.
|
|
73
181
|
|
|
74
|
-
*
|
|
182
|
+
* Inserting a node from another document adopts it. `add_child` / `before` /
|
|
75
183
|
`after` / `replace` bring the node over and remove it from the document it
|
|
76
184
|
came from, instead of copying it (`Makiri::XML`) or raising (`Makiri::HTML`).
|
|
77
185
|
A spliced fragment is left empty; a rejected insert leaves the source
|
|
78
186
|
document untouched.
|
|
79
187
|
|
|
80
|
-
The node handed back is a
|
|
188
|
+
The node handed back is a different object than the one passed in, so use
|
|
81
189
|
the return value afterwards rather than the argument. `Document#import_node`
|
|
82
190
|
is unchanged: it copies and leaves the source alone.
|
|
83
191
|
|
|
84
|
-
*
|
|
192
|
+
* XML serialization is capped at the nesting depth the parser accepts.
|
|
85
193
|
`#to_xml` and `#canonicalize` raise `Makiri::Error` past it, rather than
|
|
86
194
|
emitting XML that Makiri could not read back. A deeper tree is still fine to
|
|
87
195
|
hold, walk and query.
|
|
88
196
|
|
|
89
197
|
### Fixed
|
|
90
198
|
|
|
91
|
-
* XPath axes from an
|
|
199
|
+
* XPath axes from an attribute context node on the XML backend now follow
|
|
92
200
|
XPath 1.0 §2.2: `following-sibling` and `preceding-sibling` are empty, and
|
|
93
201
|
`following` / `preceding` exclude attribute nodes. They used to return the
|
|
94
202
|
element's later attributes.
|
|
@@ -294,7 +402,7 @@
|
|
|
294
402
|
|
|
295
403
|
### Added
|
|
296
404
|
|
|
297
|
-
*
|
|
405
|
+
* Native XML 1.0 reader + in-place editor - `Makiri::XML::Document.parse(source)`
|
|
298
406
|
/ `Makiri::XML(source)`. No libxml2: a strict, fail-closed parser builds its own
|
|
299
407
|
node arena (case- and namespace-preserving), queried by the native XPath engine.
|
|
300
408
|
* Strict & secure: fail-closed decode (bad UTF-8 / NUL -> `XML::SyntaxError`),
|
|
@@ -304,9 +412,9 @@
|
|
|
304
412
|
encoding is a fatal error, not a silent mis-decode.
|
|
305
413
|
* DoS-bounded by a single arena byte ceiling (default 256 MiB; raise per parse
|
|
306
414
|
with `max_bytes:`).
|
|
307
|
-
* `<!DOCTYPE>` recognized but
|
|
308
|
-
`XML::DocumentType`); zero entity/DTD I/O, so
|
|
309
|
-
structurally impossible
|
|
415
|
+
* `<!DOCTYPE>` recognized but not processed (`#internal_subset` ->
|
|
416
|
+
`XML::DocumentType`); zero entity/DTD I/O, so XXE and billion-laughs are
|
|
417
|
+
structurally impossible. Kept off the tree, as in libxml2.
|
|
310
418
|
* Read API mirrors Nokogiri: `#xpath` / `#at_xpath` (`{prefix => uri}`),
|
|
311
419
|
name/namespace readers, `#text`, `#[]`, traversal, and namespace introspection
|
|
312
420
|
(`Makiri::XML::Namespace`); `XPathContext` works over XML nodes too.
|
|
@@ -334,7 +442,7 @@
|
|
|
334
442
|
* `NodeSet#[]` accepts a `Range` or `start, length` (like `Array#[]`).
|
|
335
443
|
* `Node` / `NodeSet` / `Document` `#dup` / `#clone` now return real independent
|
|
336
444
|
copies (`#dup(0)` shallow; `#clone(freeze:)` honoured).
|
|
337
|
-
* A
|
|
445
|
+
* A frozen node is genuinely immutable - every mutator raises `FrozenError`.
|
|
338
446
|
|
|
339
447
|
### Changed
|
|
340
448
|
|
|
@@ -346,7 +454,7 @@
|
|
|
346
454
|
added `Node#cdata?`.
|
|
347
455
|
* Text-index range table uses `uint32` bounds (24 -> 16 B/entry; ~27% less retained
|
|
348
456
|
index, byte-identical text).
|
|
349
|
-
* Parsing
|
|
457
|
+
* Parsing honours the input String's encoding - Shift_JIS / EUC-JP / ... are now
|
|
350
458
|
transcoded to UTF-8 instead of mangled.
|
|
351
459
|
* Parsing skips its UTF-8 validation scan when the String's coderange already proves
|
|
352
460
|
it valid.
|
|
@@ -355,7 +463,7 @@
|
|
|
355
463
|
|
|
356
464
|
### Fixed
|
|
357
465
|
|
|
358
|
-
*
|
|
466
|
+
* Hardened the HTML/XML representation boundary. HTML (Lexbor) and XML (arena)
|
|
359
467
|
nodes are now distinct TypedData types, so the wrong representation raises
|
|
360
468
|
`TypeError` instead of corrupting memory:
|
|
361
469
|
* `Node#==` / `XPathContext#node=` with an XML `Document` no longer aborts the
|
|
@@ -434,12 +542,12 @@
|
|
|
434
542
|
## [0.1.0] - 2026-06-02
|
|
435
543
|
|
|
436
544
|
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
|
|
545
|
+
selectors for Ruby - built on vendored [Lexbor](https://lexbor.com/) with no
|
|
546
|
+
libxml2 / libxslt dependency at any layer.
|
|
439
547
|
|
|
440
548
|
### Added
|
|
441
549
|
|
|
442
|
-
|
|
550
|
+
Parsing & DOM
|
|
443
551
|
|
|
444
552
|
* `Makiri::HTML` / `Makiri.parse` - HTML5 parsing via vendored, unpatched Lexbor,
|
|
445
553
|
with browser-compatible UTF-8 decoding (invalid bytes → U+FFFD; parsing never
|
|
@@ -454,7 +562,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
454
562
|
quirks_mode,internal_subset,errors}` and `Makiri::DocumentType#{public_id,
|
|
455
563
|
system_id,external_id}`.
|
|
456
564
|
|
|
457
|
-
|
|
565
|
+
XPath
|
|
458
566
|
|
|
459
567
|
* Native XPath 1.0 query engine (no libxml2/libxslt): `Node#{xpath,at_xpath}`
|
|
460
568
|
and `Makiri::XPathContext` (`evaluate`, namespace/variable binding, custom
|
|
@@ -462,7 +570,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
462
570
|
built-in functions with spec-faithful semantics (XML NCNames including
|
|
463
571
|
non-ASCII, node-set vs node-set comparisons per §3.4, document order per §5.1,
|
|
464
572
|
Unicode-aware `translate`/`substring`).
|
|
465
|
-
* Namespace matching is
|
|
573
|
+
* Namespace matching is strict by default (HTML5/WHATWG-faithful, like
|
|
466
574
|
browsers' `document.evaluate` and `Nokogiri::HTML5`); pass
|
|
467
575
|
`namespace_matching: :lax` for the namespace-agnostic, `Nokogiri::HTML`-style
|
|
468
576
|
match.
|
|
@@ -470,12 +578,12 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
470
578
|
(operation / recursion-depth / node-set / string-byte caps) that raise
|
|
471
579
|
`Makiri::XPath::LimitExceeded` on overrun.
|
|
472
580
|
|
|
473
|
-
|
|
581
|
+
CSS
|
|
474
582
|
|
|
475
583
|
* `Node#{css,at_css,matches?}` via Lexbor's selector engine (descendant-only,
|
|
476
584
|
document order). Malformed selectors raise `Makiri::CSS::SyntaxError`.
|
|
477
585
|
|
|
478
|
-
|
|
586
|
+
Mutation & serialization
|
|
479
587
|
|
|
480
588
|
* DOM mutation: `add_child`/`<<`, `add_previous_sibling`/`before`,
|
|
481
589
|
`add_next_sibling`/`after`, `remove`/`unlink`, `replace`; attribute `[]=` and
|
|
@@ -494,7 +602,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
494
602
|
`NodeSet#{|,+,&,-,css,xpath,search,at,last,remove}`, and `Element.new` /
|
|
495
603
|
`Text.new`.
|
|
496
604
|
|
|
497
|
-
|
|
605
|
+
Safety & concurrency
|
|
498
606
|
|
|
499
607
|
* UTF-8 text-input contract: HTML and fragment parsing are lenient (invalid
|
|
500
608
|
bytes → U+FFFD, never reject), while strings passed to the XPath / CSS /
|
|
@@ -505,7 +613,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
505
613
|
context across threads cannot corrupt memory. Fail-closed string caps and
|
|
506
614
|
iterative (non-recursive) tree walks resist stack-exhaustion DoS.
|
|
507
615
|
|
|
508
|
-
|
|
616
|
+
Performance (`rake bench`, vs Nokogiri/libxml2)
|
|
509
617
|
|
|
510
618
|
* Meets or beats Nokogiri on every benchmarked operation: parse ~3×, css ~12×,
|
|
511
619
|
at_css ~1000×, serialize ~4×, `//tag` ~3.4×, `[@attr='v']` predicate ~1.5×,
|
|
@@ -513,7 +621,7 @@ libxml2 / libxslt dependency at any layer**.
|
|
|
513
621
|
a document element index (for `//tag`), a direct-attribute predicate fast
|
|
514
622
|
path, and a hashed per-evaluate string-value cache.
|
|
515
623
|
|
|
516
|
-
|
|
624
|
+
Tooling
|
|
517
625
|
|
|
518
626
|
* Vendored Lexbor as a git submodule (pinned v3.0.0, applied without patches).
|
|
519
627
|
Build hardening flags; AddressSanitizer+UBSan build (`rake sanitize`);
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Differences from Nokogiri
|
|
2
|
+
|
|
3
|
+
Makiri targets a Nokogiri-compatible API, but a few behaviours differ. Where
|
|
4
|
+
they do, Makiri usually follows the web platform - the WHATWG standards and
|
|
5
|
+
what browsers do - rather than libxml2. Detailed, test-backed notes live in
|
|
6
|
+
`spec/conformance/README.md`.
|
|
7
|
+
|
|
8
|
+
## XPath
|
|
9
|
+
|
|
10
|
+
* The `namespace::` axis is not implemented
|
|
11
|
+
* It raises `Makiri::Error` rather than returning a silently-empty result.
|
|
12
|
+
* Nokogiri (libxml2) supports it (for `<svg>` in HTML it yields the `xml` and `svg` namespace nodes).
|
|
13
|
+
For an element's namespace use `namespace-uri()` / `local-name()`, which are implemented.
|
|
14
|
+
* Unprefixed name tests are namespace-strict by default (HTML5/WHATWG-faithful, like browsers' `document.evaluate` and `Nokogiri::HTML5`)
|
|
15
|
+
* `//div` matches, but foreign elements need a registered prefix (`//svg:path`).
|
|
16
|
+
Pass `namespace_matching: :lax` to `Node#xpath` / `XPathContext.new` for the
|
|
17
|
+
namespace-agnostic match where `//path` finds an SVG element (the
|
|
18
|
+
`Nokogiri::HTML`/libxml2-HTML4 behaviour). Lax means "as Nokogiri does", so
|
|
19
|
+
on an XML document it changes nothing: `Nokogiri::XML` is namespace-strict too.
|
|
20
|
+
* `namespace-uri()` of an HTML element returns the XHTML URI (DOM-correct, as browsers report)
|
|
21
|
+
* `Nokogiri::HTML5` returns `""`.
|
|
22
|
+
* Name tests fold ASCII case on HTML elements, like browsers (WPT `domxpath`).
|
|
23
|
+
The HTML Standard's XPath section does not ask for this - it only sets the
|
|
24
|
+
default element namespace - so here Makiri follows the browsers over the text.
|
|
25
|
+
* `//DiV` matches `<div>` and `//div[@Id]` its `id`; SVG / MathML names compare
|
|
26
|
+
exactly (`//*[@refX]`, not `@refx`). Only ASCII folds: `Ø` still differs from `ø`.
|
|
27
|
+
This holds in `namespace_matching: :lax` too.
|
|
28
|
+
* `Nokogiri::HTML5` is case-sensitive there.
|
|
29
|
+
* A foreign element's namespace declarations are not attributes
|
|
30
|
+
* `<svg xmlns="...">` has no `@xmlns` for `//*[@xmlns]` or `@*`, as in browsers
|
|
31
|
+
and in XPath's data model. An `xmlns` on an HTML element is an ordinary
|
|
32
|
+
attribute and stays visible.
|
|
33
|
+
|
|
34
|
+
## XML
|
|
35
|
+
|
|
36
|
+
* `Makiri::XML` is XML 1.0 (Fifth Edition) only and non-validating.
|
|
37
|
+
* A `version="1.x"` document is read as XML 1.0, as §2.8 says a 1.0 processor
|
|
38
|
+
does; a construct only XML 1.1 allows still fails.
|
|
39
|
+
* The internal DTD subset is checked but never applied. §5.1 requires even
|
|
40
|
+
a non-validating parser to apply its attribute defaults and entities, so a
|
|
41
|
+
document whose DTD would change the tree is refused
|
|
42
|
+
(`Makiri::XML::SyntaxError`, "unsupported DTD construct") rather than parsed
|
|
43
|
+
without them: an attribute default (`"d"` or `#FIXED "d"`), a non-CDATA
|
|
44
|
+
attribute type (`ID`, `NMTOKEN`, an enumeration, ... - they normalize the
|
|
45
|
+
value), a parameter-entity reference, or a reference to an entity the DTD
|
|
46
|
+
declares. Declarations that change nothing (`<!ELEMENT>`, `CDATA #IMPLIED` /
|
|
47
|
+
`#REQUIRED`, unused entity declarations, notations) are accepted.
|
|
48
|
+
Nokogiri/libxml2 by default parses such documents and leaves the defaults and
|
|
49
|
+
entities out (its `DTDATTR` / `NOENT` options apply them).
|
|
50
|
+
* External entities and the external subset are never fetched (no I/O), as
|
|
51
|
+
§5.1 allows a non-validating parser.
|
|
52
|
+
* Mutation supports in-place edits, the node factories, fragments
|
|
53
|
+
(`Document#fragment` / `DocumentFragment.parse`), node insertion, and building
|
|
54
|
+
a document from scratch (`XML::Document.new` + `#root=`); only handing a raw
|
|
55
|
+
markup string straight to `#add_child` is unsupported (parse it into a fragment
|
|
56
|
+
first). (`#to_xml` serialization is supported; HTML serialization - `to_html`
|
|
57
|
+
/ `inner_html` / `outer_html` - is not.)
|
|
58
|
+
* A processing-instruction target with a colon can be created but not written.
|
|
59
|
+
* The parser rejects `<?a:b ...?>`, as Nokogiri does: Namespaces in XML §7
|
|
60
|
+
requires every Name other than element and attribute names to be an NCName.
|
|
61
|
+
* `create_processing_instruction("a:b", ...)` succeeds, as DOM
|
|
62
|
+
`createProcessingInstruction` does, but `#to_xml` / `#canonicalize` then raise,
|
|
63
|
+
as DOM Parsing's well-formed serializer does. Nokogiri writes `<?a:b ...?>`.
|
|
64
|
+
* `#freeze` on a node is ENFORCED: a frozen node's mutators raise `FrozenError`,
|
|
65
|
+
and so does passing a frozen node as the argument of an insertion, which
|
|
66
|
+
relinks it. Nokogiri reports `frozen?` but every mutator still mutates. The
|
|
67
|
+
check reaches the nodes the caller named; a fragment argument splices its
|
|
68
|
+
children, and those cannot be checked, because frozen-ness is a property of a
|
|
69
|
+
Ruby object and the arena keeps no map from a node back to its wrapper.
|
|
70
|
+
|
|
71
|
+
* A node's namespace URI is its identity, not something re-derived from the
|
|
72
|
+
declarations around it - the WHATWG DOM model, measured against Chrome 152
|
|
73
|
+
(`DOMParser` + `XMLSerializer`).
|
|
74
|
+
* Moving a node under an element that binds its prefix to a different URI does
|
|
75
|
+
not change `namespace_uri`; the serializer emits the declaration the
|
|
76
|
+
output needs (`<p:x xmlns:p="urn:a"/>`), and nothing when the destination
|
|
77
|
+
already agrees. libxml2 keeps the URI on an in-document move but does *not*
|
|
78
|
+
emit the declaration, so Nokogiri's tree and its own output disagree there.
|
|
79
|
+
* `#to_xml` on a node below the root is self-contained: it declares the
|
|
80
|
+
prefixes its subtree uses, so the output re-parses to the same namespaces
|
|
81
|
+
standing alone. Nokogiri omits them, and its subtree output does not
|
|
82
|
+
round-trip.
|
|
83
|
+
* Where one prefix would have to mean two things at once, the serializer
|
|
84
|
+
invents one (`ns1`, `ns2`, ...) rather than shadow the other, as browsers do.
|
|
85
|
+
* An element in no namespace stays that way under a default namespace,
|
|
86
|
+
serialized as `xmlns=""`.
|
|
87
|
+
* Nodes from the factories (`create_element` and friends) still take their
|
|
88
|
+
namespace from the context they are first inserted into, so a subtree can be
|
|
89
|
+
built detached and attached afterwards. Only later moves carry.
|
|
90
|
+
* Inserting a node from another document adopts it (`add_child` / `before` /
|
|
91
|
+
`after` / `replace`): it is brought over and taken out of the document it came
|
|
92
|
+
from, as `appendChild` does in the DOM and in both Chrome and Nokogiri.
|
|
93
|
+
* Each arena owns its own nodes, so the node cannot be relinked across them: it
|
|
94
|
+
is copied here and removed there. The one visible difference from Nokogiri
|
|
95
|
+
and browsers is that the node handed back is a different object than the
|
|
96
|
+
one passed in - use the return value afterwards, not the argument.
|
|
97
|
+
* `Document#import_node` is the copy: it leaves the source alone, like DOM
|
|
98
|
+
`importNode`.
|
|
99
|
+
* Otherwise the parsed tree is byte-identical to `Nokogiri::XML`'s (verified by
|
|
100
|
+
the property-based differential), including namespaces, prolog/epilog comments
|
|
101
|
+
and PIs, and adjacent-CDATA coalescing.
|
|
102
|
+
|
|
103
|
+
## HTML parsing
|
|
104
|
+
|
|
105
|
+
* `<?php ... ?>` in HTML input is a **ProcessingInstruction** node; `#to_html`
|
|
106
|
+
writes it back as `<?php ... ?>`, and `<?` at end of input is ignored.
|
|
107
|
+
* The HTML Standard added processing-instruction tokens and tree-construction
|
|
108
|
+
rules for them; Makiri follows them through Lexbor. `Nokogiri::HTML5` (gumbo)
|
|
109
|
+
still produces the older bogus comment (`<!--?php ... ?-->`), and
|
|
110
|
+
`Nokogiri::HTML` (libxml2) its own comment.
|
|
111
|
+
|
|
112
|
+
## CSS
|
|
113
|
+
|
|
114
|
+
* Most jQuery/Nokogiri CSS extensions are not supported (`:gt`, `:lt`, `:eq`, `:first`, ...)
|
|
115
|
+
* Makiri uses Lexbor's selector engine, which is standards-based apart from one
|
|
116
|
+
text-containment extension. Use XPath (`xpath("//p[contains(., 'x')]")`) or
|
|
117
|
+
Enumerable (`css('li')[1]`) for the rest.
|
|
118
|
+
Standard Level-4 selectors (`:is` / `:where` / `:has`) are supported; some of which Nokogiri rejects.
|
|
119
|
+
* `:lexbor-contains("text")` is supported (on both HTML and XML) - Lexbor's
|
|
120
|
+
spelling of the jQuery `:contains()` substring filter, matching an element
|
|
121
|
+
whose text contains the string; append ` i` (`:lexbor-contains("text" i)`)
|
|
122
|
+
for an ASCII case-insensitive match. (Nokogiri's name `:contains` is not an
|
|
123
|
+
alias.) Like Lexbor's matcher, it tests the element's immediate child text
|
|
124
|
+
nodes (not the deep string-value), so HTML and XML agree; on XML it lowers
|
|
125
|
+
to XPath `child::text()[contains(., "text")]`.
|
|
126
|
+
* Untyped `:*-of-type` (`:first-of-type`, `:nth-of-type(an+b)`, ... with no type
|
|
127
|
+
selector) is supported and correct on both HTML and XML - the "type" is the
|
|
128
|
+
element's own expanded name.
|
|
129
|
+
* Nokogiri (XML and HTML5) mistranslates these to first-/only-child
|
|
130
|
+
(`//*[position()=1]` / `//*[last()=1]`), so it under-matches; Makiri matches
|
|
131
|
+
Lexbor's HTML matcher.
|
|
132
|
+
* HTML CSS takes a `{prefix => uri}` Hash (`css(selector, ns)`) but does not
|
|
133
|
+
resolve prefixes against it: Lexbor's matcher matches a prefixed type selector
|
|
134
|
+
loosely, so `svg|path`, `|path` and `path` all find the SVG element whatever
|
|
135
|
+
is bound. `Nokogiri::HTML5` honours the binding (a wrong URI finds nothing).
|
|
136
|
+
* The Hash is accepted and unused rather than refused, so the `css(selector,
|
|
137
|
+
ns)` a caller writes for both representations works. Use `#xpath`, where a
|
|
138
|
+
prefix IS resolved against the bindings, when the namespace matters.
|
|
139
|
+
* `Makiri::XML` resolves CSS prefixes properly - it lowers the selector to the
|
|
140
|
+
XPath engine, which registers the bindings.
|
|
141
|
+
* `#matches?` answers for a DETACHED node (`document.create_element("p")
|
|
142
|
+
.matches?("p")` is true, on both representations). Nokogiri raises
|
|
143
|
+
`NoMethodError` there - it implements `#matches?` as a search from
|
|
144
|
+
`ancestors.last`, which a detached node does not have.
|
|
145
|
+
* * Type selectors are ASCII case-insensitive (CSS-correct for HTML; `LI` matches `<li>`)
|
|
146
|
+
* `Nokogiri::HTML5` is case-sensitive there.
|
|
147
|
+
|
|
148
|
+
## Serialization
|
|
149
|
+
|
|
150
|
+
* Comment data is written literally, as the WHATWG serialization algorithm
|
|
151
|
+
says and as browsers do: `comment.content = "a-->b"` serializes to
|
|
152
|
+
`<!--a-->b-->`, which re-parses as the comment `"a"` followed by text.
|
|
153
|
+
* `Nokogiri::HTML5` escapes it to `<!--a-->b-->` instead. That does not
|
|
154
|
+
round-trip either - comments do not decode entities, so the data comes back
|
|
155
|
+
as `"a-->b"`. Neither library round-trips this; Makiri matches Chrome.
|
|
156
|
+
* The same applies to the children of `style` / `script` / `xmp` / `iframe` /
|
|
157
|
+
`noembed` / `noframes` / `plaintext`, which the algorithm also writes
|
|
158
|
+
literally. `noscript` is escaped, because Makiri parses it with scripting
|
|
159
|
+
disabled (its children are elements, not raw text) and escaping is what makes
|
|
160
|
+
that round-trip; `Nokogiri::HTML5` writes it literally and contradicts its own
|
|
161
|
+
parser there.
|
|
162
|
+
|
|
163
|
+
## Text input (mutation APIs)
|
|
164
|
+
|
|
165
|
+
* Programmatic string arguments must be valid UTF-8 (invalid bytes raise
|
|
166
|
+
`Makiri::Error`, never silently repaired - unlike HTML *parsing*, which decodes
|
|
167
|
+
leniently to U+FFFD).
|
|
168
|
+
* An embedded NUL (U+0000) is accepted in HTML data-family content -
|
|
169
|
+
text/comment node content (`create_text_node`, `create_comment`, `content=`)
|
|
170
|
+
and attribute values (`[]=`, `set_attribute_ns`) - and stored/read back
|
|
171
|
+
verbatim, matching the WHATWG DOM / browsers (`document.createTextNode("\0")`).
|
|
172
|
+
It is still rejected in names, tag names, namespaces, PI target/data, CSS
|
|
173
|
+
selectors, and XPath expressions/variable names (a NUL there raises).
|
|
174
|
+
* On re-parse, the HTML tokenizer replaces a U+0000 in text/attributes with
|
|
175
|
+
U+FFFD (WHATWG), so a serialized-then-reparsed round-trip is not byte-identical.
|
|
176
|
+
* `Makiri::XML` rejects NUL everywhere: XML 1.0 has no legal U+0000 character,
|
|
177
|
+
so admitting it would produce non-well-formed XML.
|