-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathindex.js
257 lines (244 loc) · 9.5 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/**
* @import {Nodes, Parents, RootContent} from 'hast'
* @import {Schema} from 'property-information'
* @import {Options as StringifyEntitiesOptions} from 'stringify-entities'
*/
/**
* @typedef {Omit<StringifyEntitiesOptions, 'attribute' | 'escapeOnly' | 'subset'>} CharacterReferences
*
* @typedef Options
* Configuration.
* @property {boolean | null | undefined} [allowDangerousCharacters=false]
* Do not encode some characters which cause XSS vulnerabilities in older
* browsers (default: `false`).
*
* > ⚠️ **Danger**: only set this if you completely trust the content.
* @property {boolean | null | undefined} [allowDangerousHtml=false]
* Allow `raw` nodes and insert them as raw HTML (default: `false`).
*
* When `false`, `Raw` nodes are encoded.
*
* > ⚠️ **Danger**: only set this if you completely trust the content.
* @property {boolean | null | undefined} [allowParseErrors=false]
* Do not encode characters which cause parse errors (even though they work),
* to save bytes (default: `false`).
*
* Not used in the SVG space.
*
* > 👉 **Note**: intentionally creates parse errors in markup (how parse
* > errors are handled is well defined, so this works but isn’t pretty).
* @property {boolean | null | undefined} [bogusComments=false]
* Use “bogus comments” instead of comments to save byes: `<?charlie>`
* instead of `<!--charlie-->` (default: `false`).
*
* > 👉 **Note**: intentionally creates parse errors in markup (how parse
* > errors are handled is well defined, so this works but isn’t pretty).
* @property {CharacterReferences | null | undefined} [characterReferences]
* Configure how to serialize character references (optional).
* @property {boolean | null | undefined} [closeEmptyElements=false]
* Close SVG elements without any content with slash (`/`) on the opening tag
* instead of an end tag: `<circle />` instead of `<circle></circle>`
* (default: `false`).
*
* See `tightSelfClosing` to control whether a space is used before the
* slash.
*
* Not used in the HTML space.
* @property {boolean | null | undefined} [closeSelfClosing=false]
* Close self-closing nodes with an extra slash (`/`): `<img />` instead of
* `<img>` (default: `false`).
*
* See `tightSelfClosing` to control whether a space is used before the
* slash.
*
* Not used in the SVG space.
* @property {boolean | null | undefined} [collapseEmptyAttributes=false]
* Collapse empty attributes: get `class` instead of `class=""` (default:
* `false`).
*
* Not used in the SVG space.
*
* > 👉 **Note**: boolean attributes (such as `hidden`) are always collapsed.
* @property {boolean | null | undefined} [omitOptionalTags=false]
* Omit optional opening and closing tags (default: `false`).
*
* For example, in `<ol><li>one</li><li>two</li></ol>`, both `</li>` closing
* tags can be omitted.
* The first because it’s followed by another `li`, the last because it’s
* followed by nothing.
*
* Not used in the SVG space.
* @property {boolean | null | undefined} [preferUnquoted=false]
* Leave attributes unquoted if that results in less bytes (default: `false`).
*
* Not used in the SVG space.
* @property {boolean | null | undefined} [quoteSmart=false]
* Use the other quote if that results in less bytes (default: `false`).
* @property {Quote | null | undefined} [quote='"']
* Preferred quote to use (default: `'"'`).
* @property {Space | null | undefined} [space='html']
* When an `<svg>` element is found in the HTML space, this package already
* automatically switches to and from the SVG space when entering and exiting
* it (default: `'html'`).
*
* > 👉 **Note**: hast is not XML.
* > It supports SVG as embedded in HTML.
* > It does not support the features available in XML.
* > Passing SVG might break but fragments of modern SVG should be fine.
* > Use [`xast`][xast] if you need to support SVG as XML.
* @property {boolean | null | undefined} [tightAttributes=false]
* Join attributes together, without whitespace, if possible: get
* `class="a b"title="c d"` instead of `class="a b" title="c d"` to save
* bytes (default: `false`).
*
* Not used in the SVG space.
*
* > 👉 **Note**: intentionally creates parse errors in markup (how parse
* > errors are handled is well defined, so this works but isn’t pretty).
* @property {boolean | null | undefined} [tightCommaSeparatedLists=false]
* Join known comma-separated attribute values with just a comma (`,`),
* instead of padding them on the right as well (`,␠`, where `␠` represents a
* space) (default: `false`).
* @property {boolean | null | undefined} [tightDoctype=false]
* Drop unneeded spaces in doctypes: `<!doctypehtml>` instead of
* `<!doctype html>` to save bytes (default: `false`).
*
* > 👉 **Note**: intentionally creates parse errors in markup (how parse
* > errors are handled is well defined, so this works but isn’t pretty).
* @property {boolean | null | undefined} [tightSelfClosing=false]
* Do not use an extra space when closing self-closing elements: `<img/>`
* instead of `<img />` (default: `false`).
*
* > 👉 **Note**: only used if `closeSelfClosing: true` or
* > `closeEmptyElements: true`.
* @property {boolean | null | undefined} [upperDoctype=false]
* Use a `<!DOCTYPE…` instead of `<!doctype…` (default: `false`).
*
* Useless except for XHTML.
* @property {ReadonlyArray<string> | null | undefined} [voids]
* Tag names of elements to serialize without closing tag (default: `html-void-elements`).
*
* Not used in the SVG space.
*
* > 👉 **Note**: It’s highly unlikely that you want to pass this, because
* > hast is not for XML, and HTML will not add more void elements.
*
* @typedef {'"' | "'"} Quote
* HTML quotes for attribute values.
*
* @typedef {Omit<Required<{[key in keyof Options]: Exclude<Options[key], null | undefined>}>, 'space' | 'quote'>} Settings
*
* @typedef {'html' | 'svg'} Space
* Namespace.
*
* @typedef State
* Info passed around about the current state.
* @property {(node: Parents | undefined) => string} all
* Serialize the children of a parent node.
* @property {Quote} alternative
* Alternative quote.
* @property {(node: Nodes, index: number | undefined, parent: Parents | undefined) => string} one
* Serialize one node.
* @property {Quote} quote
* Preferred quote.
* @property {Schema} schema
* Current schema.
* @property {Settings} settings
* User configuration.
*/
import {htmlVoidElements} from 'html-void-elements'
import {html, svg} from 'property-information'
import {handle} from './handle/index.js'
/** @type {Options} */
const emptyOptions = {}
/** @type {CharacterReferences} */
const emptyCharacterReferences = {}
/** @type {Array<never>} */
const emptyChildren = []
/**
* Serialize hast as HTML.
*
* @param {Array<RootContent> | Nodes} tree
* Tree to serialize.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {string}
* Serialized HTML.
*/
export function toHtml(tree, options) {
const options_ = options || emptyOptions
const quote = options_.quote || '"'
const alternative = quote === '"' ? "'" : '"'
if (quote !== '"' && quote !== "'") {
throw new Error('Invalid quote `' + quote + '`, expected `\'` or `"`')
}
/** @type {State} */
const state = {
one,
all,
settings: {
omitOptionalTags: options_.omitOptionalTags || false,
allowParseErrors: options_.allowParseErrors || false,
allowDangerousCharacters: options_.allowDangerousCharacters || false,
quoteSmart: options_.quoteSmart || false,
preferUnquoted: options_.preferUnquoted || false,
tightAttributes: options_.tightAttributes || false,
upperDoctype: options_.upperDoctype || false,
tightDoctype: options_.tightDoctype || false,
bogusComments: options_.bogusComments || false,
tightCommaSeparatedLists: options_.tightCommaSeparatedLists || false,
tightSelfClosing: options_.tightSelfClosing || false,
collapseEmptyAttributes: options_.collapseEmptyAttributes || false,
allowDangerousHtml: options_.allowDangerousHtml || false,
voids: options_.voids || htmlVoidElements,
characterReferences:
options_.characterReferences || emptyCharacterReferences,
closeSelfClosing: options_.closeSelfClosing || false,
closeEmptyElements: options_.closeEmptyElements || false
},
schema: options_.space === 'svg' ? svg : html,
quote,
alternative
}
return state.one(
Array.isArray(tree) ? {type: 'root', children: tree} : tree,
undefined,
undefined
)
}
/**
* Serialize a node.
*
* @this {State}
* Info passed around about the current state.
* @param {Nodes} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @returns {string}
* Serialized node.
*/
function one(node, index, parent) {
return handle(node, index, parent, this)
}
/**
* Serialize all children of `parent`.
*
* @this {State}
* Info passed around about the current state.
* @param {Parents | undefined} parent
* Parent whose children to serialize.
* @returns {string}
*/
export function all(parent) {
/** @type {Array<string>} */
const results = []
const children = (parent && parent.children) || emptyChildren
let index = -1
while (++index < children.length) {
results[index] = this.one(children[index], index, parent)
}
return results.join('')
}