From c50a790908a28e0dd5166479ccfefbab3bd74b6e Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 27 May 2023 11:33:22 +0200 Subject: [PATCH 1/3] Change to serialize as HTML instead of XML Closes GH-21. --- packages/rehype-dom-stringify/lib/index.js | 49 ++++++++++++++++----- packages/rehype-dom-stringify/package.json | 3 +- test.js | 51 +++++++++++++++++----- 3 files changed, 80 insertions(+), 23 deletions(-) diff --git a/packages/rehype-dom-stringify/lib/index.js b/packages/rehype-dom-stringify/lib/index.js index 6d82d3d..776dcf1 100644 --- a/packages/rehype-dom-stringify/lib/index.js +++ b/packages/rehype-dom-stringify/lib/index.js @@ -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} @@ -24,13 +21,43 @@ export default function stringify(options) { /** @type {import('unified').CompilerFunction} */ 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 '' + } + + // Element. + if ('outerHTML' in node) { + return node.outerHTML + } + + // Comment, text, fragment. + if ('textContent' in node) { + const div = document.createElement('div') + div.append(node) + return div.innerHTML + /* c8 ignore next 4 */ + } + + // ? + return '' +} diff --git a/packages/rehype-dom-stringify/package.json b/packages/rehype-dom-stringify/package.json index a593535..c1c1ae7 100644 --- a/packages/rehype-dom-stringify/package.json +++ b/packages/rehype-dom-stringify/package.json @@ -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, diff --git a/test.js b/test.js index a530caf..c07420e 100644 --- a/test.js +++ b/test.js @@ -93,10 +93,25 @@ test('parse', async (t) => { .data('settings', {fragment: false}) .processSync('Hi

Hello world!') .toString(), - 'Hi

Hello world!

', + 'Hi

Hello world!

', 'should stringify a complete document' ) + assert.equal( + processor() + .data('settings', {fragment: false}) + .processSync('') + .toString(), + '', + 'should stringify a doctype' + ) + + assert.equal( + processor().processSync('').toString(), + '', + 'should support empty documents' + ) + assert.equal( processor() .data('settings', {fragment: true}) @@ -119,7 +134,7 @@ test('parse', async (t) => { processor() .use(rehypeDomStringify, {namespace: 'http://www.w3.org/2000/svg'}) .stringify(u('root', [s('#foo.bar', s('circle'))])), - '', + '', 'should support SVG' ) @@ -135,7 +150,7 @@ test('parse', async (t) => { ]) ]) ), - '
Alpha
', + '
Alpha
', 'should support HTML in SVG' ) @@ -151,7 +166,7 @@ test('parse', async (t) => { ]) ]) ), - '
Alpha
', + '
Alpha
', 'should support HTML in SVG in HTML' ) @@ -159,7 +174,7 @@ test('parse', async (t) => { processor() .use(rehypeDomStringify, {namespace: 'https://example.com'}) .stringify(u('root', [h('example', 'Alpha')])), - 'Alpha', + 'Alpha', 'should stringify namespaced elements' ) }) @@ -170,7 +185,7 @@ test('parse', async (t) => { .data('settings', {fragment: false}) .processSync('Hi

Hello world!') .toString(), - 'Hi

Hello world!

', + 'Hi

Hello world!

', 'should parse a complete document' ) @@ -194,7 +209,7 @@ test('parse', async (t) => { assert.equal( rehypeDom().processSync('').toString(), - '', + '', 'should support boolean attributes' ) @@ -213,13 +228,29 @@ test('parse', async (t) => { ` ) .toString(), - ` - - + ` + + `, 'should support svg' ) + assert.equal( + String( + rehypeDom().processSync(``) + ), + ``, + 'should process text in `style` correctly' + ) + assert.equal( rehypeDom() .use(rehypeHighlight) From aaae592d852a4263f5f0dda04b0bb3acbc2c0b3e Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 27 May 2023 11:36:18 +0200 Subject: [PATCH 2/3] Remove unneeded code --- test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test.js b/test.js index c07420e..f990027 100644 --- a/test.js +++ b/test.js @@ -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 From 02c2683c0cb3b7135fbb56c58d9ac4d599ea9eb9 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Wed, 31 May 2023 11:56:45 +0200 Subject: [PATCH 3/3] Refactor to simplify code --- packages/rehype-dom-stringify/lib/index.js | 19 ++++--------------- test.js | 8 ++++---- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/packages/rehype-dom-stringify/lib/index.js b/packages/rehype-dom-stringify/lib/index.js index 776dcf1..4ab3ed7 100644 --- a/packages/rehype-dom-stringify/lib/index.js +++ b/packages/rehype-dom-stringify/lib/index.js @@ -45,19 +45,8 @@ function serialize(node) { return '' } - // Element. - if ('outerHTML' in node) { - return node.outerHTML - } - - // Comment, text, fragment. - if ('textContent' in node) { - const div = document.createElement('div') - div.append(node) - return div.innerHTML - /* c8 ignore next 4 */ - } - - // ? - return '' + // Comment, element, fragment, text. + const template = document.createElement('template') + template.content.append(node) + return template.innerHTML } diff --git a/test.js b/test.js index f990027..5fe02f0 100644 --- a/test.js +++ b/test.js @@ -92,7 +92,7 @@ test('parse', async (t) => { .data('settings', {fragment: false}) .processSync('Hi

Hello world!') .toString(), - 'Hi

Hello world!

', + 'Hi

Hello world!

', 'should stringify a complete document' ) @@ -101,13 +101,13 @@ test('parse', async (t) => { .data('settings', {fragment: false}) .processSync('') .toString(), - '', + '', 'should stringify a doctype' ) assert.equal( processor().processSync('').toString(), - '', + '', 'should support empty documents' ) @@ -184,7 +184,7 @@ test('parse', async (t) => { .data('settings', {fragment: false}) .processSync('Hi

Hello world!') .toString(), - 'Hi

Hello world!

', + 'Hi

Hello world!

', 'should parse a complete document' )