makiri 0.12.1 → 0.13.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 +24 -0
- data/README.md +23 -1
- data/ext/makiri/rust/build.rs +7 -1
- data/ext/makiri/rust/src/bridge/html.rs +19 -1
- data/ext/makiri/rust/src/bridge/wrapper.rs +26 -0
- data/ext/makiri/rust/src/bridge/xml.rs +48 -2
- data/ext/makiri/rust/src/glue/html_doc.rs +4 -0
- data/ext/makiri/rust/src/glue/html_node/mod.rs +8 -1
- data/ext/makiri/rust/src/glue/html_node/mutate.rs +2 -1
- data/ext/makiri/rust/src/glue/html_node/read.rs +58 -1
- data/ext/makiri/rust/src/glue/node.rs +9 -0
- data/ext/makiri/rust/src/glue/xml_doc.rs +4 -0
- data/ext/makiri/rust/src/glue/xml_node/mod.rs +2 -0
- data/ext/makiri/rust/src/glue/xml_node/mutate.rs +7 -5
- data/ext/makiri/rust/src/glue/xml_node/read.rs +28 -0
- data/ext/makiri/rust/src/lexbor/adapter/html/attrs.rs +55 -10
- data/ext/makiri/rust/src/lexbor/adapter/html/build.rs +23 -2
- data/ext/makiri/rust/src/lexbor/adapter/html/mod.rs +54 -10
- data/lib/makiri/version.rb +1 -1
- data/script/check_unsafe_boundaries.rb +3 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48c6dfd55ed565531aea117967aee1f4b8f3405ff0e36ca8766ffa729a1ee27e
|
|
4
|
+
data.tar.gz: 1866e9eb174ea0a6428a8f1c81f65ed8756d61c2429de935876d60547a4bc6bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f847d9d772a2865bcd6e3f9b1626f0b67e4215d14d844b529bd194e9d287aa288cc2a555156799f842ac68fcc13405e1cfff317a506c2476b87eb24646f17c46
|
|
7
|
+
data.tar.gz: ee1fe65b9055dc2a9c11cdbbd789544ef72ef1bd2c8fa731ab0eab2f97173125ab3f96637da903d33df76dfa80e221dd0e11abeaa446f5683e1428cc7f6552af
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.0] - 2026-10-03
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
* `Document#tree_version` (HTML and XML): an Integer that increases whenever
|
|
8
|
+
a child list in the document changes - nodes added, removed or replaced,
|
|
9
|
+
`inner_html=`, `content=`, and both documents when a node moves between
|
|
10
|
+
them. Attribute changes leave it as it is, so it can key a cache of child
|
|
11
|
+
lists.
|
|
12
|
+
* `Element#attribute_value_ns(ns, local)` and `#attribute_node_ns(ns, local)`
|
|
13
|
+
(HTML and XML): the attribute's value or Attr node by namespace and local
|
|
14
|
+
name, like the DOM's `getAttributeNS` / `getAttributeNodeNS`. `nil` or `""`
|
|
15
|
+
means no namespace.
|
|
16
|
+
* `Element#set_loose_dom_attribute(name, value)` now works on HTML nodes too.
|
|
17
|
+
Like the DOM's `setAttribute`, it sets an attribute in no namespace whose
|
|
18
|
+
name may contain a colon (`v-on:click`).
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
* HTML documents keep namespace URIs as written. `create_element_ns`,
|
|
23
|
+
`set_attribute_ns` and `import_node` lower-cased them (`fooNamespace` read
|
|
24
|
+
back as `foonamespace`), and a differently cased XHTML namespace URI made
|
|
25
|
+
an HTML element.
|
|
26
|
+
|
|
3
27
|
## [0.12.1] - 2026-10-02
|
|
4
28
|
|
|
5
29
|
### Fixed
|
data/README.md
CHANGED
|
@@ -50,7 +50,7 @@ doc = Makiri::HTML(<<~HTML)
|
|
|
50
50
|
</body></html>
|
|
51
51
|
HTML
|
|
52
52
|
|
|
53
|
-
# CSS selectors (Lexbor's selector
|
|
53
|
+
# CSS selectors (Lexbor's selector parser, Makiri's own matcher)
|
|
54
54
|
doc.css("a").map { |a| a["href"] } # => ["/a", "/b"]
|
|
55
55
|
doc.at_css("p.lead").text # => "Hello"
|
|
56
56
|
|
|
@@ -184,6 +184,27 @@ and unusually large documents can raise it with `max_bytes:`.
|
|
|
184
184
|
Makiri::XML(huge_xml, max_bytes: 512 * 1024 * 1024) # also Makiri::XML::Document.parse(..., max_bytes:)
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
+
### Node identity
|
|
188
|
+
|
|
189
|
+
Navigating to the same node always gives the same Ruby object, for as long as
|
|
190
|
+
its document is alive. The document keeps every wrapper it has handed out, so
|
|
191
|
+
instance variables, singleton methods and `freeze` set on a node survive
|
|
192
|
+
garbage collection and are there the next time the node is reached.
|
|
193
|
+
|
|
194
|
+
That makes a node a safe place to keep per-node state, such as a wrapper
|
|
195
|
+
object of your own:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
el = doc.at_css("p")
|
|
199
|
+
el.instance_variable_set(:@wrapper, MyElement.new(el))
|
|
200
|
+
doc.at_css("p").instance_variable_get(:@wrapper) # => the same MyElement
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
A node created by `clone_node` or `import_node` is a new node with no state.
|
|
204
|
+
`pointer_id` is unique only among live nodes: once a document is freed, a node
|
|
205
|
+
of another document may reuse its value. So key a pointer-based cache per
|
|
206
|
+
document, not across documents.
|
|
207
|
+
|
|
187
208
|
## Non-goals (v1.0)
|
|
188
209
|
|
|
189
210
|
* XSLT, DTD / Schema / RelaxNG validation, XPointer, XInclude.
|
|
@@ -224,6 +245,7 @@ See also [`spec/conformance/README.md`](spec/conformance/README.md).
|
|
|
224
245
|
| XPath 1.0 | XML | `Nokogiri::XML` — differential | `conformance:xpath_xml` |
|
|
225
246
|
| Parsed tree (property-based) | XML | `Nokogiri::XML` — differential | `conformance:xml_pbt` |
|
|
226
247
|
| CSS selectors | XML | `Nokogiri::XML` — differential | `conformance:css_xml` |
|
|
248
|
+
| XML Builder | Ruby DSL | `Nokogiri::XML::Builder` — differential | `conformance:builder` |
|
|
227
249
|
|
|
228
250
|
## Requirements
|
|
229
251
|
|
data/ext/makiri/rust/build.rs
CHANGED
|
@@ -186,7 +186,13 @@ fn main() {
|
|
|
186
186
|
.allowlist_function("lxb_dom_document_type_system_id")
|
|
187
187
|
.allowlist_function("lxb_dom_processing_instruction_target")
|
|
188
188
|
.allowlist_function("lxb_ns_by_id")
|
|
189
|
-
|
|
189
|
+
// A namespace URI interned and looked up AS WRITTEN: Lexbor's own
|
|
190
|
+
// `lxb_ns_append` / `lxb_ns_data_by_link` fold ASCII case, where the
|
|
191
|
+
// DOM keeps a namespace an opaque string (`HtmlDoc::intern_ns`).
|
|
192
|
+
.allowlist_function("lexbor_hash_search")
|
|
193
|
+
.allowlist_function("lexbor_hash_insert")
|
|
194
|
+
.allowlist_var("lexbor_hash_search_raw")
|
|
195
|
+
.allowlist_var("lexbor_hash_insert_raw")
|
|
190
196
|
.allowlist_function("lxb_dom_document_root")
|
|
191
197
|
.allowlist_type("lxb_html_token_t")
|
|
192
198
|
.allowlist_type("lxb_html_token_type_t")
|
|
@@ -324,10 +324,26 @@ impl<'a> HtmlEdit<'a> {
|
|
|
324
324
|
/// not the token's - so a caller keeps the span from here to the change
|
|
325
325
|
/// to engine calls and checks that call no Ruby (`insert` reads its
|
|
326
326
|
/// argument's node and frozen flag there, and nothing more).
|
|
327
|
+
///
|
|
328
|
+
/// It counts as a change to a child list ([`bump_tree_version`]); an
|
|
329
|
+
/// attribute edit takes [`HtmlEdit::node_for_attributes`] instead.
|
|
327
330
|
pub fn node(self) -> Result<HtmlNodeMut<'a>, Error> {
|
|
331
|
+
self.mutable(true)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// [`HtmlEdit::node`] for an edit of the element's ATTRIBUTES only, which
|
|
335
|
+
/// changes no child list and so leaves the tree version alone.
|
|
336
|
+
pub fn node_for_attributes(self) -> Result<HtmlNodeMut<'a>, Error> {
|
|
337
|
+
self.mutable(false)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
fn mutable(self, structural: bool) -> Result<HtmlNodeMut<'a>, Error> {
|
|
328
341
|
crate::bridge::ruby::check_frozen(self.this.value)?;
|
|
329
342
|
ensure_document_mutable(self.this.document)?;
|
|
330
343
|
invalidate_indexes(self.this.document);
|
|
344
|
+
if structural {
|
|
345
|
+
bump_tree_version(self.this.document);
|
|
346
|
+
}
|
|
331
347
|
// SAFETY: the receiver is not frozen and no XPath evaluation is
|
|
332
348
|
// reading its document - both checked just now.
|
|
333
349
|
Ok(unsafe { HtmlNodeMut::assume_mutable(self.this.raw().as_node()) })
|
|
@@ -369,7 +385,9 @@ fn adopt_release(src: Value) -> Result<(), Error> {
|
|
|
369
385
|
release_from_tree(unsafe { HtmlNodeMut::assume_mutable(node) });
|
|
370
386
|
})?;
|
|
371
387
|
/* After the borrow `with_arg_node` held: dropping them borrows again. */
|
|
372
|
-
|
|
388
|
+
let src_doc = keepalive_document(src)?;
|
|
389
|
+
invalidate_indexes(src_doc);
|
|
390
|
+
bump_tree_version(src_doc);
|
|
373
391
|
Ok(())
|
|
374
392
|
}
|
|
375
393
|
|
|
@@ -322,6 +322,9 @@ pub struct DocData {
|
|
|
322
322
|
/// what [`account_growth`] compares against, since the byte count
|
|
323
323
|
/// itself costs a walk of every chunk.
|
|
324
324
|
reported_chunks: usize,
|
|
325
|
+
/// `Document#tree_version`: bumped by every edit that can change a child
|
|
326
|
+
/// list of a node this document owns (see [`bump_tree_version`]).
|
|
327
|
+
tree_version: u64,
|
|
325
328
|
/// One wrapper per node; see [`NodeCache`].
|
|
326
329
|
///
|
|
327
330
|
/// Boxed and optional so a document nobody navigates never allocates a
|
|
@@ -590,6 +593,7 @@ impl DocumentShell {
|
|
|
590
593
|
errors: QFALSE,
|
|
591
594
|
reported: 0,
|
|
592
595
|
reported_chunks: 0,
|
|
596
|
+
tree_version: 0,
|
|
593
597
|
nodes: None,
|
|
594
598
|
},
|
|
595
599
|
|d| d.errors = errors.as_raw(),
|
|
@@ -880,6 +884,28 @@ pub fn keepalive_document(rb_node: Value) -> Result<Value, Error> {
|
|
|
880
884
|
Ok(unsafe { value(nd.document) })
|
|
881
885
|
}
|
|
882
886
|
|
|
887
|
+
/* ---- the tree version ---- */
|
|
888
|
+
|
|
889
|
+
/// Count a change to a child list of a node `rb_doc` owns - attached,
|
|
890
|
+
/// detached or inside a fragment alike. Called where an edit is handed its
|
|
891
|
+
/// mutable node (`HtmlEdit::node`, `Editing::with_arena`) and for the source
|
|
892
|
+
/// of an adoption, so no structural mutator can miss it; attribute edits take
|
|
893
|
+
/// the paths that skip it, as they change no child list.
|
|
894
|
+
///
|
|
895
|
+
/// Bumped inside the edit, with no Ruby run between the bump and the change,
|
|
896
|
+
/// so nothing can read the tree in between and cache it under the new number.
|
|
897
|
+
/// An edit that then fails costs a reader a cache refill, never a stale
|
|
898
|
+
/// answer.
|
|
899
|
+
pub fn bump_tree_version(rb_doc: Value) {
|
|
900
|
+
with_doc_data_known(rb_doc, |d| d.tree_version = d.tree_version.wrapping_add(1));
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/// `Document#tree_version`: how many structural edits the document has seen.
|
|
904
|
+
/// `TypeError` for a non-Document.
|
|
905
|
+
pub fn tree_version(rb_doc: Value) -> Result<u64, Error> {
|
|
906
|
+
Ok(DOC_TYPE.get(&rb_doc)?.tree_version)
|
|
907
|
+
}
|
|
908
|
+
|
|
883
909
|
/* ---- the document's mutation gate ---- */
|
|
884
910
|
|
|
885
911
|
/// `Err(Makiri::Error)` while an evaluation with a handler is reading `rb_doc`.
|
|
@@ -330,13 +330,36 @@ impl Editing {
|
|
|
330
330
|
/// it indexes. Not in `begin_edit`: the argument conversion between the two
|
|
331
331
|
/// runs `#to_s`, and a query there rebuilt the index from the tree about to
|
|
332
332
|
/// change - `//a` then kept finding an element renamed to `b`.
|
|
333
|
+
///
|
|
334
|
+
/// It counts as a change to a child list ([`bump_tree_version`]); an
|
|
335
|
+
/// attribute edit takes [`Editing::with_attributes`] instead.
|
|
333
336
|
pub fn with_arena<R>(&self, f: impl FnOnce(&mut XmlDoc, NodeId) -> R) -> Result<R, Error> {
|
|
337
|
+
self.lend(true, f)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// [`Editing::with_arena`] for an edit of the element's ATTRIBUTES only,
|
|
341
|
+
/// which changes no child list and so leaves the tree version alone.
|
|
342
|
+
pub fn with_attributes<R>(&self, f: impl FnOnce(&mut XmlDoc, NodeId) -> R) -> Result<R, Error> {
|
|
343
|
+
self.lend(false, f)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
fn lend<R>(
|
|
347
|
+
&self,
|
|
348
|
+
structural: bool,
|
|
349
|
+
f: impl FnOnce(&mut XmlDoc, NodeId) -> R,
|
|
350
|
+
) -> Result<R, Error> {
|
|
334
351
|
let id = self.id;
|
|
335
352
|
check_frozen(self.receiver)?;
|
|
336
|
-
with_arena_for_new_node(self.document, |d| {
|
|
353
|
+
let r = with_arena_for_new_node(self.document, |d| {
|
|
337
354
|
d.invalidate_name_index();
|
|
338
355
|
f(d, id)
|
|
339
|
-
})
|
|
356
|
+
});
|
|
357
|
+
/* After the arena call, which refuses an evaluated document first -
|
|
358
|
+
* and that, like the frozen check, leaves the tree as it was. */
|
|
359
|
+
if structural && r.is_ok() {
|
|
360
|
+
bump_tree_version(self.document);
|
|
361
|
+
}
|
|
362
|
+
r
|
|
340
363
|
}
|
|
341
364
|
}
|
|
342
365
|
|
|
@@ -559,6 +582,25 @@ pub fn find_attribute(this: XmlSelf, name: Value) -> Result<Option<NodeId>, Erro
|
|
|
559
582
|
Ok(find_attribute_bytes(this.doc_ref(), id, bytes))
|
|
560
583
|
}
|
|
561
584
|
|
|
585
|
+
/// The attribute of `el` in namespace `ns` (nil or "" for none) with local
|
|
586
|
+
/// name `local` - DOM "get an attribute by namespace and local name", the key
|
|
587
|
+
/// `remove_attribute_ns` removes by. Converted before the arena is borrowed,
|
|
588
|
+
/// as [`find_attribute`].
|
|
589
|
+
pub fn find_attribute_ns(this: XmlSelf, ns: Value, local: Value) -> Result<Option<NodeId>, Error> {
|
|
590
|
+
let id = this.id;
|
|
591
|
+
if this.doc_ref().type_(id) != Some(ArenaKind::Element) {
|
|
592
|
+
return Ok(None);
|
|
593
|
+
}
|
|
594
|
+
let lv = ruby_verified_text(local, "attribute local name")?;
|
|
595
|
+
let nv = crate::bridge::string::namespace_arg(ns, "namespace")?;
|
|
596
|
+
let key = crate::xml::attr_key::AttrKey::Ns {
|
|
597
|
+
ns: nv.as_ref().map_or(&b""[..], |n| n.as_bytes()),
|
|
598
|
+
local: lv.as_bytes(),
|
|
599
|
+
};
|
|
600
|
+
let d = this.doc_ref();
|
|
601
|
+
Ok(d.attributes(id).find(|&a| key.matches(d, a)))
|
|
602
|
+
}
|
|
603
|
+
|
|
562
604
|
/// The attribute of `el` whose qualified name is `name`.
|
|
563
605
|
///
|
|
564
606
|
/// Namespace declarations included: in the DOM an `xmlns` / `xmlns:p` is an
|
|
@@ -585,6 +627,8 @@ fn find_attribute_bytes(d: &XmlDoc, el: NodeId, name: &[u8]) -> Option<NodeId> {
|
|
|
585
627
|
/// cannot fail - there is nothing left to look up.
|
|
586
628
|
pub struct Adoption {
|
|
587
629
|
src_doc: *mut XmlDoc,
|
|
630
|
+
/// The source's Document, whose tree version the removal bumps.
|
|
631
|
+
src_document: Value,
|
|
588
632
|
src: NodeId,
|
|
589
633
|
/// The source node's wrapper, which keeps its document - and so
|
|
590
634
|
/// `src_doc` - alive until the adoption is finished.
|
|
@@ -606,6 +650,7 @@ impl Adoption {
|
|
|
606
650
|
remove_node(sdoc, self.src);
|
|
607
651
|
}
|
|
608
652
|
sdoc.invalidate_name_index();
|
|
653
|
+
bump_tree_version(self.src_document);
|
|
609
654
|
}
|
|
610
655
|
}
|
|
611
656
|
|
|
@@ -644,6 +689,7 @@ pub fn incoming_node(target_doc: Value, arg: Value) -> Result<(NodeId, Option<Ad
|
|
|
644
689
|
copy,
|
|
645
690
|
Some(Adoption {
|
|
646
691
|
src_doc,
|
|
692
|
+
src_document,
|
|
647
693
|
src,
|
|
648
694
|
_keep: arg,
|
|
649
695
|
}),
|
|
@@ -167,6 +167,10 @@ pub fn init_html_doc() -> Result<(), Error> {
|
|
|
167
167
|
html_doc.define_method("quirks_mode", method!(doc_quirks_mode, 0))?;
|
|
168
168
|
html_doc.define_method("fragment", method!(doc_fragment, -1))?;
|
|
169
169
|
html_doc.define_method("import_node", method!(doc_import_node, -1))?;
|
|
170
|
+
html_doc.define_method(
|
|
171
|
+
"tree_version",
|
|
172
|
+
method!(crate::glue::node::document_tree_version, 0),
|
|
173
|
+
)?;
|
|
170
174
|
|
|
171
175
|
let frag = crate::init::CLASS_DOCUMENT_FRAGMENT.defined()?;
|
|
172
176
|
frag.define_singleton_method("parse", method!(frag_s_parse, -1))?;
|
|
@@ -110,6 +110,8 @@ fn init_read() -> Result<(), Error> {
|
|
|
110
110
|
"attribute_value_by_qualified_name",
|
|
111
111
|
method!(read::attribute_value_by_qualified_name, 1),
|
|
112
112
|
)?;
|
|
113
|
+
m.define_method("attribute_value_ns", method!(read::attribute_value_ns, 2))?;
|
|
114
|
+
m.define_method("attribute_node_ns", method!(read::attribute_node_ns, 2))?;
|
|
113
115
|
m.define_method("value", method!(read::value, 0))?;
|
|
114
116
|
m.define_method("line", method!(read::line, 0))?;
|
|
115
117
|
|
|
@@ -158,7 +160,12 @@ fn init_mutate() -> Result<(), Error> {
|
|
|
158
160
|
m.define_method("inner_html=", method!(mutate::set_inner_html, 1))?;
|
|
159
161
|
m.define_method("outer_html=", method!(mutate::set_outer_html, 1))?;
|
|
160
162
|
|
|
161
|
-
|
|
163
|
+
/* `[]=` is the DOM's setAttribute already (no-namespace, the whole name as
|
|
164
|
+
* the local name, lower-cased on an HTML element); the XML name for that
|
|
165
|
+
* is `set_loose_dom_attribute`, so it answers here too. */
|
|
166
|
+
for name in ["[]=", "set_loose_dom_attribute"] {
|
|
167
|
+
m.define_method(name, method!(mutate::aset, 2))?;
|
|
168
|
+
}
|
|
162
169
|
m.define_method("set_attribute_ns", method!(mutate::set_attribute_ns, 3))?;
|
|
163
170
|
m.define_method(
|
|
164
171
|
"remove_attribute_ns",
|
|
@@ -75,8 +75,9 @@ fn extract<'q>(
|
|
|
75
75
|
/// The receiver as an element, once every argument is converted. Its node type
|
|
76
76
|
/// was checked before the conversion (an argument cannot change it), so the
|
|
77
77
|
/// `None` arm is unreachable - it answers `refusal` rather than assuming so.
|
|
78
|
+
/// For the attribute mutators only: it leaves the tree version alone.
|
|
78
79
|
fn element_of<'a>(edit: HtmlEdit<'a>, refusal: &'static str) -> Result<HtmlElementMut<'a>, Error> {
|
|
79
|
-
edit.
|
|
80
|
+
edit.node_for_attributes()?
|
|
80
81
|
.element_mut()
|
|
81
82
|
.ok_or_else(|| makiri_error(refusal))
|
|
82
83
|
}
|
|
@@ -23,7 +23,7 @@ use crate::bridge::node_set::node_set_with_fill;
|
|
|
23
23
|
use crate::bridge::ruby::is_kind_of;
|
|
24
24
|
use crate::bridge::string::ruby_verified_text;
|
|
25
25
|
use crate::init::{CLASS_NODE, CLASS_XML_DOCUMENT};
|
|
26
|
-
use crate::lexbor::adapter::html::{HtmlNode, NodeType, RawNode};
|
|
26
|
+
use crate::lexbor::adapter::html::{HtmlAttr, HtmlElement, HtmlNode, NodeType, RawNode};
|
|
27
27
|
|
|
28
28
|
/* ------------------------------------------------------------------ *
|
|
29
29
|
* small helpers *
|
|
@@ -469,6 +469,63 @@ pub fn attribute_value_by_qualified_name(
|
|
|
469
469
|
})
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
/// The attribute of `el` in namespace `rb_ns` (nil or "" for none) with local
|
|
473
|
+
/// name `rb_local` - DOM "get an attribute by namespace and local name", the
|
|
474
|
+
/// key `remove_attribute_ns` removes by. The namespace compares exactly.
|
|
475
|
+
fn attr_by_ns_arg<'d>(
|
|
476
|
+
el: HtmlElement<'d>,
|
|
477
|
+
rb_ns: Value,
|
|
478
|
+
rb_local: Value,
|
|
479
|
+
) -> Result<Option<HtmlAttr<'d>>, Error> {
|
|
480
|
+
let lv = ruby_verified_text(rb_local, "attribute local name")?;
|
|
481
|
+
let nv = crate::bridge::string::namespace_arg(rb_ns, "namespace")?;
|
|
482
|
+
/* Looked up, not interned: a namespace the document never interned is
|
|
483
|
+
* one no attribute here carries. */
|
|
484
|
+
let want_ns = match nv.as_ref() {
|
|
485
|
+
Some(uri) => match el.node().owner_document().lookup_ns(uri.as_bytes()) {
|
|
486
|
+
Some(id) => Some(id),
|
|
487
|
+
None => return Ok(None),
|
|
488
|
+
},
|
|
489
|
+
None => None,
|
|
490
|
+
};
|
|
491
|
+
Ok(el.attr_by_ns(want_ns, lv.as_bytes()))
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/// `#attribute_value_ns(ns, local)` (DOM `getAttributeNS`): the value of the
|
|
495
|
+
/// attribute in namespace `ns` (nil or "" for none) with local name `local`,
|
|
496
|
+
/// or nil - for a non-element too.
|
|
497
|
+
pub fn attribute_value_ns(
|
|
498
|
+
_ruby: &Ruby,
|
|
499
|
+
this: super::HtmlSelf,
|
|
500
|
+
rb_ns: Value,
|
|
501
|
+
rb_local: Value,
|
|
502
|
+
) -> Result<Option<Value>, Error> {
|
|
503
|
+
crate::bridge::ruby::entry(|| {
|
|
504
|
+
let Some(el) = this.node().element() else {
|
|
505
|
+
return Ok(None);
|
|
506
|
+
};
|
|
507
|
+
let found = attr_by_ns_arg(el, rb_ns, rb_local)?;
|
|
508
|
+
Ok(found.map(|at| dom_str(at.value())))
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/// `#attribute_node_ns(ns, local)` (DOM `getAttributeNodeNS`): the Attr
|
|
513
|
+
/// [`attribute_value_ns`] reads, or nil.
|
|
514
|
+
pub fn attribute_node_ns(
|
|
515
|
+
_ruby: &Ruby,
|
|
516
|
+
this: super::HtmlSelf,
|
|
517
|
+
rb_ns: Value,
|
|
518
|
+
rb_local: Value,
|
|
519
|
+
) -> Result<Option<Value>, Error> {
|
|
520
|
+
crate::bridge::ruby::entry(|| {
|
|
521
|
+
let Some(el) = this.node().element() else {
|
|
522
|
+
return Ok(None);
|
|
523
|
+
};
|
|
524
|
+
let found = attr_by_ns_arg(el, rb_ns, rb_local)?;
|
|
525
|
+
wrap_node(found.map(|at| at.node()), this.document)
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
|
|
472
529
|
/// `attr.value`. For a non-attribute node this falls back to text content,
|
|
473
530
|
/// matching the loose Nokogiri-ish meaning of `#value`.
|
|
474
531
|
pub fn value(ruby: &Ruby, this: super::HtmlSelf) -> Result<Value, Error> {
|
|
@@ -40,3 +40,12 @@ pub fn node_pointer_id(ruby: &Ruby, rb_self: Value) -> Result<Integer, magnus::E
|
|
|
40
40
|
pub fn node_hash(ruby: &Ruby, rb_self: Value) -> Result<Integer, magnus::Error> {
|
|
41
41
|
crate::bridge::ruby::entry(|| node_pointer_id(ruby, rb_self))
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
/// `Document#tree_version`: an Integer that grows with every edit that can
|
|
45
|
+
/// change a child list of a node the document owns (attached, detached or in
|
|
46
|
+
/// a fragment) - add, remove, replace, `inner_html=`, `content=` and the
|
|
47
|
+
/// like, and both documents of a move between them. Attribute edits leave it
|
|
48
|
+
/// alone. A reader caching a child list keys it by this.
|
|
49
|
+
pub fn document_tree_version(rb_self: Value) -> Result<u64, magnus::Error> {
|
|
50
|
+
crate::bridge::ruby::entry(|| crate::bridge::wrapper::tree_version(rb_self))
|
|
51
|
+
}
|
|
@@ -140,6 +140,10 @@ pub fn init_xml_doc() -> Result<(), Error> {
|
|
|
140
140
|
doc.define_method("root", method!(doc_root, 0))?;
|
|
141
141
|
doc.define_method("internal_subset", method!(doc_internal_subset, 0))?;
|
|
142
142
|
doc.define_method("fragment", method!(doc_fragment, 1))?;
|
|
143
|
+
doc.define_method(
|
|
144
|
+
"tree_version",
|
|
145
|
+
method!(crate::glue::node::document_tree_version, 0),
|
|
146
|
+
)?;
|
|
143
147
|
|
|
144
148
|
CLASS_XML_DOCUMENT_FRAGMENT
|
|
145
149
|
.defined()?
|
|
@@ -83,6 +83,8 @@ fn init_read() -> Result<(), Error> {
|
|
|
83
83
|
"attribute_by_qualified_name",
|
|
84
84
|
method!(read::attribute_by_qualified_name, 1),
|
|
85
85
|
)?;
|
|
86
|
+
m.define_method("attribute_value_ns", method!(read::attribute_value_ns, 2))?;
|
|
87
|
+
m.define_method("attribute_node_ns", method!(read::attribute_node_ns, 2))?;
|
|
86
88
|
|
|
87
89
|
/* Node identity by the underlying pointer, so #path, NodeSet dedup, Set and
|
|
88
90
|
* Hash all work - the same contract HTML nodes have, from the same code. */
|
|
@@ -65,7 +65,7 @@ pub fn aset(_ruby: &Ruby, this: XmlSelf, name: Value, val: Value) -> Result<Valu
|
|
|
65
65
|
let nv = verified_name(name, "attribute name")?;
|
|
66
66
|
let vv = verified_data(val, "attribute value")?;
|
|
67
67
|
let (name, value) = (nv.as_bytes(), vv.as_bytes());
|
|
68
|
-
xml_mut_result(edit.
|
|
68
|
+
xml_mut_result(edit.with_attributes(|d, n| mutate::set_attribute(d, n, name, value))?)?;
|
|
69
69
|
Ok(val)
|
|
70
70
|
})
|
|
71
71
|
}
|
|
@@ -88,7 +88,9 @@ pub fn set_attribute_ns(
|
|
|
88
88
|
qv.as_bytes(),
|
|
89
89
|
vv.as_bytes(),
|
|
90
90
|
);
|
|
91
|
-
xml_mut_result(
|
|
91
|
+
xml_mut_result(
|
|
92
|
+
edit.with_attributes(|d, n| mutate::set_attribute_ns(d, n, ns, qname, value))?,
|
|
93
|
+
)?;
|
|
92
94
|
Ok(val)
|
|
93
95
|
})
|
|
94
96
|
}
|
|
@@ -110,7 +112,7 @@ pub fn set_loose_dom_attribute(
|
|
|
110
112
|
let vv = verified_data(val, "attribute value")?;
|
|
111
113
|
let (name, value) = (nv.as_bytes(), vv.as_bytes());
|
|
112
114
|
xml_mut_result(
|
|
113
|
-
edit.
|
|
115
|
+
edit.with_attributes(|d, n| mutate::set_loose_dom_attribute(d, n, name, value))?,
|
|
114
116
|
)?;
|
|
115
117
|
Ok(val)
|
|
116
118
|
})
|
|
@@ -133,7 +135,7 @@ pub fn remove_attribute_ns(
|
|
|
133
135
|
let nv = namespace_arg(ns, "namespace")?;
|
|
134
136
|
let ns = nv.as_ref().map_or(&b""[..], |n| n.as_bytes());
|
|
135
137
|
let local = lv.as_bytes();
|
|
136
|
-
edit.
|
|
138
|
+
edit.with_attributes(|d, n| mutate::remove_attribute_ns(d, n, ns, local))?;
|
|
137
139
|
Ok(rb_self)
|
|
138
140
|
})
|
|
139
141
|
}
|
|
@@ -148,7 +150,7 @@ pub fn delete(_ruby: &Ruby, this: XmlSelf, name: Value) -> Result<Value, Error>
|
|
|
148
150
|
}
|
|
149
151
|
let nv = verified_text(name, "attribute name")?;
|
|
150
152
|
let name = nv.as_bytes();
|
|
151
|
-
edit.
|
|
153
|
+
edit.with_attributes(|d, n| mutate::remove_attribute(d, n, name))?;
|
|
152
154
|
Ok(rb_self)
|
|
153
155
|
})
|
|
154
156
|
}
|
|
@@ -281,6 +281,34 @@ pub fn aref(ruby: &Ruby, this: XmlSelf, rb_name: Value) -> Result<Option<Value>,
|
|
|
281
281
|
})
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
/// `#attribute_value_ns(ns, local)` (DOM `getAttributeNS`): the value of the
|
|
285
|
+
/// attribute in namespace `ns` (nil or "" for none) with local name `local`,
|
|
286
|
+
/// or nil - for a non-element too.
|
|
287
|
+
pub fn attribute_value_ns(
|
|
288
|
+
ruby: &Ruby,
|
|
289
|
+
this: XmlSelf,
|
|
290
|
+
rb_ns: Value,
|
|
291
|
+
rb_local: Value,
|
|
292
|
+
) -> Result<Option<Value>, Error> {
|
|
293
|
+
crate::bridge::ruby::entry(|| {
|
|
294
|
+
let found = crate::bridge::xml::find_attribute_ns(this, rb_ns, rb_local)?;
|
|
295
|
+
Ok(str_or_nil(ruby, found.map(|at| this.doc_ref().value(at))))
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// `#attribute_node_ns(ns, local)` (DOM `getAttributeNodeNS`): the Attr
|
|
300
|
+
/// [`attribute_value_ns`] reads, or nil.
|
|
301
|
+
pub fn attribute_node_ns(
|
|
302
|
+
this: XmlSelf,
|
|
303
|
+
rb_ns: Value,
|
|
304
|
+
rb_local: Value,
|
|
305
|
+
) -> Result<Option<Value>, Error> {
|
|
306
|
+
crate::bridge::ruby::entry(|| {
|
|
307
|
+
let found = crate::bridge::xml::find_attribute_ns(this, rb_ns, rb_local)?;
|
|
308
|
+
wrap_rel(this, found)
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
284
312
|
/// The Attr NODE with that qualified name.
|
|
285
313
|
pub fn attribute_by_qualified_name(this: XmlSelf, rb_name: Value) -> Result<Option<Value>, Error> {
|
|
286
314
|
crate::bridge::ruby::entry(|| {
|
|
@@ -209,21 +209,31 @@ impl<'doc> HtmlElement<'doc> {
|
|
|
209
209
|
value: &[u8],
|
|
210
210
|
lower: bool,
|
|
211
211
|
) -> Result<HtmlAttr<'doc>, AdapterOom> {
|
|
212
|
+
/* Interned as written first: Lexbor's set_name_ns below interns the
|
|
213
|
+
* URI too, case-folded, and its id is replaced by this one. */
|
|
214
|
+
let ns_id = match ns {
|
|
215
|
+
Some(uri) => Some(self.node().owner_document().intern_ns(uri).or_oom()?),
|
|
216
|
+
None => None,
|
|
217
|
+
};
|
|
212
218
|
// SAFETY: a live element of a live document; every slice is read and
|
|
213
219
|
// copied by Lexbor, and the attribute is one nothing else holds.
|
|
214
220
|
unsafe {
|
|
215
221
|
let at = lxb::lxb_dom_attr_interface_create(self.node().owner_document().as_raw());
|
|
216
222
|
let at = HtmlAttr::link(at).or_oom()?;
|
|
217
|
-
let named = match ns {
|
|
218
|
-
Some(uri) =>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
let named = match (ns, ns_id) {
|
|
224
|
+
(Some(uri), Some(id)) => {
|
|
225
|
+
let st = lxb::lxb_dom_attr_set_name_ns(
|
|
226
|
+
at.raw(),
|
|
227
|
+
uri.as_ptr(),
|
|
228
|
+
uri.len(),
|
|
229
|
+
qname.as_ptr(),
|
|
230
|
+
qname.len(),
|
|
231
|
+
false,
|
|
232
|
+
);
|
|
233
|
+
(*at.raw()).node.ns = id.raw();
|
|
234
|
+
st
|
|
235
|
+
}
|
|
236
|
+
_ => {
|
|
227
237
|
(*at.raw()).node.ns = (*self.raw()).node.ns;
|
|
228
238
|
lxb::lxb_dom_attr_set_name(at.raw(), qname.as_ptr(), qname.len(), lower)
|
|
229
239
|
}
|
|
@@ -431,6 +441,21 @@ unsafe fn repair_element(
|
|
|
431
441
|
unsafe { de.link_attr(copy, None) };
|
|
432
442
|
}
|
|
433
443
|
}
|
|
444
|
+
/* Lexbor's copy re-interns a namespace past the built-in ones in this
|
|
445
|
+
* document case-folded (`lxb_dom_node_interface_copy`), so `fooNamespace`
|
|
446
|
+
* would arrive as `foonamespace`: such a copy gets its source's URI as
|
|
447
|
+
* written, element and attributes alike. An attribute with no namespace
|
|
448
|
+
* of its own carries its element's, and gets the same id the element
|
|
449
|
+
* does. */
|
|
450
|
+
if se.node().owner_document() != doc {
|
|
451
|
+
// SAFETY: the copy is unshared, per the contract.
|
|
452
|
+
unsafe {
|
|
453
|
+
restore_ns(doc, se.node(), de.node())?;
|
|
454
|
+
for (sa, da) in se.attrs().zip(de.attrs()) {
|
|
455
|
+
restore_ns(doc, sa.node(), da.node())?;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
434
459
|
/* The lists now match one for one, so each shortcut goes to the copy of
|
|
435
460
|
* the attribute that holds it in the source - which the source keeps as
|
|
436
461
|
* the DOM's ID / class attribute. */
|
|
@@ -450,3 +475,23 @@ unsafe fn repair_element(
|
|
|
450
475
|
}
|
|
451
476
|
Ok(())
|
|
452
477
|
}
|
|
478
|
+
|
|
479
|
+
/// Give `dst`, a copy of `src` from another document, `src`'s namespace as
|
|
480
|
+
/// written - for one past the built-in ones, whose id Lexbor's copy re-interned
|
|
481
|
+
/// case-folded. A built-in id is the same number in every document.
|
|
482
|
+
///
|
|
483
|
+
/// # Safety
|
|
484
|
+
/// `dst` is a node of a copy nothing but the caller holds, in `doc`.
|
|
485
|
+
unsafe fn restore_ns(
|
|
486
|
+
doc: HtmlDoc<'_>,
|
|
487
|
+
src: HtmlNode<'_>,
|
|
488
|
+
dst: HtmlNode<'_>,
|
|
489
|
+
) -> Result<(), AdapterOom> {
|
|
490
|
+
if src.ns_id().is_none_or(|id| id.is_static()) {
|
|
491
|
+
return Ok(());
|
|
492
|
+
}
|
|
493
|
+
let id = doc.intern_ns(src.ns_uri().or_oom()?).or_oom()?;
|
|
494
|
+
// SAFETY: per the contract; `id` is interned in `doc`'s table.
|
|
495
|
+
unsafe { (*dst.as_raw()).ns = id.raw() };
|
|
496
|
+
Ok(())
|
|
497
|
+
}
|
|
@@ -47,6 +47,22 @@ impl<'doc> HtmlDoc<'doc> {
|
|
|
47
47
|
if has_ascii_uppercase(local) && self.lookup_ns(ns) == Some(NsId::HTML) {
|
|
48
48
|
return self.create_html_element_as_written(local, prefix);
|
|
49
49
|
}
|
|
50
|
+
/* The namespace is interned here, as written, and only a built-in
|
|
51
|
+
* one is named to Lexbor - by its exact URI, which its case-folding
|
|
52
|
+
* lookup maps back to the same id. Any other is created in no
|
|
53
|
+
* namespace and given its id after: handed to Lexbor, `fooNamespace`
|
|
54
|
+
* would be interned lower-cased, and `HTTP://WWW.W3.ORG/1999/XHTML`
|
|
55
|
+
* would make an HTML element. In no namespace and in a namespace past
|
|
56
|
+
* the built-in ones, Lexbor builds the same plain element struct. */
|
|
57
|
+
let ns_id = if ns.is_empty() {
|
|
58
|
+
None
|
|
59
|
+
} else {
|
|
60
|
+
Some(self.intern_ns(ns)?)
|
|
61
|
+
};
|
|
62
|
+
let lexbor_ns: &[u8] = match ns_id {
|
|
63
|
+
Some(id) if id.is_static() => ns,
|
|
64
|
+
_ => &[],
|
|
65
|
+
};
|
|
50
66
|
let or_null = |s: &[u8]| {
|
|
51
67
|
if s.is_empty() {
|
|
52
68
|
core::ptr::null()
|
|
@@ -66,8 +82,8 @@ impl<'doc> HtmlDoc<'doc> {
|
|
|
66
82
|
self.as_raw(),
|
|
67
83
|
local.as_ptr(),
|
|
68
84
|
local.len(),
|
|
69
|
-
or_null(
|
|
70
|
-
|
|
85
|
+
or_null(lexbor_ns),
|
|
86
|
+
lexbor_ns.len(),
|
|
71
87
|
core::ptr::null(),
|
|
72
88
|
0,
|
|
73
89
|
core::ptr::null(),
|
|
@@ -75,6 +91,11 @@ impl<'doc> HtmlDoc<'doc> {
|
|
|
75
91
|
false,
|
|
76
92
|
))
|
|
77
93
|
}?;
|
|
94
|
+
if let Some(id) = ns_id.filter(|id| !id.is_static()) {
|
|
95
|
+
// SAFETY: an element just made in this document, in no tree; `id`
|
|
96
|
+
// is interned in its namespace table.
|
|
97
|
+
unsafe { (*el.0.raw()).node.ns = id.raw() };
|
|
98
|
+
}
|
|
78
99
|
let (p, p_len) = if prefix.is_empty() {
|
|
79
100
|
if !has_ascii_uppercase(local) {
|
|
80
101
|
return Some(el);
|
|
@@ -75,6 +75,20 @@ const fn nonzero(v: usize) -> NonZeroUsize {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/// The built-in namespace whose URI is exactly `uri`, as Lexbor spells it.
|
|
79
|
+
/// Byte for byte: Lexbor's own static lookup folds ASCII case.
|
|
80
|
+
fn static_ns(uri: &[u8]) -> Option<NsId> {
|
|
81
|
+
/* From HTML: `UNDEF` and `ANY` have no URI. */
|
|
82
|
+
(lxb::lxb_ns_id_enum_t_LXB_NS_HTML as usize..lxb::lxb_ns_id_enum_t_LXB_NS__LAST_ENTRY as usize)
|
|
83
|
+
.find_map(|id| {
|
|
84
|
+
let mut len = 0;
|
|
85
|
+
// SAFETY: a static id, which `lxb_ns_by_id` answers from Lexbor's
|
|
86
|
+
// static table without reading the (here null) document table.
|
|
87
|
+
let link = unsafe { seen(lxb::lxb_ns_by_id(core::ptr::null_mut(), id, &mut len), len) };
|
|
88
|
+
(link == uri).then(|| NsId::from_raw(id)).flatten()
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
78
92
|
/// An interned namespace id of a document's namespace table - never
|
|
79
93
|
/// `LXB_NS__UNDEF`, which reads as `None`.
|
|
80
94
|
///
|
|
@@ -589,19 +603,31 @@ impl<'doc> HtmlDoc<'doc> {
|
|
|
589
603
|
/// interning it. `None` for an empty URI and for one never interned - no
|
|
590
604
|
/// node or attribute can carry a namespace the table does not hold.
|
|
591
605
|
///
|
|
592
|
-
///
|
|
593
|
-
///
|
|
606
|
+
/// Exact, as the DOM compares namespaces: `fooNamespace` is not
|
|
607
|
+
/// `foonamespace`, and `HTTP://WWW.W3.ORG/1999/XHTML` is not the HTML
|
|
608
|
+
/// namespace. Lexbor's own `lxb_ns_data_by_link` folds ASCII case in both,
|
|
609
|
+
/// so neither it nor `lxb_ns_append` is called with a URI from outside;
|
|
610
|
+
/// see [`intern_ns`](Self::intern_ns).
|
|
594
611
|
pub fn lookup_ns(self, uri: &[u8]) -> Option<NsId> {
|
|
595
612
|
if uri.is_empty() {
|
|
596
613
|
return None;
|
|
597
614
|
}
|
|
598
|
-
|
|
615
|
+
if let Some(id) = static_ns(uri) {
|
|
616
|
+
return Some(id);
|
|
617
|
+
}
|
|
618
|
+
// SAFETY: a live document; the table and the URI are only read, and
|
|
619
|
+
// every entry of the namespace table is an `lxb_ns_data_t`.
|
|
599
620
|
let id = unsafe {
|
|
600
621
|
let table = (*self.as_raw()).ns;
|
|
601
622
|
if table.is_null() {
|
|
602
623
|
return None;
|
|
603
624
|
}
|
|
604
|
-
let d = lxb::
|
|
625
|
+
let d = lxb::lexbor_hash_search(
|
|
626
|
+
table,
|
|
627
|
+
lxb::lexbor_hash_search_raw,
|
|
628
|
+
uri.as_ptr(),
|
|
629
|
+
uri.len(),
|
|
630
|
+
) as *const lxb::lxb_ns_data_t;
|
|
605
631
|
if d.is_null() {
|
|
606
632
|
return None;
|
|
607
633
|
}
|
|
@@ -610,24 +636,42 @@ impl<'doc> HtmlDoc<'doc> {
|
|
|
610
636
|
NsId::from_raw(id)
|
|
611
637
|
}
|
|
612
638
|
|
|
613
|
-
/// The id of `uri` in this document's namespace table, interning it
|
|
614
|
-
/// the table does not hold it yet. `None` for an empty URI or
|
|
615
|
-
/// could not intern it.
|
|
639
|
+
/// The id of `uri` in this document's namespace table, interning it AS
|
|
640
|
+
/// WRITTEN if the table does not hold it yet. `None` for an empty URI or
|
|
641
|
+
/// when Lexbor could not intern it.
|
|
642
|
+
///
|
|
643
|
+
/// A built-in namespace is its static id only for its exact URI; any other
|
|
644
|
+
/// URI gets an entry of its own, keyed case-sensitively - what
|
|
645
|
+
/// `lxb_ns_append` makes, but without its case folding. A URI equal to one
|
|
646
|
+
/// Lexbor interned lower-cased (all its entries are) finds that entry, so
|
|
647
|
+
/// the two kinds share one table without colliding.
|
|
616
648
|
pub fn intern_ns(self, uri: &[u8]) -> Option<NsId> {
|
|
649
|
+
if let Some(id) = self.lookup_ns(uri) {
|
|
650
|
+
return Some(id);
|
|
651
|
+
}
|
|
617
652
|
if uri.is_empty() {
|
|
618
653
|
return None;
|
|
619
654
|
}
|
|
620
655
|
// SAFETY: a live document; Lexbor copies the URI into its own table,
|
|
621
|
-
// which keeps the entry for the document's lifetime
|
|
656
|
+
// which keeps the entry for the document's lifetime, and the table was
|
|
657
|
+
// made with `lxb_ns_data_t` entries (`lxb_dom_document_init`).
|
|
622
658
|
let id = unsafe {
|
|
623
659
|
let table = (*self.as_raw()).ns;
|
|
624
660
|
if table.is_null() {
|
|
625
661
|
return None;
|
|
626
662
|
}
|
|
627
|
-
let d = lxb::
|
|
628
|
-
|
|
663
|
+
let d = lxb::lexbor_hash_insert(
|
|
664
|
+
table,
|
|
665
|
+
lxb::lexbor_hash_insert_raw,
|
|
666
|
+
uri.as_ptr(),
|
|
667
|
+
uri.len(),
|
|
668
|
+
) as *mut lxb::lxb_ns_data_t;
|
|
669
|
+
/* The same guard `lxb_ns_append` applies: an id must lie past the
|
|
670
|
+
* static range, which a real allocation always does. */
|
|
671
|
+
if (d as usize) <= lxb::lxb_ns_id_enum_t_LXB_NS__LAST_ENTRY as usize {
|
|
629
672
|
return None;
|
|
630
673
|
}
|
|
674
|
+
(*d).ns_id = d as usize;
|
|
631
675
|
(*d).ns_id
|
|
632
676
|
};
|
|
633
677
|
NsId::from_raw(id)
|
data/lib/makiri/version.rb
CHANGED
|
@@ -55,9 +55,9 @@ UNSAFE_ISLANDS = {
|
|
|
55
55
|
"lexbor/abi.rs" => 4,
|
|
56
56
|
"lexbor/adapter/arena_bytes.rs" => 14,
|
|
57
57
|
"lexbor/adapter/cross_import.rs" => 2,
|
|
58
|
-
"lexbor/adapter/html/attrs.rs" =>
|
|
59
|
-
"lexbor/adapter/html/build.rs" =>
|
|
60
|
-
"lexbor/adapter/html/mod.rs" =>
|
|
58
|
+
"lexbor/adapter/html/attrs.rs" => 23,
|
|
59
|
+
"lexbor/adapter/html/build.rs" => 25,
|
|
60
|
+
"lexbor/adapter/html/mod.rs" => 63,
|
|
61
61
|
"lexbor/adapter/html/mutate.rs" => 9,
|
|
62
62
|
"lexbor/adapter/html/serialize.rs" => 18,
|
|
63
63
|
"lexbor/adapter/post_parse.rs" => 13,
|