makiri 0.11.0.rc2 → 0.12.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 +47 -0
- data/NOKOGIRI_DIFFERENCES.md +34 -15
- data/ext/makiri/rust/build.rs +18 -0
- data/ext/makiri/rust/src/bridge/doc.rs +6 -0
- data/ext/makiri/rust/src/bridge/html.rs +13 -6
- data/ext/makiri/rust/src/bridge/wrapper.rs +19 -0
- data/ext/makiri/rust/src/bridge/xml.rs +22 -3
- data/ext/makiri/rust/src/glue/html_node/mutate.rs +7 -3
- data/ext/makiri/rust/src/glue/html_node/serialize.rs +12 -5
- data/ext/makiri/rust/src/glue/xml_node/mutate.rs +10 -10
- data/ext/makiri/rust/src/glue/xml_node/serialize.rs +7 -3
- data/ext/makiri/rust/src/lexbor/abi.rs +11 -2
- data/ext/makiri/rust/src/lexbor/adapter/cross_import.rs +65 -127
- data/ext/makiri/rust/src/lexbor/adapter/dom_index.rs +19 -2
- data/ext/makiri/rust/src/lexbor/adapter/html/build.rs +74 -14
- data/ext/makiri/rust/src/lexbor/adapter/html/mod.rs +3 -2
- data/ext/makiri/rust/src/lexbor/adapter/html/serialize.rs +187 -0
- data/ext/makiri/rust/src/lexbor/adapter/post_parse.rs +21 -0
- data/ext/makiri/rust/src/lexbor/serialize.rs +10 -3
- data/ext/makiri/rust/src/lexbor/xpath.rs +3 -2
- data/ext/makiri/rust/src/xml/model.rs +10 -7
- data/ext/makiri/rust/src/xml/mutate/attr.rs +45 -18
- data/ext/makiri/rust/src/xml/mutate/edit.rs +21 -21
- data/ext/makiri/rust/src/xml/mutate/factory.rs +19 -11
- data/ext/makiri/rust/src/xml/mutate/mod.rs +2 -1
- data/ext/makiri/rust/src/xml/selftest.rs +26 -25
- data/ext/makiri/rust/src/xml/serialize/c14n.rs +71 -37
- data/ext/makiri/rust/src/xml/serialize/mod.rs +11 -6
- data/ext/makiri/rust/src/xml/serialize/out.rs +109 -0
- data/ext/makiri/rust/src/xml/serialize/xml.rs +3 -6
- data/lib/makiri/element.rb +16 -0
- data/lib/makiri/node_path.rb +8 -1
- data/lib/makiri/version.rb +1 -1
- data/script/check_unsafe_boundaries.rb +3 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 19309bc1e3663b299aa192c879718ffb75926783690cd1213338c472d7d439fe
|
|
4
|
+
data.tar.gz: 0a7498c693ec14f659ea75dcd85c02af153fe5c19b172f62ffcaaf3d2c549ab0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2465968a7f276bf2c93b4d0e37928a3d292f05212e37d0148a29b529494a3ef52e51ae2679c478c4b11994ad229fac5e6869f7d169c4293bab68ae791e654a5b
|
|
7
|
+
data.tar.gz: 1ffe5dd03e3d00914ecf4c6753000a6a82dfc160deb7b0d4b3fa6da93d27dbd082c6e073f63f6d35c2b6ad2ae7f508edb9f011da7cfb50ba70235bd96eea4c50
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0] - 2026-10-01
|
|
4
|
+
|
|
5
|
+
Most changes accept what the DOM allows where 0.11.0 raised. The XML
|
|
6
|
+
serializers (`to_xml`, `canonicalize`) raise instead when the tree holds
|
|
7
|
+
something XML cannot write.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
HTML:
|
|
12
|
+
|
|
13
|
+
* `to_html` / `inner_html` write a `<template>`'s contents, not its own
|
|
14
|
+
children (added with `add_child`), as browsers do.
|
|
15
|
+
* `create_element_ns` in the HTML namespace accepts an upper-case name
|
|
16
|
+
(`BR`, `DIV`), as the DOM does. It makes an unknown element: not void, and
|
|
17
|
+
not matched by type selectors. Importing such an XHTML element from XML
|
|
18
|
+
behaves the same way.
|
|
19
|
+
|
|
20
|
+
XML:
|
|
21
|
+
|
|
22
|
+
* Text, attribute values and comments accept the characters the DOM allows:
|
|
23
|
+
`"\f"`, U+0001, NUL, and `--` in a comment.
|
|
24
|
+
* `set_attribute_ns` accepts names XML cannot write, such as `p:a}b`.
|
|
25
|
+
* `to_xml` and `canonicalize` raise while any of the above is in the tree.
|
|
26
|
+
* `create_cdata` with `]]>` and `create_processing_instruction` with `?>`
|
|
27
|
+
now raise `ArgumentError`.
|
|
28
|
+
* `canonicalize` adds the namespace declarations a name needs instead of
|
|
29
|
+
raising. It still raises where it would have to invent a prefix.
|
|
30
|
+
|
|
31
|
+
HTML to XML (`import_node`):
|
|
32
|
+
|
|
33
|
+
* The copy is in its namespace at once. Before, it was in none until
|
|
34
|
+
inserted.
|
|
35
|
+
* The copy gets no `xmlns` attribute.
|
|
36
|
+
* An attribute XML cannot write (`x-on:click`, `@click`) is copied as it is
|
|
37
|
+
instead of raising.
|
|
38
|
+
* A `<template>` keeps its own children, after its contents.
|
|
39
|
+
|
|
40
|
+
Other:
|
|
41
|
+
|
|
42
|
+
* `Node#path` round-trips when a sibling with a different prefix or case
|
|
43
|
+
would also match the same XPath step.
|
|
44
|
+
|
|
45
|
+
## [0.11.0] - 2026-09-30
|
|
46
|
+
|
|
47
|
+
No code changes since 0.11.0.rc2. Coming from 0.10.x, read the rc2 and rc1
|
|
48
|
+
notes below as well.
|
|
49
|
+
|
|
3
50
|
## [0.11.0.rc2] - 2026-09-30
|
|
4
51
|
|
|
5
52
|
### Added
|
data/NOKOGIRI_DIFFERENCES.md
CHANGED
|
@@ -184,11 +184,11 @@ what browsers do - rather than libxml2. Detailed, test-backed notes live in
|
|
|
184
184
|
that does not fit the name (`set_attribute_ns`, `create_element_ns`)
|
|
185
185
|
raises `Makiri::Error`. Invalid UTF-8 raises `Makiri::Error` for every
|
|
186
186
|
argument, names included.
|
|
187
|
-
* `create_element_ns`
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
* `create_element_ns` keeps the case of an HTML-namespace name, as the DOM
|
|
188
|
+
does: `create_element_ns(XHTML, "BR")` is an unknown element named `BR`,
|
|
189
|
+
not a void `br`, and type selectors do not match it (XPath name tests do,
|
|
190
|
+
folding case on HTML elements as browsers do). Nokogiri has no
|
|
191
|
+
`create_element_ns`.
|
|
192
192
|
* An HTML `<template>` follows the WHATWG content model, which Nokogiri does
|
|
193
193
|
not: its parsed contents live in the separate fragment `Element#content_fragment`
|
|
194
194
|
returns, `template.children` is empty, and `inner_html` / `inner_html=`
|
|
@@ -198,6 +198,11 @@ what browsers do - rather than libxml2. Detailed, test-backed notes live in
|
|
|
198
198
|
ordinary element, with the parsed nodes as its children, so
|
|
199
199
|
`template.inner_html` and `template.children` answer the other way round and
|
|
200
200
|
there is no `content_fragment`.
|
|
201
|
+
* `Makiri::XML` has no template contents: an XHTML `<template>`'s children
|
|
202
|
+
are its children. Crossing into HTML they become its contents, and back
|
|
203
|
+
they become children - what a browser's XML parser, which puts them in the
|
|
204
|
+
contents, and its importNode give for the same document. An XML
|
|
205
|
+
`<template>` whose children should stay children has no way to say so.
|
|
201
206
|
* An HTML document has one root element and no text child, as the DOM requires;
|
|
202
207
|
`doc << element` beside an existing root raises.
|
|
203
208
|
* An insertion the DOM refuses - a child under a text, comment, PI, doctype
|
|
@@ -205,13 +210,20 @@ what browsers do - rather than libxml2. Detailed, test-backed notes live in
|
|
|
205
210
|
`Makiri::Error` in both representations. Nokogiri refuses the same ones with
|
|
206
211
|
`ArgumentError` (or `RuntimeError` for a second XML root).
|
|
207
212
|
* Moving HTML into an XML document (`xml_doc.import_node(html_node)`, or
|
|
208
|
-
inserting one)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
inserting one) copies it as the DOM's clone does: every element in its
|
|
214
|
+
namespace from the start (an imported `<p>` is XHTML before it is
|
|
215
|
+
inserted), every attribute named as it is, and no `xmlns` attribute added -
|
|
216
|
+
`to_xml` and `canonicalize` write the declarations the output needs.
|
|
217
|
+
Nokogiri's copy of an HTML5 `<div>` is in no namespace, and libxml2
|
|
218
|
+
declares `xmlns:svg` on it for an SVG child (in `namespace_definitions`),
|
|
219
|
+
writing the child as `<svg:svg>`. One XML cannot write that way -
|
|
220
|
+
in no namespace with a colon or no XML name, `v-on:click`, `:href`,
|
|
221
|
+
`@click`, `fb:like` - crosses DOM-loose, and `to_xml` refuses the tree
|
|
222
|
+
while it is there; so does an element named with a colon (`<fb:like>`).
|
|
223
|
+
Nokogiri copies them and writes output that is not namespace-well-formed.
|
|
224
|
+
* One exception to the DOM: an HTML attribute named `xml:lang` (in no
|
|
225
|
+
namespace) becomes the XML namespace's `xml:lang`, as the XML reader reads
|
|
226
|
+
that name, so XHTML-style HTML still writes as XML.
|
|
215
227
|
* A known gap, in Lexbor's tag table: an HTML document that already holds a
|
|
216
228
|
parsed element named with a colon (`<x:y>`, one local name) and then
|
|
217
229
|
receives, by `import_node` from another document, a prefixed element
|
|
@@ -284,7 +296,7 @@ what browsers do - rather than libxml2. Detailed, test-backed notes live in
|
|
|
284
296
|
case-sensitivity rule, as browsers do: lower-cased for an HTML element (`LI`
|
|
285
297
|
matches `<li>`), as written for any other (`feGaussianBlur` matches the SVG
|
|
286
298
|
element, `fegaussianblur` does not). An HTML element named in upper case
|
|
287
|
-
(`create_element_ns(XHTML, "
|
|
299
|
+
(`create_element_ns(XHTML, "DIV")`, which keeps its name as the DOM does)
|
|
288
300
|
therefore matches no type selector.
|
|
289
301
|
* `Nokogiri::HTML5` is case-sensitive on HTML elements too, so `LI` does not
|
|
290
302
|
match `<li>` there. `Makiri::XML`'s `#css` is case-sensitive, as XML names
|
|
@@ -321,5 +333,12 @@ what browsers do - rather than libxml2. Detailed, test-backed notes live in
|
|
|
321
333
|
`Makiri::Error`.
|
|
322
334
|
* On re-parse, the HTML tokenizer replaces a U+0000 in text/attributes with
|
|
323
335
|
U+FFFD (WHATWG), so a serialized-then-reparsed round-trip is not byte-identical.
|
|
324
|
-
* `Makiri::XML`
|
|
325
|
-
|
|
336
|
+
* `Makiri::XML` holds the character data the DOM holds, NUL included:
|
|
337
|
+
text or an attribute value with a character XML has no `Char` for (`\f`,
|
|
338
|
+
U+0001, U+0000), a comment with `--`. Nokogiri takes the same, but NUL
|
|
339
|
+
(`ArgumentError`, a Ruby C-string limit). Where they part is the output. `to_xml` / `canonicalize` raise for such a tree; Nokogiri writes
|
|
340
|
+
`\f` in text as U+FFFD (the data changes) and a comment's `--` or an
|
|
341
|
+
attribute value's `\f` as it stands (the output does not parse).
|
|
342
|
+
`create_cdata` refuses `]]>` and `create_processing_instruction` `?>`,
|
|
343
|
+
raising `ArgumentError` as the DOM's factories do; Nokogiri takes the PI
|
|
344
|
+
and writes `<?t a?>b?>`, which re-reads as another tree.
|
data/ext/makiri/rust/build.rs
CHANGED
|
@@ -262,6 +262,14 @@ fn main() {
|
|
|
262
262
|
.allowlist_function("lxb_dom_attr_set_name")
|
|
263
263
|
.allowlist_function("lxb_dom_document_create_element")
|
|
264
264
|
.allowlist_function("lxb_dom_element_create")
|
|
265
|
+
// An HTML element named as written (`DIV`, not `div`): its case-kept
|
|
266
|
+
// tag is Lexbor's `lxb_tag_append` (UNDECLARED_EXPORTS), and the rest
|
|
267
|
+
// is `lxb_dom_element_create`'s own steps with that tag.
|
|
268
|
+
.allowlist_function("lxb_dom_document_create_interface_noi")
|
|
269
|
+
.allowlist_function("lxb_dom_document_destroy_interface_noi")
|
|
270
|
+
.allowlist_function("lxb_ns_prefix_append")
|
|
271
|
+
.allowlist_type("lxb_tag_data_t")
|
|
272
|
+
.allowlist_type("lxb_ns_prefix_data_t")
|
|
265
273
|
.allowlist_function("lxb_dom_document_create_text_node")
|
|
266
274
|
.allowlist_function("lxb_dom_document_create_comment")
|
|
267
275
|
.allowlist_function("lxb_dom_document_create_processing_instruction")
|
|
@@ -281,6 +289,10 @@ fn main() {
|
|
|
281
289
|
.allowlist_function("lxb_css_selector_serialize_chain")
|
|
282
290
|
// The HTML serializers `lexbor::serialize` drives through its sink.
|
|
283
291
|
.allowlist_function("lxb_html_serialize_tree_cb")
|
|
292
|
+
// One node's own markup: Makiri drives the walk
|
|
293
|
+
// itself (`adapter::html::serialize`), so a <template> writes its
|
|
294
|
+
// contents and not its own children, as the HTML Standard says.
|
|
295
|
+
.allowlist_function("lxb_html_serialize_cb")
|
|
284
296
|
.allowlist_function("lxb_html_serialize_deep_cb")
|
|
285
297
|
.allowlist_function("lxb_html_serialize_pretty_tree_cb")
|
|
286
298
|
.allowlist_function("lxb_html_serialize_pretty_deep_cb")
|
|
@@ -382,6 +394,12 @@ const UNDECLARED_EXPORTS: &[(&str, &str, &str, &str)] = &[
|
|
|
382
394
|
"lxb_dom_attr_qualified_name_append",
|
|
383
395
|
"lexbor_hash_t *hash, const lxb_char_t *name, size_t length",
|
|
384
396
|
),
|
|
397
|
+
(
|
|
398
|
+
"lexbor/tag/tag.c",
|
|
399
|
+
"LXB_API const lxb_tag_data_t *",
|
|
400
|
+
"lxb_tag_append",
|
|
401
|
+
"lexbor_hash_t *hash, lxb_tag_id_t tag_id, const lxb_char_t *name, size_t length",
|
|
402
|
+
),
|
|
385
403
|
(
|
|
386
404
|
"lexbor/dom/interfaces/element.c",
|
|
387
405
|
"LXB_API lxb_status_t",
|
|
@@ -163,6 +163,12 @@ pub fn import_node(rb_self: Value, node_v: Value, deep: bool) -> Result<Value, E
|
|
|
163
163
|
// SAFETY: `doc` is the receiver's live document.
|
|
164
164
|
unsafe { import_copy(doc, src, deep, "import node") }
|
|
165
165
|
})??;
|
|
166
|
+
/* The copy carries whatever its source held. */
|
|
167
|
+
if crate::bridge::wrapper::may_hold_template_children(
|
|
168
|
+
crate::bridge::wrapper::keepalive_document(node_v)?,
|
|
169
|
+
) {
|
|
170
|
+
crate::bridge::wrapper::note_template_children(rb_self);
|
|
171
|
+
}
|
|
166
172
|
wrap_html_node(imp, rb_self)
|
|
167
173
|
}
|
|
168
174
|
|
|
@@ -396,6 +396,15 @@ fn release_from_tree(node: HtmlNodeMut<'_>) {
|
|
|
396
396
|
pub fn insert(this: &HtmlSelf, rb_incoming: Value, place: Place) -> Result<Value, Error> {
|
|
397
397
|
let target = edit(this)?.node()?;
|
|
398
398
|
let (key, incoming_doc) = html_node_key(rb_incoming)?;
|
|
399
|
+
/* A child placed under a template is one of its own, not its contents
|
|
400
|
+
* (see `HtmlParsed::note_template_children`). */
|
|
401
|
+
let parent_after = match place {
|
|
402
|
+
Place::Child => Some(target.node()),
|
|
403
|
+
Place::Before | Place::After | Place::Replace => target.node().parent(),
|
|
404
|
+
};
|
|
405
|
+
if parent_after.is_some_and(HtmlNode::is_html_template) {
|
|
406
|
+
crate::bridge::wrapper::note_template_children(this.document);
|
|
407
|
+
}
|
|
399
408
|
/* The argument is relinked too - `place` changes its parent and siblings, and
|
|
400
409
|
* an adoption removes it from its own document - so a frozen argument is a
|
|
401
410
|
* frozen node being modified. The receiver check alone let it through, which
|
|
@@ -419,6 +428,10 @@ pub fn insert(this: &HtmlSelf, rb_incoming: Value, place: Place) -> Result<Value
|
|
|
419
428
|
if !adopted {
|
|
420
429
|
return Ok(rb_incoming);
|
|
421
430
|
}
|
|
431
|
+
/* The copy carries whatever its source held. */
|
|
432
|
+
if crate::bridge::wrapper::may_hold_template_children(incoming_doc) {
|
|
433
|
+
crate::bridge::wrapper::note_template_children(this.document);
|
|
434
|
+
}
|
|
422
435
|
adopt_release(rb_incoming)?;
|
|
423
436
|
wrap_html_node(placed, this.document)
|
|
424
437
|
}
|
|
@@ -563,12 +576,6 @@ pub fn create_element<'d>(doc: HtmlDoc<'d>, name: &RubyText) -> Option<RawNode>
|
|
|
563
576
|
doc.create_element(name.as_bytes()).map(RawNode::from)
|
|
564
577
|
}
|
|
565
578
|
|
|
566
|
-
/// The refusal of an HTML-namespace name Lexbor would make as another element
|
|
567
|
-
/// (`HtmlDoc::misreads_html_name`), from `create_element_ns` and the
|
|
568
|
-
/// XML-to-HTML import alike.
|
|
569
|
-
pub const HTML_NAME_CASE: &str = "cannot make an HTML element whose upper-case name \
|
|
570
|
-
lower-cases to a known element (BR, SCRIPT, ...): it would be made as that element";
|
|
571
|
-
|
|
572
579
|
/// A new element named `local` in namespace `ns` ("" = none), with `prefix`
|
|
573
580
|
/// ("" = none) - the DOM's createElementNS, once the caller has validated and
|
|
574
581
|
/// split the name.
|
|
@@ -895,6 +895,25 @@ pub fn ensure_document_mutable(rb_doc: Value) -> Result<(), Error> {
|
|
|
895
895
|
|
|
896
896
|
/// Drop the DOM and text indexes so the next query rebuilds them. An XML
|
|
897
897
|
/// Document keeps none.
|
|
898
|
+
/// Record that `rb_doc` may now hold a `<template>` with children of its own
|
|
899
|
+
/// (`HtmlParsed::note_template_children`): an edit placed one under a
|
|
900
|
+
/// template, or a copy came from a document that may hold one.
|
|
901
|
+
pub fn note_template_children(rb_doc: Value) {
|
|
902
|
+
if let Content::Html(_) = doc_content_known(rb_doc) {
|
|
903
|
+
with_html_parsed_known(rb_doc, HtmlParsed::note_template_children);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/// Whether `rb_doc` may hold a `<template>` with children of its own. True for
|
|
908
|
+
/// anything that is no parsed HTML document, which the template-aware
|
|
909
|
+
/// serializer is right for too.
|
|
910
|
+
pub fn may_hold_template_children(rb_doc: Value) -> bool {
|
|
911
|
+
match doc_content_known(rb_doc) {
|
|
912
|
+
Content::Html(_) => with_html_parsed_known(rb_doc, |p| p.may_hold_template_children()),
|
|
913
|
+
_ => true,
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
898
917
|
pub fn invalidate_indexes(rb_doc: Value) {
|
|
899
918
|
if let Content::Html(_) = doc_content_known(rb_doc) {
|
|
900
919
|
with_html_parsed_known(rb_doc, HtmlParsed::invalidate_indexes);
|
|
@@ -209,7 +209,7 @@ fn xml_mut_error(st: MutError) -> Error {
|
|
|
209
209
|
return crate::bridge::ruby::arg_error("not a well-formed XML name");
|
|
210
210
|
}
|
|
211
211
|
MutError::BadDomName(why) => return crate::bridge::ruby::arg_error(why),
|
|
212
|
-
MutError::
|
|
212
|
+
MutError::InvalidCharacter(why) => return crate::bridge::ruby::arg_error(why),
|
|
213
213
|
MutError::UnboundNs => "namespace prefix is not bound in this scope",
|
|
214
214
|
MutError::Type => "operation unsupported for this node type",
|
|
215
215
|
MutError::Cycle => "cannot insert a node into its own subtree",
|
|
@@ -244,7 +244,6 @@ namespace (only xmlns=\"\" undeclares, and only the default)"
|
|
|
244
244
|
"the namespace does not fit the qualified name (a prefix needs a namespace; \
|
|
245
245
|
xml and xmlns take only their own)"
|
|
246
246
|
}
|
|
247
|
-
MutError::HtmlNameCase => crate::bridge::html::HTML_NAME_CASE,
|
|
248
247
|
MutError::Internal => "internal error mutating XML (no document)",
|
|
249
248
|
/* The document's own budget, not the machine's memory - so the same
|
|
250
249
|
* exception a parse raises for the same cause. */
|
|
@@ -364,7 +363,7 @@ pub fn verified_text(v: Value, what: &str) -> Result<RubyText, Error> {
|
|
|
364
363
|
}
|
|
365
364
|
|
|
366
365
|
/// `t`, unless it is too long for an XML node's `u32` span.
|
|
367
|
-
fn fits_xml_node(t:
|
|
366
|
+
fn fits_xml_node<T: core::ops::Deref<Target = str>>(t: T) -> Result<T, Error> {
|
|
368
367
|
if u32::try_from(t.len()).is_err() {
|
|
369
368
|
return Err(makiri_error("string too long for an XML node (max 4 GiB)"));
|
|
370
369
|
}
|
|
@@ -378,6 +377,26 @@ pub fn verified_name(v: Value, what: &str) -> Result<RubyText, Error> {
|
|
|
378
377
|
fits_xml_node(crate::bridge::string::ruby_verified_name(v, what)?)
|
|
379
378
|
}
|
|
380
379
|
|
|
380
|
+
/// [`verified_text`] for DATA - text, comment, CDATA and PI content, an
|
|
381
|
+
/// attribute value: valid UTF-8, and NUL allowed, as the DOM allows it there
|
|
382
|
+
/// ([`crate::bridge::string::ruby_verified_data`], what HTML's data takes).
|
|
383
|
+
/// XML cannot write U+0000, so the serializers refuse a tree holding one, as
|
|
384
|
+
/// they refuse any character XML has no `Char` for.
|
|
385
|
+
pub fn verified_data(v: Value, what: &str) -> Result<crate::bridge::string::RubyData, Error> {
|
|
386
|
+
fits_xml_node(crate::bridge::string::ruby_verified_data(v, what)?)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// [`verified_data`] for an optional argument: `nil` is `None`.
|
|
390
|
+
pub fn verified_data_opt(
|
|
391
|
+
v: Value,
|
|
392
|
+
what: &str,
|
|
393
|
+
) -> Result<Option<crate::bridge::string::RubyData>, Error> {
|
|
394
|
+
if v.is_nil() {
|
|
395
|
+
return Ok(None);
|
|
396
|
+
}
|
|
397
|
+
verified_data(v, what).map(Some)
|
|
398
|
+
}
|
|
399
|
+
|
|
381
400
|
/// [`verified_name`] for an optional argument: `nil` is `None`.
|
|
382
401
|
pub fn verified_name_opt(v: Value, what: &str) -> Result<Option<RubyText>, Error> {
|
|
383
402
|
if v.is_nil() {
|
|
@@ -209,6 +209,10 @@ pub fn set_content(_ruby: &Ruby, this: HtmlSelf, rb_text: Value) -> Result<Value
|
|
|
209
209
|
let edit = edit(&this)?;
|
|
210
210
|
let tv = ruby_verified_data(rb_text, "node content")?;
|
|
211
211
|
let node = edit.node()?;
|
|
212
|
+
/* A template's text is a child of its own, not its contents. */
|
|
213
|
+
if node.node().is_html_template() {
|
|
214
|
+
crate::bridge::wrapper::note_template_children(this.document);
|
|
215
|
+
}
|
|
212
216
|
crate::bridge::html::set_text_content(node, &tv)
|
|
213
217
|
.map_err(|_| makiri_error("failed to set node content"))?;
|
|
214
218
|
Ok(rb_text)
|
|
@@ -284,6 +288,9 @@ pub fn set_outer_html(_ruby: &Ruby, this: HtmlSelf, rb_html: Value) -> Result<Va
|
|
|
284
288
|
"outer_html= requires a node with a parent element",
|
|
285
289
|
));
|
|
286
290
|
};
|
|
291
|
+
if parent.node().is_html_template() {
|
|
292
|
+
crate::bridge::wrapper::note_template_children(this.document);
|
|
293
|
+
}
|
|
287
294
|
let staged = stage_fragment_in(parent, html)?;
|
|
288
295
|
node.place(staged, Place::Replace);
|
|
289
296
|
Ok(rb_html)
|
|
@@ -336,9 +343,6 @@ pub fn create_element_ns(
|
|
|
336
343
|
let ns = nv.as_ref().map_or(&b""[..], |n| n.as_bytes());
|
|
337
344
|
let (prefix, local) =
|
|
338
345
|
extract(ruby, ns, &qv, dom_name::valid_element_local_name, "element")?;
|
|
339
|
-
if doc.misreads_html_name(local, ns) {
|
|
340
|
-
return Err(makiri_error(crate::bridge::html::HTML_NAME_CASE));
|
|
341
|
-
}
|
|
342
346
|
created(
|
|
343
347
|
crate::bridge::html::create_element_ns(doc, local, ns, prefix),
|
|
344
348
|
rb_self,
|
|
@@ -23,9 +23,16 @@ fn pretty_opt(ruby: &Ruby, args: &[Value]) -> Result<bool, Error> {
|
|
|
23
23
|
|
|
24
24
|
/// The receiver's serialization as a UTF-8 String, or `Makiri::Error` on a
|
|
25
25
|
/// Lexbor status failure.
|
|
26
|
-
fn render(
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
fn render(
|
|
27
|
+
ruby: &Ruby,
|
|
28
|
+
document: Value,
|
|
29
|
+
node: RawNode,
|
|
30
|
+
deep: bool,
|
|
31
|
+
pretty: bool,
|
|
32
|
+
) -> Result<RString, Error> {
|
|
33
|
+
let aware = crate::bridge::wrapper::may_hold_template_children(document);
|
|
34
|
+
let buf = serialize(node, deep, pretty, aware)
|
|
35
|
+
.ok_or_else(|| makiri_error("HTML serialization failed"))?;
|
|
29
36
|
/* Lexbor emits UTF-8, so the String is tagged UTF-8 rather than built as
|
|
30
37
|
* binary and re-tagged. */
|
|
31
38
|
Ok(ruby.enc_str_new(buf.as_slice(), ruby.utf8_encoding()))
|
|
@@ -39,7 +46,7 @@ fn to_html(ruby: &Ruby, this: HtmlSelf, args: &[Value]) -> Result<RString, Error
|
|
|
39
46
|
* children: the deep serializer is the right one (the tree serializer
|
|
40
47
|
* rejects a fragment node). */
|
|
41
48
|
let deep = this.node().node_type() == NodeType::DocumentFragment;
|
|
42
|
-
render(ruby, this.raw(), deep, pretty)
|
|
49
|
+
render(ruby, this.document, this.raw(), deep, pretty)
|
|
43
50
|
})
|
|
44
51
|
}
|
|
45
52
|
|
|
@@ -56,7 +63,7 @@ fn inner_html(ruby: &Ruby, this: HtmlSelf, args: &[Value]) -> Result<RString, Er
|
|
|
56
63
|
.node()
|
|
57
64
|
.template_content()
|
|
58
65
|
.map_or(this.raw(), RawNode::from);
|
|
59
|
-
render(ruby, target, true, pretty)
|
|
66
|
+
render(ruby, this.document, target, true, pretty)
|
|
60
67
|
})
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -16,9 +16,9 @@ use crate::bridge::ruby::makiri_error;
|
|
|
16
16
|
use crate::bridge::string::namespace_arg;
|
|
17
17
|
|
|
18
18
|
use crate::bridge::xml::{
|
|
19
|
-
begin_edit, import_copy, incoming_node,
|
|
20
|
-
|
|
21
|
-
XmlSelf,
|
|
19
|
+
begin_edit, import_copy, incoming_node, verified_data, verified_data_opt, verified_name,
|
|
20
|
+
verified_name_opt, verified_text, verified_text_opt, with_arena_for_new_node,
|
|
21
|
+
wrap_xml_node as wrap, xml_mut_result, Editing, XmlSelf,
|
|
22
22
|
};
|
|
23
23
|
use crate::init::CLASS_XML_DOCUMENT;
|
|
24
24
|
use crate::xml::dom_name::split_loose_dom_name;
|
|
@@ -63,7 +63,7 @@ pub fn aset(_ruby: &Ruby, this: XmlSelf, name: Value, val: Value) -> Result<Valu
|
|
|
63
63
|
crate::bridge::ruby::entry(|| {
|
|
64
64
|
let edit = element_for(this)?;
|
|
65
65
|
let nv = verified_name(name, "attribute name")?;
|
|
66
|
-
let vv =
|
|
66
|
+
let vv = verified_data(val, "attribute value")?;
|
|
67
67
|
let (name, value) = (nv.as_bytes(), vv.as_bytes());
|
|
68
68
|
xml_mut_result(edit.with_arena(|d, n| mutate::set_attribute(d, n, name, value))?)?;
|
|
69
69
|
Ok(val)
|
|
@@ -81,7 +81,7 @@ pub fn set_attribute_ns(
|
|
|
81
81
|
crate::bridge::ruby::entry(|| {
|
|
82
82
|
let edit = element_for(this)?;
|
|
83
83
|
let qv = verified_name(qname, "attribute qualified name")?;
|
|
84
|
-
let vv =
|
|
84
|
+
let vv = verified_data(val, "attribute value")?;
|
|
85
85
|
let nv = namespace_arg(ns, "namespace")?;
|
|
86
86
|
let (ns, qname, value) = (
|
|
87
87
|
nv.as_ref().map_or(&b""[..], |n| n.as_bytes()),
|
|
@@ -107,7 +107,7 @@ pub fn set_loose_dom_attribute(
|
|
|
107
107
|
crate::bridge::ruby::entry(|| {
|
|
108
108
|
let edit = element_for(this)?;
|
|
109
109
|
let nv = verified_name(name, "attribute name")?;
|
|
110
|
-
let vv =
|
|
110
|
+
let vv = verified_data(val, "attribute value")?;
|
|
111
111
|
let (name, value) = (nv.as_bytes(), vv.as_bytes());
|
|
112
112
|
xml_mut_result(
|
|
113
113
|
edit.with_arena(|d, n| mutate::set_loose_dom_attribute(d, n, name, value))?,
|
|
@@ -157,7 +157,7 @@ pub fn delete(_ruby: &Ruby, this: XmlSelf, name: Value) -> Result<Value, Error>
|
|
|
157
157
|
pub fn set_content(_ruby: &Ruby, this: XmlSelf, text: Value) -> Result<Value, Error> {
|
|
158
158
|
crate::bridge::ruby::entry(|| {
|
|
159
159
|
let edit = begin_edit(this)?;
|
|
160
|
-
let tv =
|
|
160
|
+
let tv = verified_data(text, "node content")?;
|
|
161
161
|
let bytes = tv.as_bytes();
|
|
162
162
|
xml_mut_result(edit.with_arena(|d, n| mutate::set_content(d, n, bytes))?)?;
|
|
163
163
|
Ok(text)
|
|
@@ -248,7 +248,7 @@ pub fn create_element(ruby: &Ruby, rb_self: Value, args: &[Value]) -> Result<Val
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
let nv = verified_name(name, "element name")?;
|
|
251
|
-
let cv =
|
|
251
|
+
let cv = verified_data_opt(content, "element content")?;
|
|
252
252
|
let name = nv.as_bytes();
|
|
253
253
|
let el = xml_mut_result(with_arena_for_new_node(rb_self, |d| {
|
|
254
254
|
mutate::new_element(d, name)
|
|
@@ -342,7 +342,7 @@ fn create_chardata(
|
|
|
342
342
|
type_: ArenaKind,
|
|
343
343
|
what: &str,
|
|
344
344
|
) -> Result<Value, Error> {
|
|
345
|
-
let tv =
|
|
345
|
+
let tv = verified_data(text, what)?;
|
|
346
346
|
let bytes = tv.as_bytes();
|
|
347
347
|
let n = xml_mut_result(with_arena_for_new_node(rb_self, |d| {
|
|
348
348
|
mutate::new_chardata(d, type_, bytes)
|
|
@@ -367,7 +367,7 @@ pub fn create_cdata(_ruby: &Ruby, rb_self: Value, t: Value) -> Result<Value, Err
|
|
|
367
367
|
pub fn create_pi(_ruby: &Ruby, rb_self: Value, target: Value, data: Value) -> Result<Value, Error> {
|
|
368
368
|
crate::bridge::ruby::entry(|| {
|
|
369
369
|
let tg = verified_name(target, "PI target")?;
|
|
370
|
-
let dt =
|
|
370
|
+
let dt = verified_data(data, "PI data")?;
|
|
371
371
|
let (target, data) = (tg.as_bytes(), dt.as_bytes());
|
|
372
372
|
let pi = xml_mut_result(with_arena_for_new_node(rb_self, |d| {
|
|
373
373
|
mutate::new_pi(d, target, data)
|
|
@@ -40,6 +40,10 @@ fn failure_error(f: Failure, verb: &str) -> Error {
|
|
|
40
40
|
"cannot {verb} XML containing a DOM-loose doctype (a name or id XML cannot write)"
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
|
+
Failure::UnwritableData => format!(
|
|
44
|
+
"cannot {verb} XML containing character data XML cannot hold (a character \
|
|
45
|
+
outside XML's, -- in a comment, or ?> in a processing instruction)"
|
|
46
|
+
),
|
|
43
47
|
Failure::ForbiddenDeclaration => format!(
|
|
44
48
|
"cannot {verb} XML containing a namespace declaration XML forbids \
|
|
45
49
|
(such as xmlns:p=\"\", held as set_attribute_ns made it)"
|
|
@@ -74,9 +78,9 @@ fn failure_error(f: Failure, verb: &str) -> Error {
|
|
|
74
78
|
or insert the node where it is declared)"
|
|
75
79
|
),
|
|
76
80
|
Failure::NamespaceMismatch => format!(
|
|
77
|
-
"cannot {verb} XML
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
"cannot {verb} XML where one prefix would have to mean two namespaces on an \
|
|
82
|
+
element, or an unprefixed attribute has a namespace (canonical form invents no \
|
|
83
|
+
prefix); to_xml writes such a tree"
|
|
80
84
|
),
|
|
81
85
|
};
|
|
82
86
|
makiri_error(msg)
|
|
@@ -110,7 +110,7 @@ pub type LxbDoc = lxb_dom_document_t;
|
|
|
110
110
|
/* Every other Lexbor function the crate calls - the `_noi` twins of the
|
|
111
111
|
* `lxb_inline` accessors included - is GENERATED by build.rs from the header
|
|
112
112
|
* that declares it, so its signature is Lexbor's own and a change to it fails
|
|
113
|
-
* the build. These
|
|
113
|
+
* the build. These four are the exception: exported by Lexbor but declared
|
|
114
114
|
* in NO header, so bindgen cannot see them. Lexbor's own sources
|
|
115
115
|
* forward-declare them exactly this way. What stands in for the header is
|
|
116
116
|
* build.rs's `UNDECLARED_EXPORTS`, which reads each C DEFINITION and fails the
|
|
@@ -121,7 +121,10 @@ pub type LxbDoc = lxb_dom_document_t;
|
|
|
121
121
|
* `lxb_dom_attr_qualified_name_append` interns a name CASE-PRESERVING, which is
|
|
122
122
|
* what lets createDocumentType keep its case where Lexbor's own create()
|
|
123
123
|
* lowercases. `lxb_dom_element_qualified_name_set` does the same for an
|
|
124
|
-
* element's name.
|
|
124
|
+
* element's name. `lxb_tag_append` interns a TAG name case-preserving, where
|
|
125
|
+
* every Lexbor create lowercases it first (`lxb_tag_append_lower`): an HTML
|
|
126
|
+
* element named `DIV` by createElementNS gets a tag of its own rather than
|
|
127
|
+
* `div`'s, and so is the unknown element the DOM makes rather than a div. */
|
|
125
128
|
extern "C" {
|
|
126
129
|
pub fn lxb_dom_attr_set_name_ns(
|
|
127
130
|
attr: *mut LxbAttr,
|
|
@@ -146,6 +149,12 @@ extern "C" {
|
|
|
146
149
|
lname: *const u8,
|
|
147
150
|
lname_len: usize,
|
|
148
151
|
) -> u32;
|
|
152
|
+
pub fn lxb_tag_append(
|
|
153
|
+
hash: *mut lexbor_hash_t,
|
|
154
|
+
tag_id: lxb_tag_id_t,
|
|
155
|
+
name: *const u8,
|
|
156
|
+
length: usize,
|
|
157
|
+
) -> *const lxb_tag_data_t;
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
/* The C allocator, which `lexbor::memory` wraps for Lexbor. Written here
|