Skip to content

Commit

Permalink
Change to serialize as HTML instead of XML
Browse files Browse the repository at this point in the history
Closes GH-22.
  • Loading branch information
wooorm committed Sep 4, 2023
1 parent 6d41f5f commit 8ab866e
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 22 deletions.
38 changes: 27 additions & 11 deletions packages/rehype-dom-stringify/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
*/

import {toDom} from 'hast-util-to-dom'
import {webNamespaces} from 'web-namespaces'

const htmlXmlnsExpression = new RegExp(` xmlns="${webNamespaces.html}"`, 'g')

/**
* @this {import('unified').Processor}
Expand All @@ -24,13 +21,32 @@ export default function stringify(options) {

/** @type {import('unified').CompilerFunction<Root, string>} */
function compiler(tree) {
const node = toDom(tree, settings)
const serialized = new XMLSerializer().serializeToString(node)

// XMLSerializer puts xmlns on root elements (typically the document
// element, but in case of a fragment all of the fragments children).
// We’re using the DOM, and we focus on HTML, so we can always remove HTML
// XMLNS attributes (HTML inside SVG does not need to have an XMLNS).
return serialized.replace(htmlXmlnsExpression, '')
return serialize(toDom(tree, settings))
}
}

/**
* Serialize DOM nodes.
*
* @param {XMLDocument | DocumentFragment | Text | DocumentType | Comment | Element} node
* @returns {string}
*/
function serialize(node) {
// Document.
if ('doctype' in node) {
const doctype = node.doctype ? serialize(node.doctype) : ''
const docelem = serialize(node.documentElement)
return doctype + docelem
}

// Doctype.
if ('publicId' in node) {
// We don’t support non-HTML doctypes.
return '<DOCTYPE html>'
}

// Comment, element, fragment, text.
const template = document.createElement('template')
template.content.append(node)
return template.innerHTML
}
3 changes: 1 addition & 2 deletions packages/rehype-dom-stringify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
"@types/hast": "^2.0.0",
"@types/web": "^0.0.99",
"hast-util-to-dom": "^3.0.0",
"unified": "^10.0.0",
"web-namespaces": "^2.0.0"
"unified": "^10.0.0"
},
"scripts": {},
"xo": false,
Expand Down
48 changes: 39 additions & 9 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {rehypeDom} from 'rehype-dom'
const {window} = new JSDOM('')

// The globals needed by `rehype-dom`.
global.XMLSerializer = window.XMLSerializer
global.document = window.document
global.DOMParser = window.DOMParser

Expand Down Expand Up @@ -97,6 +96,21 @@ test('parse', async (t) => {
'should stringify a complete document'
)

assert.equal(
processor()
.data('settings', {fragment: false})
.processSync('<!doctype html>')
.toString(),
'<DOCTYPE html><html><head></head><body></body></html>',
'should stringify a doctype'
)

assert.equal(
processor().processSync('<!doctype html>').toString(),
'<DOCTYPE html><html><head></head><body></body></html>',
'should support empty documents'
)

assert.equal(
processor()
.data('settings', {fragment: true})
Expand All @@ -119,7 +133,7 @@ test('parse', async (t) => {
processor()
.use(rehypeDomStringify, {namespace: 'http://www.w3.org/2000/svg'})
.stringify(u('root', [s('#foo.bar', s('circle'))])),
'<g xmlns="http://www.w3.org/2000/svg" id="foo" class="bar"><circle/></g>',
'<g id="foo" class="bar"><circle></circle></g>',
'should support SVG'
)

Expand All @@ -135,7 +149,7 @@ test('parse', async (t) => {
])
])
),
'<svg xmlns="http://www.w3.org/2000/svg"><foreignObject><div>Alpha</div></foreignObject></svg>',
'<svg><foreignObject><div xmlns="http://www.w3.org/1999/xhtml">Alpha</div></foreignObject></svg>',
'should support HTML in SVG'
)

Expand All @@ -151,15 +165,15 @@ test('parse', async (t) => {
])
])
),
'<div><svg xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"><foreignObject><div>Alpha</div></foreignObject></svg></div>',
'<div><svg xmlns="http://www.w3.org/2000/svg"><foreignObject><div xmlns="http://www.w3.org/1999/xhtml">Alpha</div></foreignObject></svg></div>',
'should support HTML in SVG in HTML'
)

assert.equal(
processor()
.use(rehypeDomStringify, {namespace: 'https://example.com'})
.stringify(u('root', [h('example', 'Alpha')])),
'<example xmlns="https://example.com">Alpha</example>',
'<example>Alpha</example>',
'should stringify namespaced elements'
)
})
Expand Down Expand Up @@ -194,7 +208,7 @@ test('parse', async (t) => {

assert.equal(
rehypeDom().processSync('<input type="checkbox" checked />').toString(),
'<input type="checkbox" checked="" />',
'<input type="checkbox" checked="">',
'should support boolean attributes'
)

Expand All @@ -213,13 +227,29 @@ test('parse', async (t) => {
</svg>`
)
.toString(),
`<svg xmlns="http://www.w3.org/2000/svg" width="230" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="60" cy="60" r="50" fill="red"/>
<circle cx="170" cy="60" r="50" fill="green"/>
`<svg width="230" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="60" cy="60" r="50" fill="red"></circle>
<circle cx="170" cy="60" r="50" fill="green"></circle>
</svg>`,
'should support svg'
)

assert.equal(
String(
rehypeDom().processSync(`<style>
body > style {
width: 100px;
}
</style>`)
),
`<style>
body > style {
width: 100px;
}
</style>`,
'should process text in `style` correctly'
)

assert.equal(
rehypeDom()
.use(rehypeHighlight)
Expand Down

0 comments on commit 8ab866e

Please sign in to comment.