Repository navigation
Serialization
Four methods turn a node back into a string. Three of them write markup following the HTML fragment serialization algorithm, section 13.3 of the HTML Standard, which is what a browser's innerHTML and outerHTML getters run. The fourth gives the text. The code is in crates/parsec/src/serialize.rs.
On this page: inner_html · outer_html · Escaping · html_alone · text · What's not here yet
| Method | Gives |
|---|---|
inner_html(&self, id: NodeId) -> String |
The markup for id's children, as innerHTML returns it. |
outer_html(&self, id: NodeId) -> String |
The markup for id and its children, as outerHTML returns it. |
html_alone(&self, id: NodeId) -> String |
For an element, its start tag alone. For anything else, the same as outer_html. |
text(&self, id: NodeId) -> String |
The text of id and everything under it, in tree order, close to textContent. |
All four panic on an id that's doc.len() or more, and all four allocate a new String each call. None of them recurses, so a tree tens of thousands of levels deep serializes on a small thread stack.
Writes each child of id in order.
- For a
DocumentorFragmentnode, that's the whole tree:doc.inner_html(doc.root())is how you write out a page. - For an HTML
<template>, it writes the template's contents (and then any children it has, which it only has if you added them or calledadopt_template_contents). - For a void HTML element (
area,base,basefont,bgsound,br,col,embed,frame,hr,img,input,keygen,link,meta,param,source,track,wbr), it's always"", even if an edit gave it children. - For text, comments, doctypes and processing instructions, it's
"".
Writes the node itself, then its children, then its end tag:
| Node | Written as |
|---|---|
| HTML element |
<name attr="value" ...>, children, </name>. Void elements get no end tag and no children. |
| SVG or MathML element | Start tag, children, end tag, always: an empty <clipPath/> comes out as <clipPath></clipPath>. |
| Text | The text, escaped (see below), or as it is inside raw text elements. |
| Comment | <!--text--> |
| Processing instruction | <?target data?> |
| CDATA section | <![CDATA[text]]> |
| Doctype |
<!DOCTYPE name>. The public and system identifiers aren't written, as the spec says. |
Document or Fragment
|
Its children, the same as inner_html. |
Element and attribute names come out as written: SVG's clipPath and viewBox, MathML's definitionURL, and prefixed names such as xlink:href and xml:lang (see attrs_written). Attribute values are always in double quotes. Comments and processing instructions are written as they are, without escaping.
let doc = parsec::parse(
r#"<body><svg viewBox="0 0 1 1"><clipPath/></svg><br><!--c--><?t d?>"#,
false,
);
assert_eq!(
doc.inner_html(doc.root()),
r#"<html><head></head><body><svg viewBox="0 0 1 1"><clipPath></clipPath></svg><br><!--c--><?t d?></body></html>"#
);Text and attribute values are escaped as the spec's "escaping a string" says:
- In text:
&becomes&, U+00A0 NO-BREAK SPACE becomes ,<becomes<and>becomes>. - In attribute values: the same four, plus
"becomes".
Nothing else is escaped. Non-ASCII text is written as UTF-8, not as references.
Text whose parent is a raw text element is written as it is, because the parser never decoded it in the first place. Those parents are the HTML elements style, script, xmp, iframe, noembed, noframes and plaintext, and noscript when the document was parsed with scripting on.
let doc = parsec::parse(
"<p title='a\"b<c>&d'>1 < 2 & 3 !</p><script>if (a < b && c) {}</script>",
false,
);
assert_eq!(
doc.inner_html(doc.root()),
"<html><head></head><body>\
<p title=\"a"b<c>&d\">1 < 2 & 3 !</p>\
<script>if (a < b && c) {}</script></body></html>"
);Warning
A text node you created and put inside a <script> is written unescaped too. If you put </script> in it, the output won't parse back the same way; that's true in a browser as well.
For an element, the start tag and nothing else: name and attributes, escaped as above. For any other node it's the same as outer_html, so for the root it writes the whole document.
use parsec::css::SelectorList;
let doc = parsec::parse(r#"<p title="a&b">x & y</p>"#, false);
let p = SelectorList::parse("p").unwrap().select(&doc, doc.root())[0];
assert_eq!(doc.html_alone(p), r#"<p title="a&b">"#);
assert_eq!(doc.outer_html(p), r#"<p title="a&b">x & y</p>"#);
let text = doc.first_child(p).unwrap();
assert_eq!(doc.html_alone(text), "x & y");Every text node at or under id, joined in tree order. Character references are decoded, nothing is escaped, and whitespace is kept exactly as parsed.
Where it differs from the DOM's textContent:
- Text inside
<script>and<style>is included, astextContentincludes it. If you want visible text only, skip those elements yourself. - Template contents aren't included, as in the DOM; they aren't under the template.
- CDATA sections aren't included. The HTML parser never makes them, so this only matters for trees you built.
- On a comment or processing instruction it gives
"", wheretextContentgives the comment's data. - On the document it gives the whole page's text, where
textContentgives null.
let doc = parsec::parse("<p>a<b>b</b>c<!--no--></p><script>x=1</script>", false);
assert_eq!(doc.text(doc.root()), "abcx=1");- Pretty-printed output, and the html5lib tree format the tree-construction tests use. Planned in #9, with the html5lib serializer tests.
- Serializing straight to a
Writeor afmt::Formatterwithout building aString. - XML serialization. An
Ns::Xmltree is written with the HTML rules above.
- The document tree, for the nodes being written.
- Editing, for changing the tree first.
-
Parsing, for why the scripting flag matters to
<noscript>.
Parsec is Apache-2.0 and not yet released; these pages describe main. Found something wrong here? Open an issue or ask in Discussions.
Repository · Issues · Discussions · Roadmap · Security policy · Licence