diff --git a/docs/docs/parser.md b/docs/docs/parser.md index d48a565..88e65ae 100644 --- a/docs/docs/parser.md +++ b/docs/docs/parser.md @@ -300,7 +300,7 @@ Let's see what parameters we have: #

A Text

``` -* **`fragment_context`** - Parses a fragment as if it were the contents of this context element (like setting `innerHTML` on it). Pass a tag name such as `"td"`, or a `QualName` for an element in another namespace, such as `QualName("svg", "svg")`; a name without a namespace is an HTML element. Implies `full_document=False`. default: None (fragments are parsed in a `body` context). +* **`fragment_context`** - Parses a fragment as if it were the contents of this context element (like setting `innerHTML` on it). Pass a tag name such as `"td"`, or a `QualName` for an element in another namespace, such as `QualName("svg", "svg")`; a name without a namespace is an HTML element. Implies `full_document=False`. The parsed tree is serialized as the contents of the context element, so that, for example, text parsed in a `script` context isn't escaped. default: None (fragments are parsed in a `body` context). === "\"tbody\"" diff --git a/python/tests/test_dom.py b/python/tests/test_dom.py index 7220e41..dc8d2be 100644 --- a/python/tests/test_dom.py +++ b/python/tests/test_dom.py @@ -546,3 +546,14 @@ def test_attach_beside_orphan(): orphan.create_element("b", ordering=ordering) assert dom.serialize(indent=0) == "

x

" + + +def test_serializer_children_raw_text(): + dom = markupever.parse( + "

a < b

", + markupever.HtmlOptions(full_document=False), + ) + script, style, p = dom.root().children() + assert script.serialize(indent=0, include_self=False) == "if (a < b && c) {}" + assert style.serialize(indent=0, include_self=False) == "a > b { }" + assert p.serialize(indent=0, include_self=False) == "a < b" diff --git a/python/tests/test_parser.py b/python/tests/test_parser.py index 4672305..fe3d2d8 100644 --- a/python/tests/test_parser.py +++ b/python/tests/test_parser.py @@ -139,3 +139,32 @@ def test_meta_content_ending_in_charset(): f'

x

' ) assert dom.select_one("p").text() == "x" + + +def test_fragment_context_serialize(): + # A fragment is serialized as the children of its context element, so that it round-trips. + src = 'console.log("
") &' + for context, text, html in [ + ("script", src, src), + ("style", src, src), + ("xmp", src, src), + ("plaintext", src, src), + ("textarea", 'console.log("
") &', 'console.log("<br>") &'), + ("title", 'console.log("
") &', 'console.log("<br>") &'), + ]: + options = markupever.HtmlOptions(fragment_context=context) + dom = markupever.parse(src, options) + assert dom.root().text() == text + assert dom.serialize() == html + assert dom.root().serialize(include_self=False) == html + assert markupever.parse(dom.serialize(), options).root().text() == text + + # Text in other contexts is escaped. + dom = markupever.parse("a < b", markupever.HtmlOptions(full_document=False)) + assert dom.serialize() == "a < b" + + # Descendants of the fragment's nodes are serialized as usual. + dom = markupever.parse( + "

a < b

", markupever.HtmlOptions(fragment_context="div") + ) + assert dom.serialize() == "

a < b

" diff --git a/src/parser.rs b/src/parser.rs index f563b18..f3f2cf7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -482,10 +482,21 @@ pub fn serialize( let serializer = ::treedom::Serializer::new(&dom, node.id, indent); - let traversal_scope = if include_self { + let value = dom.get(node.id).unwrap().value(); + + let traversal_scope = if let Some(context) = value.document().and(dom.fragment_context()) { + // A fragment's nodes are the children of the document node, but were parsed as + // children of the context element, so serialize them as its children: for example, + // text parsed in a