Skip to content

Commit

Permalink
feat: serializeSafeHTML
Browse files Browse the repository at this point in the history
  • Loading branch information
holtwick committed Jan 8, 2024
1 parent 869188c commit 12fbfa8
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/index.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { xml } from './xml'
export { handleHTML } from './manipulate'
export { serializeMarkdown } from './serialize-markdown'
export { serializePlaintext } from './serialize-plaintext'
export { serializeSafeHTML } from './serialize-safehtml'
91 changes: 91 additions & 0 deletions src/serialize-safehtml.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// (C)opyright 2021-07-20 Dirk Holtwick, holtwick.it. All rights reserved.

import { serializeSafeHTML } from './serialize-safehtml'
import { parseHTML } from './vdomparser'

describe('serialize safe html', () => {
it('should create safe html', () => {
const document = parseHTML(`
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello</h1>
<p>
This is a
<b>sample</b>
. &gt; And a link
<a href="https://example.com?x=a%20b&y=1" title="Will this be stripped?">example</a>
.
</p>
<p>
Some & lines
<br />
line
<br />
line
</p>
<ol>
<li>One</li>
<li>Two</li>
</ol>
<pre>
<p>Do nothing</p>
</pre>
</body>
`)

expect(document.render()).toMatchInlineSnapshot(`
"
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello</h1>
<p>
This is a
<b>sample</b>
. &gt; And a link
<a href="https://example.com?x=a%20b&amp;y=1" title="Will this be stripped?">example</a>
.
</p>
<p>
Some &amp; lines
<br>
line
<br>
line
</p>
<ol>
<li>One</li>
<li>Two</li>
</ol>
<pre>
<p>Do nothing</p>
</pre>
</body>
</html>"
`)

const md = serializeSafeHTML(document)
expect(md).toMatchInlineSnapshot(`
"<h1>Hello</h1>
<p>This is a
sample
. &gt; And a link
<a href="https://example.com?x=a%20b&amp;y=1">example</a>
.</p>
<p>Some &amp; lines
<br>
line
<br>
line</p>
<ol><li>One</li>
<li>Two</li></ol>
<p>Do nothing</p>"
`)
})
})
49 changes: 49 additions & 0 deletions src/serialize-safehtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { escapeHTML } from './encoding'
import { VNode, isVElement } from './vdom'

export const SELECTOR_BLOCK_ELEMENTS = 'p,h1,h2,h3,h4,h5,h6,blockquote,div,ul,ol,li,article,section,footer,nav,hr,form'

interface SerializeContext {
level: number
count: 0
mode?: 'ol' | 'ul'
}

function serialize(node: VNode, context: SerializeContext = {
level: 0,
count: 0,
}): string {
if (node.nodeType === VNode.DOCUMENT_FRAGMENT_NODE) {
return node.children.map(c => serialize(c, { ...context })).join('')
}

else if (isVElement(node)) {
const tag: string = node.tagName?.toLowerCase()
const handleChildren = (ctx?: Partial<SerializeContext>): string => node.children.map(c => serialize(c, { ...context, ...ctx })).join('')

const rules: Record<string, () => string> = {
a: () => `<a href="${escapeHTML(node.getAttribute('href') ?? '')}">${handleChildren()}</a>`,
br: () => `<br>`,
title: () => '',
script: () => '',
style: () => '',
head: () => '',
}

SELECTOR_BLOCK_ELEMENTS.split(',').forEach((tag) => {
rules[tag] = () => `<${tag}>${handleChildren().trim()}</${tag}>`
})

const fn = rules[tag]

if (fn)
return fn()

return handleChildren()
}
return escapeHTML(node.textContent ?? '')
}

export function serializeSafeHTML(node: VNode): string {
return serialize(node).trim()
}
2 changes: 1 addition & 1 deletion src/tidy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { VDocument } from './vdom'
import { VNode, VTextNode } from './vdom'

export const SELECTOR_BLOCK_ELEMENTS = 'meta,link,script,p,h1,h2,h3,h4,h5,h6,blockquote,div,ul,ol,li,article,section,footer,head,body,title,nav,section,article,hr,form'
export const SELECTOR_BLOCK_ELEMENTS = 'meta,link,script,p,h1,h2,h3,h4,h5,h6,blockquote,div,ul,ol,li,article,section,footer,head,body,title,nav,hr,form'
export const TAGS_KEEP_CONTENT = ['PRE', 'CODE', 'SCRIPT', 'STYLE', 'TT']

function level(element: VNode): string {
Expand Down

0 comments on commit 12fbfa8

Please sign in to comment.