makiri 0.15.0 → 0.16.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 +27 -0
- data/ext/makiri/rust/build.rs +37 -0
- data/ext/makiri/rust/src/bridge/html.rs +47 -1
- data/ext/makiri/rust/src/bridge/ruby.rs +41 -0
- data/ext/makiri/rust/src/bridge/string.rs +24 -0
- data/ext/makiri/rust/src/bridge/wrapper.rs +53 -0
- data/ext/makiri/rust/src/bridge/xml.rs +30 -0
- data/ext/makiri/rust/src/child_index/tests.rs +407 -0
- data/ext/makiri/rust/src/child_index.rs +357 -0
- data/ext/makiri/rust/src/dom_rules/tests.rs +32 -0
- data/ext/makiri/rust/src/dom_rules.rs +15 -1
- data/ext/makiri/rust/src/glue/html_node/mod.rs +17 -2
- data/ext/makiri/rust/src/glue/html_node/read.rs +27 -28
- data/ext/makiri/rust/src/glue/node.rs +44 -3
- data/ext/makiri/rust/src/glue/xml_node/mod.rs +17 -2
- data/ext/makiri/rust/src/glue/xml_node/read.rs +30 -31
- data/ext/makiri/rust/src/init.rs +14 -0
- data/ext/makiri/rust/src/lexbor/adapter/html/mutate.rs +21 -3
- data/ext/makiri/rust/src/lib.rs +4 -0
- data/ext/makiri/rust/src/ptr_table.rs +14 -0
- data/ext/makiri/rust/src/xml/mutate/insert.rs +8 -0
- data/lib/makiri/version.rb +1 -1
- data/lib/makiri.rb +27 -8
- data/script/check_unsafe_boundaries.rb +43 -35
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 80c74b7d335f5f5f93f8476a8f99a8918560df8cf75f43a5a71d9b132e892873
|
|
4
|
+
data.tar.gz: d6593459917f22ceb301093d17b981a597d8be7d975d1fab1e4142f6cf17fcbc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efa98c3ca322d6a3ee141428c1f5e9c3e5b46fba9dde4d7ac75d7dcfdf27d4dd083ef46eed5fb8db21685396d86caeb58e1755f9dd5014d396674cc149378df9
|
|
7
|
+
data.tar.gz: 836d97359577dfcd4bd60a8de0dfcf355c596b95fa7fffd5e56a0a355bb4267f3141bf8786f4f905596d79bef44721eeb3bfe4f25fa97e489c5aa3319c9f7655
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.16.0] - 2026-10-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
* `Makiri::NATIVE_VERSION` and `Makiri::NATIVE_RUBY_API_VERSION`: the gem
|
|
8
|
+
version and Ruby `MAJOR.MINOR` the native extension was built as.
|
|
9
|
+
`require "makiri"` raises `LoadError` for an extension built for another
|
|
10
|
+
Ruby (refused by the extension itself, before it defines anything) or as
|
|
11
|
+
another makiri version (e.g. a stale `lib/makiri/makiri.bundle` in a
|
|
12
|
+
checkout).
|
|
13
|
+
* `Node#child_count`, `#child_at(i)`, `#element_child_count` and
|
|
14
|
+
`#element_child_at(i)` (HTML and XML): count and index a child list without
|
|
15
|
+
building `#children`. A loop over the indexes in order costs O(1) a step,
|
|
16
|
+
also while other lists are read in between. Any child-list edit in the
|
|
17
|
+
document resets that, so a loop that edits as it indexes walks from the
|
|
18
|
+
nearer end after each edit. `child_at` returns nil for a negative index or
|
|
19
|
+
one past the end.
|
|
20
|
+
* `Node#first_child` (an alias of `#child`) and `#last_child`.
|
|
21
|
+
* `Node#root_node`, DOM `getRootNode()`: the Document, a DocumentFragment, or
|
|
22
|
+
the top of a detached subtree. An Attr is its own root.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
* `#local_name`, `#prefix`, `#namespace_uri` and `#tag_name` return frozen,
|
|
27
|
+
interned Strings: every read of one name returns the same object. `#name`
|
|
28
|
+
still returns a new, mutable String.
|
|
29
|
+
|
|
3
30
|
## [0.15.0] - 2026-10-09
|
|
4
31
|
|
|
5
32
|
### Added
|
data/ext/makiri/rust/build.rs
CHANGED
|
@@ -41,6 +41,10 @@ fn main() {
|
|
|
41
41
|
println!("cargo:rerun-if-changed=build.rs");
|
|
42
42
|
println!("cargo:rerun-if-env-changed=MAKIRI_LEXBOR_INCLUDE");
|
|
43
43
|
|
|
44
|
+
if std::env::var_os("CARGO_FEATURE_RUBY").is_some() {
|
|
45
|
+
gem_version();
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
// Only a build that reads Lexbor's layout pays for this. Kani builds with
|
|
45
49
|
// `--no-default-features` and has no Lexbor headers to point at, which is
|
|
46
50
|
// the whole reason `rake kani` needs no `rake compile` first.
|
|
@@ -472,3 +476,36 @@ fn definition(text: &str, name: &str) -> Option<(String, String)> {
|
|
|
472
476
|
}
|
|
473
477
|
None
|
|
474
478
|
}
|
|
479
|
+
|
|
480
|
+
/// Embed the gem's version (`lib/makiri/version.rb`, the one place it is
|
|
481
|
+
/// written) as `MAKIRI_GEM_VERSION`, which `init` publishes as
|
|
482
|
+
/// `Makiri::NATIVE_VERSION` for `lib/makiri.rb` to check the loaded binary
|
|
483
|
+
/// against. Read from the source rather than passed in by extconf, so every
|
|
484
|
+
/// cargo build of the extension carries it and none can carry a stale one.
|
|
485
|
+
fn gem_version() {
|
|
486
|
+
let manifest = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
487
|
+
let path = manifest.join("../../../lib/makiri/version.rb");
|
|
488
|
+
println!("cargo:rerun-if-changed={}", path.display());
|
|
489
|
+
let source = std::fs::read_to_string(&path)
|
|
490
|
+
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
|
|
491
|
+
/* `VERSION = "x"` or `VERSION = 'x'`, a trailing `.freeze` or comment
|
|
492
|
+
* allowed: the literal is what is between the first quote and its match. */
|
|
493
|
+
let version = source
|
|
494
|
+
.lines()
|
|
495
|
+
.find_map(|line| {
|
|
496
|
+
let rest = line.trim().strip_prefix("VERSION")?.trim_start();
|
|
497
|
+
let rest = rest.strip_prefix('=')?.trim_start();
|
|
498
|
+
let quote = rest.chars().next().filter(|c| *c == '"' || *c == '\'')?;
|
|
499
|
+
let body = &rest[1..];
|
|
500
|
+
body.find(quote).map(|end| &body[..end])
|
|
501
|
+
})
|
|
502
|
+
.filter(|v| !v.is_empty())
|
|
503
|
+
.unwrap_or_else(|| {
|
|
504
|
+
panic!(
|
|
505
|
+
"build.rs reads the gem version from a `VERSION = \"x.y.z\"` line in {}, \
|
|
506
|
+
and found none",
|
|
507
|
+
path.display()
|
|
508
|
+
)
|
|
509
|
+
});
|
|
510
|
+
println!("cargo:rustc-env=MAKIRI_GEM_VERSION={version}");
|
|
511
|
+
}
|
|
@@ -17,12 +17,14 @@ use crate::init::{
|
|
|
17
17
|
CLASS_HTML_PROCESSING_INSTRUCTION, CLASS_HTML_TEXT, CLASS_XML_DOCUMENT,
|
|
18
18
|
};
|
|
19
19
|
use crate::lexbor::adapter::html::{
|
|
20
|
-
ForeignNode, HtmlNode, HtmlNodeKey, HtmlNodeMut, Insertion, NodeType, Place, RawDoc,
|
|
20
|
+
ForeignNode, HtmlNode, HtmlNodeKey, HtmlNodeMut, HtmlTree, Insertion, NodeType, Place, RawDoc,
|
|
21
|
+
RawNode,
|
|
21
22
|
};
|
|
22
23
|
use crate::lexbor::fragment::import_with_fixup;
|
|
23
24
|
|
|
24
25
|
use crate::bridge::string::{RubyData, RubyText};
|
|
25
26
|
use crate::bridge::wrapper::*;
|
|
27
|
+
use crate::child_index::{ChildList, Children, TokenTree};
|
|
26
28
|
use crate::lexbor::adapter::html::{HtmlDoc, HtmlElementMut};
|
|
27
29
|
use crate::lexbor::adapter::AdapterOom;
|
|
28
30
|
|
|
@@ -241,6 +243,50 @@ pub fn wrap_node(node: Option<HtmlNode<'_>>, document: Value) -> Result<Option<V
|
|
|
241
243
|
.transpose()
|
|
242
244
|
}
|
|
243
245
|
|
|
246
|
+
/* ------------------------------------------------------------------ *
|
|
247
|
+
* indexing a child list *
|
|
248
|
+
* ------------------------------------------------------------------ */
|
|
249
|
+
|
|
250
|
+
/// The Lexbor tree for [`crate::child_index`]: a node's pointer is its token.
|
|
251
|
+
/// The links are [`HtmlTree`]'s, the raw ones `#children` follows, so the two
|
|
252
|
+
/// agree - a `<template>`'s own (empty) child list included.
|
|
253
|
+
impl<'d> TokenTree for HtmlTree<'d> {
|
|
254
|
+
fn token(&self, n: HtmlNode<'d>) -> usize {
|
|
255
|
+
RawNode::from(n).as_ptr() as usize
|
|
256
|
+
}
|
|
257
|
+
fn node_of(&self, token: usize) -> Option<HtmlNode<'d>> {
|
|
258
|
+
let raw = RawNode::from_ptr(token as *mut core::ffi::c_void)?;
|
|
259
|
+
// SAFETY: `child_index` asks only for a token it recorded for a list
|
|
260
|
+
// of this document, so by `ChildPositionMemo`'s invariant it names a
|
|
261
|
+
// node that is still a child there - in the document the receiver
|
|
262
|
+
// keeps alive for `'d`. Lexbor detaches rather than frees, so it was
|
|
263
|
+
// never released.
|
|
264
|
+
Some(unsafe { raw.as_node() })
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// The tree every HTML receiver lends its child lists, as `&'static`: a
|
|
269
|
+
/// `const` item is what may be borrowed for that long (the type holds
|
|
270
|
+
/// nothing).
|
|
271
|
+
const HTML_TREE: HtmlTree<'static> = HtmlTree::new();
|
|
272
|
+
|
|
273
|
+
impl ChildListHost for HtmlSelf {
|
|
274
|
+
type Tree<'a> = HtmlTree<'a>;
|
|
275
|
+
fn document(&self) -> Value {
|
|
276
|
+
self.document
|
|
277
|
+
}
|
|
278
|
+
fn children(&self, list: ChildList) -> Children<'_, HtmlTree<'_>> {
|
|
279
|
+
Children {
|
|
280
|
+
tree: &HTML_TREE,
|
|
281
|
+
parent: self.node(),
|
|
282
|
+
list,
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
fn wrap_child(&self, n: Option<HtmlNode<'_>>) -> Result<Option<Value>, Error> {
|
|
286
|
+
wrap_node(n, self.document)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
244
290
|
/* ------------------------------------------------------------------ *
|
|
245
291
|
* resolving a long-lived key *
|
|
246
292
|
* ------------------------------------------------------------------ */
|
|
@@ -20,6 +20,47 @@ pub use rb_sys::{ID, VALUE};
|
|
|
20
20
|
|
|
21
21
|
/* The two conveniences every bridge and glue module shares. */
|
|
22
22
|
|
|
23
|
+
/// The `MAJOR.MINOR` of the Ruby whose headers this extension was compiled
|
|
24
|
+
/// against - its ABI, since a teeny release keeps it. Published as
|
|
25
|
+
/// `Makiri::NATIVE_RUBY_API_VERSION`.
|
|
26
|
+
pub fn ruby_api_version() -> String {
|
|
27
|
+
format!(
|
|
28
|
+
"{}.{}",
|
|
29
|
+
rb_sys::RUBY_API_VERSION_MAJOR,
|
|
30
|
+
rb_sys::RUBY_API_VERSION_MINOR
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// `Err(LoadError)` when the running Ruby's API `MAJOR.MINOR` is not the one
|
|
35
|
+
/// this extension was compiled for.
|
|
36
|
+
///
|
|
37
|
+
/// The FIRST thing `Init_makiri` does. A release Ruby does not check
|
|
38
|
+
/// `ruby_abi_version` itself, and under macOS's `-undefined dynamic_lookup`
|
|
39
|
+
/// a binary built for another Ruby loads and then misreads the interpreter's
|
|
40
|
+
/// structs - defining one class could already corrupt it, so the check
|
|
41
|
+
/// cannot wait for `lib/makiri.rb`. It reads the running Ruby's exported
|
|
42
|
+
/// `ruby_api_version` (three ints, whose layout no Ruby changes) and nothing
|
|
43
|
+
/// else; only a mismatch goes on to build the exception.
|
|
44
|
+
pub fn check_running_ruby_api(ruby: &Ruby) -> Result<(), Error> {
|
|
45
|
+
// SAFETY: an immutable `const int[3]` libruby exports, read by value.
|
|
46
|
+
let running = unsafe { (rb_sys::ruby_api_version[0], rb_sys::ruby_api_version[1]) };
|
|
47
|
+
let built = (
|
|
48
|
+
rb_sys::RUBY_API_VERSION_MAJOR as core::ffi::c_int,
|
|
49
|
+
rb_sys::RUBY_API_VERSION_MINOR as core::ffi::c_int,
|
|
50
|
+
);
|
|
51
|
+
if running == built {
|
|
52
|
+
return Ok(());
|
|
53
|
+
}
|
|
54
|
+
Err(Error::new(
|
|
55
|
+
ruby.exception_load_error(),
|
|
56
|
+
format!(
|
|
57
|
+
"makiri's native extension was built for Ruby {}.{}, but this is Ruby {}.{}; \
|
|
58
|
+
rebuild it (`bundle exec rake clean compile` in a checkout, or reinstall the gem)",
|
|
59
|
+
built.0, built.1, running.0, running.1
|
|
60
|
+
),
|
|
61
|
+
))
|
|
62
|
+
}
|
|
63
|
+
|
|
23
64
|
/// `Makiri::Error` - the one definition; the modules that each kept a private
|
|
24
65
|
/// copy of this now import it.
|
|
25
66
|
pub fn error_class() -> magnus::ExceptionClass {
|
|
@@ -411,6 +411,30 @@ pub unsafe fn ruby_str_from_utf8(bytes: &[u8]) -> VALUE {
|
|
|
411
411
|
rb_sys::rb_utf8_str_new(bytes.as_ptr() as *const c_char, bytes.len() as c_long)
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
/// A DOM name as a Ruby String: frozen and deduplicated, tagged UTF-8 -
|
|
415
|
+
/// Ruby's own `-"div"`, so every read of one name answers the same object and
|
|
416
|
+
/// allocates nothing once Ruby has it.
|
|
417
|
+
///
|
|
418
|
+
/// THE contract for the name readers of both representations: `local_name`,
|
|
419
|
+
/// `prefix`, `namespace_uri` and `tag_name` answer through this, and only
|
|
420
|
+
/// `#name` (Nokogiri's, which callers may edit) answers a fresh String.
|
|
421
|
+
/// Character data - text, attribute values - is never interned.
|
|
422
|
+
///
|
|
423
|
+
/// Safe to call with any bytes: `rb_enc_interned_str` copies them, and bytes
|
|
424
|
+
/// that were not UTF-8 would make a wrong String, never a memory error. Every
|
|
425
|
+
/// caller passes a document's name, which is UTF-8 by the text-input contract.
|
|
426
|
+
pub fn dom_name_str(bytes: &[u8]) -> Value {
|
|
427
|
+
// SAFETY: a slice's pointer and length, read for the call; the encoding
|
|
428
|
+
// is Ruby's static UTF-8 one. The result is a live String.
|
|
429
|
+
unsafe {
|
|
430
|
+
crate::bridge::ruby::value(rb_sys::rb_enc_interned_str(
|
|
431
|
+
bytes.as_ptr() as *const c_char,
|
|
432
|
+
bytes.len() as c_long,
|
|
433
|
+
rb_sys::rb_utf8_encoding(),
|
|
434
|
+
))
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
414
438
|
/* ---- the strict text contract ---- */
|
|
415
439
|
|
|
416
440
|
/// Check `bytes` against the strict contract, returning the specific
|
|
@@ -18,6 +18,8 @@ use magnus::{Error, Value};
|
|
|
18
18
|
|
|
19
19
|
use crate::bridge::ruby::{value, VALUE};
|
|
20
20
|
use crate::bridge::typed::{Hooks, Marker, Relocator, TypedType};
|
|
21
|
+
use crate::child_index::{ChildList, ChildPositionMemo, Children, TokenTree};
|
|
22
|
+
use crate::dom_rules::Tree;
|
|
21
23
|
use crate::falloc::{MapInsert, Reserve};
|
|
22
24
|
use crate::init::{RbConst, CLASS_DOCUMENT};
|
|
23
25
|
use crate::lexbor::adapter::html::{HtmlDoc, HtmlNodeKey, RawDoc, RawNode};
|
|
@@ -387,6 +389,9 @@ pub struct DocData {
|
|
|
387
389
|
/// Boxed and optional so a document nobody navigates never allocates a
|
|
388
390
|
/// cache, and its wrapper stays one pointer wide.
|
|
389
391
|
nodes: Option<Box<NodeCache>>,
|
|
392
|
+
/// Where `child_count` / `child_at` last were ([`ChildPositionMemo`]).
|
|
393
|
+
/// Allocates nothing until the first of them records.
|
|
394
|
+
child_memo: ChildPositionMemo,
|
|
390
395
|
}
|
|
391
396
|
|
|
392
397
|
impl DocData {
|
|
@@ -482,6 +487,7 @@ impl Hooks for DocData {
|
|
|
482
487
|
core::mem::size_of::<DocData>()
|
|
483
488
|
.saturating_add(self.external_bytes())
|
|
484
489
|
.saturating_add(self.nodes.as_ref().map_or(0, |c| c.memsize()))
|
|
490
|
+
.saturating_add(self.child_memo.memsize())
|
|
485
491
|
}
|
|
486
492
|
}
|
|
487
493
|
|
|
@@ -642,6 +648,7 @@ impl DocumentShell {
|
|
|
642
648
|
errors: QFALSE,
|
|
643
649
|
report: ExternalReport::default(),
|
|
644
650
|
nodes: None,
|
|
651
|
+
child_memo: ChildPositionMemo::default(),
|
|
645
652
|
},
|
|
646
653
|
|d| d.errors = errors.as_raw(),
|
|
647
654
|
)
|
|
@@ -1028,6 +1035,52 @@ pub fn attribute_version(rb_doc: Value) -> Result<u64, Error> {
|
|
|
1028
1035
|
Ok(DOC_TYPE.get(&rb_doc)?.edits.attribute_version)
|
|
1029
1036
|
}
|
|
1030
1037
|
|
|
1038
|
+
/* ---- the child-position memo ---- */
|
|
1039
|
+
|
|
1040
|
+
/// Run `f` over the document's [`ChildPositionMemo`] and its current
|
|
1041
|
+
/// `tree_version`, which every use of the memo must pass it - the memo keeps
|
|
1042
|
+
/// its own invariant from that (`crate::child_index`).
|
|
1043
|
+
///
|
|
1044
|
+
/// `f` runs inside the document's borrow, so it must not run Ruby; a walk of
|
|
1045
|
+
/// the child links does not.
|
|
1046
|
+
fn with_child_memo<R>(rb_doc: Value, f: impl FnOnce(&mut ChildPositionMemo, u64) -> R) -> R {
|
|
1047
|
+
with_doc_data_known(rb_doc, |d| f(&mut d.child_memo, d.edits.tree_version))
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/// A receiver whose child lists `#child_count` / `#child_at` and their
|
|
1051
|
+
/// element twins read - an HTML or XML node or Document. A representation
|
|
1052
|
+
/// says which tree and node it is and how a child is wrapped; counting and
|
|
1053
|
+
/// indexing are written once, here, and the four Ruby methods once, over
|
|
1054
|
+
/// this, in `glue::node`.
|
|
1055
|
+
pub trait ChildListHost: magnus::TryConvert {
|
|
1056
|
+
type Tree<'a>: TokenTree
|
|
1057
|
+
where
|
|
1058
|
+
Self: 'a;
|
|
1059
|
+
|
|
1060
|
+
/// The Document whose memo the lists use.
|
|
1061
|
+
fn document(&self) -> Value;
|
|
1062
|
+
/// The receiver's `list`.
|
|
1063
|
+
fn children(&self, list: ChildList) -> Children<'_, Self::Tree<'_>>;
|
|
1064
|
+
/// A child of the receiver as its Ruby wrapper; None is nil.
|
|
1065
|
+
fn wrap_child(&self, n: Option<<Self::Tree<'_> as Tree>::Node>)
|
|
1066
|
+
-> Result<Option<Value>, Error>;
|
|
1067
|
+
|
|
1068
|
+
/// How many children are in `list`.
|
|
1069
|
+
fn child_count(&self, list: ChildList) -> usize {
|
|
1070
|
+
with_child_memo(self.document(), |memo, version| {
|
|
1071
|
+
self.children(list).count(memo, version)
|
|
1072
|
+
})
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/// The wrapped child at `index` in `list`, or nil past the end.
|
|
1076
|
+
fn child_at(&self, list: ChildList, index: usize) -> Result<Option<Value>, Error> {
|
|
1077
|
+
let found = with_child_memo(self.document(), |memo, version| {
|
|
1078
|
+
self.children(list).at(memo, version, index)
|
|
1079
|
+
});
|
|
1080
|
+
self.wrap_child(found)
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1031
1084
|
/* ---- the document's mutation gate ---- */
|
|
1032
1085
|
|
|
1033
1086
|
/// `Err(Makiri::Error)` while an evaluation with a handler is reading `rb_doc`.
|
|
@@ -29,6 +29,7 @@ use crate::bridge::wrapper::{
|
|
|
29
29
|
ensure_document_mutable, node_repr, DocKind, DocumentShell, NodeRepr,
|
|
30
30
|
};
|
|
31
31
|
use crate::bridge::xml_decode::xml_decode_input_value;
|
|
32
|
+
use crate::child_index::{ChildList, Children, TokenTree};
|
|
32
33
|
use crate::init::{CLASS_NODE, CLASS_XML_DOCUMENT, EXC_XML_LIMIT_EXCEEDED, EXC_XML_SYNTAX_ERROR};
|
|
33
34
|
use crate::init::{
|
|
34
35
|
CLASS_XML_ATTR, CLASS_XML_CDATA_SECTION, CLASS_XML_COMMENT, CLASS_XML_DOCUMENT_FRAGMENT,
|
|
@@ -86,6 +87,35 @@ impl crate::bridge::wrapper::NodeHandleSource for NodeId {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/// The arena for [`crate::child_index`]: a node's token is its `NodeId`'s,
|
|
91
|
+
/// which the arena never reuses. The links are the arena's `Tree` ones,
|
|
92
|
+
/// which `#children` follows (attributes are not on them).
|
|
93
|
+
impl TokenTree for XmlDoc {
|
|
94
|
+
fn token(&self, n: NodeId) -> usize {
|
|
95
|
+
n.to_token()
|
|
96
|
+
}
|
|
97
|
+
fn node_of(&self, token: usize) -> Option<NodeId> {
|
|
98
|
+
NodeId::from_token(token)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
impl ChildListHost for XmlSelf {
|
|
103
|
+
type Tree<'a> = XmlDoc;
|
|
104
|
+
fn document(&self) -> Value {
|
|
105
|
+
self.document
|
|
106
|
+
}
|
|
107
|
+
fn children(&self, list: ChildList) -> Children<'_, XmlDoc> {
|
|
108
|
+
Children {
|
|
109
|
+
tree: self.doc_ref(),
|
|
110
|
+
parent: self.id,
|
|
111
|
+
list,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
fn wrap_child(&self, n: Option<NodeId>) -> Result<Option<Value>, Error> {
|
|
115
|
+
n.map(|id| wrap_xml_node(id, self.document)).transpose()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
89
119
|
/// The arena node behind a wrapper.
|
|
90
120
|
///
|
|
91
121
|
/// An XML Document resolves to its arena's DOCUMENT node. Anything else goes
|