// import { parseDOM } from 'htmlparser2'
import { Node, DataNode, Element, NodeWithChildren } from 'domhandler'
import * as entities from 'entities'
const selfCloseTagMap = new Map([
['area', true],
['base', true],
['br', true],
['col', true],
['embed', true],
['hr', true],
['img', true],
['input', true],
['link', true],
['meta', true],
['param', true],
['source', true],
['track', true],
['wbr', true],
['command', true],
['keygen', true],
['menuitem', true],
])
export interface GeneratorOptions {
disableAttrEscape?: boolean
dumpEmptyAttri?: boolean
beforeEach?: (node: Node, parent: NodeWithChildren | null) => Node
}
const defaultOptions: GeneratorOptions = {
disableAttrEscape: false,
dumpEmptyAttri: false,
beforeEach: undefined,
}
function escapeAttr(attr: string, disableAttrEscape: boolean = false) {
if (!attr) return attr
return disableAttrEscape ? attr : entities.escape(attr)
}
function dfs(node: Node[] | Node, options: GeneratorOptions): string {
if (Array.isArray(node)) {
return node.map(n => dfs(n, options)).join('')
}
if (options.beforeEach) {
node = options.beforeEach(node, node.parent)
}
const dataNode = node as DataNode
const el = node as Element
// console.log(node)
if (el.type === 'text') {
return dataNode.data
} /* else if(el.type === 'doctype') { // couldn't find doctype example
return ''
} */ else if (el.type === 'cdata') {
return ``
} else if (el.type === 'directive') {
return `<${dataNode.data}>`
} else if (el.type === 'comment') {
return ``
} else if (['style', 'script', 'tag'].includes(el.type)) {
let ans = '<' + el.name
if (el.attribs && Object.keys(el.attribs).length) {
ans += ' ' + Object.keys(el.attribs).map(key => {
const val = el.attribs[key]
return key + (options.dumpEmptyAttri && !val ? '' : `="${escapeAttr(val, options.disableAttrEscape)}"`)
}).join(' ')
}
if (el.children?.length) {
ans += '>' + dfs(el.children, options) + `${el.name}>`
} else {
if (selfCloseTagMap.has(el.name)) {
ans += '/>'
} else {
ans += `>${el.name}>`
}
}
return ans
}
return ''
}
function generator(dom: Node[], userOptions: GeneratorOptions = {}): string {
const options: GeneratorOptions = { ...defaultOptions, ...userOptions }
return dfs(dom, options)
}
export default generator
// export default generator
// console.log(generator(parseDOM("Yay!")))
// console.log(generator(parseDOM(``)))
// console.log(generator(parseDOM(``)))
// console.log(generator(parseDOM(`John Smith]]>`, { recognizeCDATA: true, xmlMode: true })))
// console.log(generator(parseDOM(
// `The TitleHello world`
// )))
// javascript:alert('Haello');
// console.log(generator(parseDOM('<__proto__>')))
// console.log(generator(parseDOM("")))