diff --git a/index.js b/index.js
index 50ac015..0665512 100644
--- a/index.js
+++ b/index.js
@@ -2,6 +2,7 @@
var Parser = require('parse5/lib/parser')
var pos = require('unist-util-position')
+var visit = require('unist-util-visit')
var fromParse5 = require('hast-util-from-parse5')
var toParse5 = require('hast-util-to-parse5')
var voids = require('html-void-elements')
@@ -19,12 +20,9 @@ var endTagToken = 'END_TAG_TOKEN'
var commentToken = 'COMMENT_TOKEN'
var doctypeToken = 'DOCTYPE_TOKEN'
-var parseOptions = {
- sourceCodeLocationInfo: true,
- scriptingEnabled: false
-}
+var parseOptions = {sourceCodeLocationInfo: true, scriptingEnabled: false}
-function wrap(tree, file) {
+function wrap(tree, file, options) {
var parser = new Parser(parseOptions)
var one = zwitch('type', {
handlers: {
@@ -37,11 +35,32 @@ function wrap(tree, file) {
},
unknown: unknown
})
+ var stitches
var tokenizer
var preprocessor
var posTracker
var locationTracker
- var result = fromParse5(documentMode(tree) ? document() : fragment(), file)
+ var result
+ var index
+
+ if (file && !('contents' in file)) {
+ options = file
+ file = undefined
+ }
+
+ if (options && options.passThrough) {
+ index = -1
+
+ while (++index < options.passThrough.length) {
+ one.handlers[options.passThrough[index]] = stitch
+ }
+ }
+
+ result = fromParse5(documentMode(tree) ? document() : fragment(), file)
+
+ if (stitches) {
+ visit(result, 'comment', mend)
+ }
// Unpack if possible and when not given a `root`.
if (tree.type !== 'root' && result.children.length === 1) {
@@ -50,6 +69,13 @@ function wrap(tree, file) {
return result
+ function mend(node, index, parent) {
+ if (node.value.stitch) {
+ parent.children[index] = node.value.stitch
+ return index
+ }
+ }
+
function fragment() {
var context = {
nodeName: 'template',
@@ -208,6 +234,27 @@ function wrap(tree, file) {
}
}
+ function stitch(node) {
+ var clone = Object.assign({}, node)
+
+ stitches = true
+
+ // Recurse, because to somewhat handle `[]` (where `[]` denotes the
+ // passed through node).
+ if (node.children) {
+ clone.children = wrap(
+ {type: 'root', children: node.children},
+ file,
+ options
+ ).children
+ }
+
+ // Hack: `value` is supposed to be a string, but as none of the tools
+ // (`parse5` or `hast-util-from-parse5`) looks at it, we can pass nodes
+ // through.
+ comment({value: {stitch: clone}})
+ }
+
function resetTokenizer() {
// Reset tokenizer:
// See: .
diff --git a/package.json b/package.json
index 7b4baaf..e99cd74 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"html-void-elements": "^1.0.0",
"parse5": "^6.0.0",
"unist-util-position": "^3.0.0",
+ "unist-util-visit": "^2.0.0",
"vfile": "^4.0.0",
"web-namespaces": "^1.0.0",
"xtend": "^4.0.0",
diff --git a/readme.md b/readme.md
index 72b7281..0cab987 100644
--- a/readme.md
+++ b/readme.md
@@ -61,12 +61,19 @@ Yields:
## API
-### `raw(tree[, file])`
+### `raw(tree[, file][, options])`
Given a [**hast**][hast] [*tree*][tree] and an optional [vfile][] (for
[positional info][position-information]), return a new parsed-again
[**hast**][hast] [*tree*][tree].
+###### `options.passThrough`
+
+List of custom hast node types to pass through (keep) in hast
+(`Array.`, default: `[]`).
+If the passed through nodes have children, those children are expected to be
+hast and will be handled.
+
## Security
Use of `hast-util-raw` can open you up to a [cross-site scripting (XSS)][xss]
diff --git a/test.js b/test.js
index 7e769e7..5070972 100644
--- a/test.js
+++ b/test.js
@@ -185,6 +185,68 @@ test('raw', function (t) {
'should not discard HTML broken over several raw nodes'
)
+ t.deepEqual(
+ raw(u('root', [u('custom', 'x')]), {passThrough: ['custom']}),
+ u('root', {data: {quirksMode: false}}, [u('custom', 'x')]),
+ 'should support passing through nodes w/o children'
+ )
+
+ t.deepEqual(
+ raw(u('root', [u('custom', [u('raw', 'j')])]), {
+ passThrough: ['custom']
+ }),
+ u('root', {data: {quirksMode: false}}, [u('custom', [h('i', 'j')])]),
+ 'should support passing through nodes w/ `raw` children'
+ )
+
+ t.deepEqual(
+ raw(u('root', [u('custom', [u('comment', 'x')])]), {
+ passThrough: ['custom']
+ }),
+ u('root', {data: {quirksMode: false}}, [u('custom', [u('comment', 'x')])]),
+ 'should support passing through nodes w/ `comment` children'
+ )
+
+ t.deepEqual(
+ raw(u('root', [u('custom', [])]), {
+ passThrough: ['custom']
+ }),
+ u('root', {data: {quirksMode: false}}, [u('custom', [])]),
+ 'should support passing through nodes w/ `0` children'
+ )
+
+ t.deepEqual(
+ raw(u('root', [u('custom', [u('raw', '')])]), {
+ passThrough: ['custom']
+ }),
+ u('root', {data: {quirksMode: false}}, [u('custom', [h('x')])]),
+ 'should support passing through nodes w/ broken raw children (2)'
+ )
+
+ t.deepEqual(
+ raw(u('root', [u('custom', [u('raw', '')])]), {
+ passThrough: ['custom']
+ }),
+ u('root', {data: {quirksMode: false}}, [u('custom', [])]),
+ 'should support passing through nodes w/ broken raw children (3)'
+ )
+
+ t.deepEqual(
+ raw(u('root', [u('custom', [u('raw', '')]), u('raw', '')]), {
+ passThrough: ['custom']
+ }),
+ u('root', {data: {quirksMode: false}}, [u('custom', [h('x')])]),
+ 'should support passing through nodes w/ broken raw children (4)'
+ )
+
t.deepEqual(
raw(u('root', [u('raw', '')])),
u('root', {data: {quirksMode: false}}, [
diff --git a/types/hast-util-raw-test.ts b/types/hast-util-raw-test.ts
index dbc1b6b..b07ff8a 100644
--- a/types/hast-util-raw-test.ts
+++ b/types/hast-util-raw-test.ts
@@ -6,7 +6,14 @@ raw({type: 'element', tagName: 'div', properties: {}, children: []}) // $ExpectT
// prettier-ignore
raw({type: 'element', tagName: 'div', properties: {}, children: []}, vFile('test')) // $ExpectType Node
+raw({type: 'raw'}, {}) // $ExpectType Node
+raw({type: 'raw'}, {passThrough: []}) // $ExpectType Node
+raw({type: 'raw'}, {passThrough: ['x']}) // $ExpectType Node
+raw({type: 'raw'}, vFile(), {}) // $ExpectType Node
+
raw() // $ExpectError
raw({}) // $ExpectError
// prettier-ignore
raw({type: 'element', tagName: 'div', properties: {}, children: []}, 'not a vFile') // $ExpectError
+raw({type: 'raw'}, {x: 1}) // $ExpectError
+raw({type: 'raw'}, {}, vFile()) // $ExpectError
diff --git a/types/index.d.ts b/types/index.d.ts
index 41c4e57..17758eb 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -3,11 +3,24 @@
import {Node} from 'hast'
import {VFile} from 'vfile'
+declare namespace raw {
+ interface Options {
+ /**
+ * List of custom hast node types to pass through (keep) in hast.
+ * If the passed through nodes have children, those children are expected to
+ * be hast and will be handled.
+ */
+ passThrough?: string[]
+ }
+}
+
/**
* Given a hast tree and an optional vfile (for positional info), return a new parsed-again hast tree.
* @param tree original hast tree
* @param file positional info
+ * @param options settings
*/
-declare function raw(tree: Node, file?: VFile): Node
+declare function raw(tree: Node, file?: VFile, options?: raw.Options): Node
+declare function raw(tree: Node, options?: raw.Options): Node
export = raw