@cjser/parse5 0.0.0-stage → 8.0.1-cjser.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ import { TAG_ID as $ } from '../common/html.js';
2
+ import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface.js';
3
+ export interface StackHandler<T extends TreeAdapterTypeMap> {
4
+ onItemPush: (node: T['parentNode'], tid: number, isTop: boolean) => void;
5
+ onItemPop: (node: T['parentNode'], isTop: boolean) => void;
6
+ }
7
+ export declare class OpenElementStack<T extends TreeAdapterTypeMap> {
8
+ private treeAdapter;
9
+ private handler;
10
+ items: T['parentNode'][];
11
+ tagIDs: $[];
12
+ current: T['parentNode'] | undefined;
13
+ stackTop: number;
14
+ tmplCount: number;
15
+ currentTagId: number | undefined;
16
+ get currentTmplContentOrNode(): T['parentNode'];
17
+ constructor(document: T['document'], treeAdapter: TreeAdapter<T>, handler: StackHandler<T>);
18
+ private _indexOf;
19
+ private _isInTemplate;
20
+ private _updateCurrentElement;
21
+ push(element: T['element'], tagID: $): void;
22
+ pop(): void;
23
+ replace(oldElement: T['element'], newElement: T['element']): void;
24
+ insertAfter(referenceElement: T['element'], newElement: T['element'], newElementID: $): void;
25
+ popUntilTagNamePopped(tagName: $): void;
26
+ shortenToLength(idx: number): void;
27
+ popUntilElementPopped(element: T['element']): void;
28
+ private popUntilPopped;
29
+ popUntilNumberedHeaderPopped(): void;
30
+ popUntilTableCellPopped(): void;
31
+ popAllUpToHtmlElement(): void;
32
+ private _indexOfTagNames;
33
+ private clearBackTo;
34
+ clearBackToTableContext(): void;
35
+ clearBackToTableBodyContext(): void;
36
+ clearBackToTableRowContext(): void;
37
+ remove(element: T['element']): void;
38
+ tryPeekProperlyNestedBodyElement(): T['element'] | null;
39
+ contains(element: T['element']): boolean;
40
+ getCommonAncestor(element: T['element']): T['element'] | null;
41
+ isRootHtmlElementCurrent(): boolean;
42
+ private hasInDynamicScope;
43
+ hasInScope(tagName: $): boolean;
44
+ hasInListItemScope(tagName: $): boolean;
45
+ hasInButtonScope(tagName: $): boolean;
46
+ hasNumberedHeaderInScope(): boolean;
47
+ hasInTableScope(tagName: $): boolean;
48
+ hasTableBodyContextInTableScope(): boolean;
49
+ hasInSelectScope(tagName: $): boolean;
50
+ generateImpliedEndTags(): void;
51
+ generateImpliedEndTagsThoroughly(): void;
52
+ generateImpliedEndTagsWithExclusion(exclusionId: $): void;
53
+ }
@@ -0,0 +1,324 @@
1
+ import { TAG_ID as $, NS, NUMBERED_HEADERS } from '../common/html.js';
2
+ //Element utils
3
+ const IMPLICIT_END_TAG_REQUIRED = new Set([$.DD, $.DT, $.LI, $.OPTGROUP, $.OPTION, $.P, $.RB, $.RP, $.RT, $.RTC]);
4
+ const IMPLICIT_END_TAG_REQUIRED_THOROUGHLY = new Set([
5
+ ...IMPLICIT_END_TAG_REQUIRED,
6
+ $.CAPTION,
7
+ $.COLGROUP,
8
+ $.TBODY,
9
+ $.TD,
10
+ $.TFOOT,
11
+ $.TH,
12
+ $.THEAD,
13
+ $.TR,
14
+ ]);
15
+ const SCOPING_ELEMENTS_HTML = new Set([
16
+ $.APPLET,
17
+ $.CAPTION,
18
+ $.HTML,
19
+ $.MARQUEE,
20
+ $.OBJECT,
21
+ $.TABLE,
22
+ $.TD,
23
+ $.TEMPLATE,
24
+ $.TH,
25
+ ]);
26
+ const SCOPING_ELEMENTS_HTML_LIST = new Set([...SCOPING_ELEMENTS_HTML, $.OL, $.UL]);
27
+ const SCOPING_ELEMENTS_HTML_BUTTON = new Set([...SCOPING_ELEMENTS_HTML, $.BUTTON]);
28
+ const SCOPING_ELEMENTS_MATHML = new Set([$.ANNOTATION_XML, $.MI, $.MN, $.MO, $.MS, $.MTEXT]);
29
+ const SCOPING_ELEMENTS_SVG = new Set([$.DESC, $.FOREIGN_OBJECT, $.TITLE]);
30
+ const TABLE_ROW_CONTEXT = new Set([$.TR, $.TEMPLATE, $.HTML]);
31
+ const TABLE_BODY_CONTEXT = new Set([$.TBODY, $.TFOOT, $.THEAD, $.TEMPLATE, $.HTML]);
32
+ const TABLE_CONTEXT = new Set([$.TABLE, $.TEMPLATE, $.HTML]);
33
+ const TABLE_CELLS = new Set([$.TD, $.TH]);
34
+ //Stack of open elements
35
+ export class OpenElementStack {
36
+ get currentTmplContentOrNode() {
37
+ return this._isInTemplate() ? this.treeAdapter.getTemplateContent(this.current) : this.current;
38
+ }
39
+ constructor(document, treeAdapter, handler) {
40
+ this.treeAdapter = treeAdapter;
41
+ this.handler = handler;
42
+ this.items = [];
43
+ this.tagIDs = [];
44
+ this.stackTop = -1;
45
+ this.tmplCount = 0;
46
+ this.currentTagId = $.UNKNOWN;
47
+ this.current = document;
48
+ }
49
+ //Index of element
50
+ _indexOf(element) {
51
+ return this.items.lastIndexOf(element, this.stackTop);
52
+ }
53
+ //Update current element
54
+ _isInTemplate() {
55
+ return this.currentTagId === $.TEMPLATE && this.treeAdapter.getNamespaceURI(this.current) === NS.HTML;
56
+ }
57
+ _updateCurrentElement() {
58
+ this.current = this.items[this.stackTop];
59
+ this.currentTagId = this.tagIDs[this.stackTop];
60
+ }
61
+ //Mutations
62
+ push(element, tagID) {
63
+ this.stackTop++;
64
+ this.items[this.stackTop] = element;
65
+ this.current = element;
66
+ this.tagIDs[this.stackTop] = tagID;
67
+ this.currentTagId = tagID;
68
+ if (this._isInTemplate()) {
69
+ this.tmplCount++;
70
+ }
71
+ this.handler.onItemPush(element, tagID, true);
72
+ }
73
+ pop() {
74
+ const popped = this.current;
75
+ if (this.tmplCount > 0 && this._isInTemplate()) {
76
+ this.tmplCount--;
77
+ }
78
+ this.stackTop--;
79
+ this._updateCurrentElement();
80
+ this.handler.onItemPop(popped, true);
81
+ }
82
+ replace(oldElement, newElement) {
83
+ const idx = this._indexOf(oldElement);
84
+ this.items[idx] = newElement;
85
+ if (idx === this.stackTop) {
86
+ this.current = newElement;
87
+ }
88
+ }
89
+ insertAfter(referenceElement, newElement, newElementID) {
90
+ const insertionIdx = this._indexOf(referenceElement) + 1;
91
+ this.items.splice(insertionIdx, 0, newElement);
92
+ this.tagIDs.splice(insertionIdx, 0, newElementID);
93
+ this.stackTop++;
94
+ if (insertionIdx === this.stackTop) {
95
+ this._updateCurrentElement();
96
+ }
97
+ if (this.current && this.currentTagId !== undefined) {
98
+ this.handler.onItemPush(this.current, this.currentTagId, insertionIdx === this.stackTop);
99
+ }
100
+ }
101
+ popUntilTagNamePopped(tagName) {
102
+ let targetIdx = this.stackTop + 1;
103
+ do {
104
+ targetIdx = this.tagIDs.lastIndexOf(tagName, targetIdx - 1);
105
+ } while (targetIdx > 0 && this.treeAdapter.getNamespaceURI(this.items[targetIdx]) !== NS.HTML);
106
+ this.shortenToLength(Math.max(targetIdx, 0));
107
+ }
108
+ shortenToLength(idx) {
109
+ while (this.stackTop >= idx) {
110
+ const popped = this.current;
111
+ if (this.tmplCount > 0 && this._isInTemplate()) {
112
+ this.tmplCount -= 1;
113
+ }
114
+ this.stackTop--;
115
+ this._updateCurrentElement();
116
+ this.handler.onItemPop(popped, this.stackTop < idx);
117
+ }
118
+ }
119
+ popUntilElementPopped(element) {
120
+ const idx = this._indexOf(element);
121
+ this.shortenToLength(Math.max(idx, 0));
122
+ }
123
+ popUntilPopped(tagNames, targetNS) {
124
+ const idx = this._indexOfTagNames(tagNames, targetNS);
125
+ this.shortenToLength(Math.max(idx, 0));
126
+ }
127
+ popUntilNumberedHeaderPopped() {
128
+ this.popUntilPopped(NUMBERED_HEADERS, NS.HTML);
129
+ }
130
+ popUntilTableCellPopped() {
131
+ this.popUntilPopped(TABLE_CELLS, NS.HTML);
132
+ }
133
+ popAllUpToHtmlElement() {
134
+ //NOTE: here we assume that the root <html> element is always first in the open element stack, so
135
+ //we perform this fast stack clean up.
136
+ this.tmplCount = 0;
137
+ this.shortenToLength(1);
138
+ }
139
+ _indexOfTagNames(tagNames, namespace) {
140
+ for (let i = this.stackTop; i >= 0; i--) {
141
+ if (tagNames.has(this.tagIDs[i]) && this.treeAdapter.getNamespaceURI(this.items[i]) === namespace) {
142
+ return i;
143
+ }
144
+ }
145
+ return -1;
146
+ }
147
+ clearBackTo(tagNames, targetNS) {
148
+ const idx = this._indexOfTagNames(tagNames, targetNS);
149
+ this.shortenToLength(idx + 1);
150
+ }
151
+ clearBackToTableContext() {
152
+ this.clearBackTo(TABLE_CONTEXT, NS.HTML);
153
+ }
154
+ clearBackToTableBodyContext() {
155
+ this.clearBackTo(TABLE_BODY_CONTEXT, NS.HTML);
156
+ }
157
+ clearBackToTableRowContext() {
158
+ this.clearBackTo(TABLE_ROW_CONTEXT, NS.HTML);
159
+ }
160
+ remove(element) {
161
+ const idx = this._indexOf(element);
162
+ if (idx >= 0) {
163
+ if (idx === this.stackTop) {
164
+ this.pop();
165
+ }
166
+ else {
167
+ this.items.splice(idx, 1);
168
+ this.tagIDs.splice(idx, 1);
169
+ this.stackTop--;
170
+ this._updateCurrentElement();
171
+ this.handler.onItemPop(element, false);
172
+ }
173
+ }
174
+ }
175
+ //Search
176
+ tryPeekProperlyNestedBodyElement() {
177
+ //Properly nested <body> element (should be second element in stack).
178
+ return this.stackTop >= 1 && this.tagIDs[1] === $.BODY ? this.items[1] : null;
179
+ }
180
+ contains(element) {
181
+ return this._indexOf(element) > -1;
182
+ }
183
+ getCommonAncestor(element) {
184
+ const elementIdx = this._indexOf(element) - 1;
185
+ return elementIdx >= 0 ? this.items[elementIdx] : null;
186
+ }
187
+ isRootHtmlElementCurrent() {
188
+ return this.stackTop === 0 && this.tagIDs[0] === $.HTML;
189
+ }
190
+ //Element in scope
191
+ hasInDynamicScope(tagName, htmlScope) {
192
+ for (let i = this.stackTop; i >= 0; i--) {
193
+ const tn = this.tagIDs[i];
194
+ switch (this.treeAdapter.getNamespaceURI(this.items[i])) {
195
+ case NS.HTML: {
196
+ if (tn === tagName)
197
+ return true;
198
+ if (htmlScope.has(tn))
199
+ return false;
200
+ break;
201
+ }
202
+ case NS.SVG: {
203
+ if (SCOPING_ELEMENTS_SVG.has(tn))
204
+ return false;
205
+ break;
206
+ }
207
+ case NS.MATHML: {
208
+ if (SCOPING_ELEMENTS_MATHML.has(tn))
209
+ return false;
210
+ break;
211
+ }
212
+ }
213
+ }
214
+ return true;
215
+ }
216
+ hasInScope(tagName) {
217
+ return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML);
218
+ }
219
+ hasInListItemScope(tagName) {
220
+ return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML_LIST);
221
+ }
222
+ hasInButtonScope(tagName) {
223
+ return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML_BUTTON);
224
+ }
225
+ hasNumberedHeaderInScope() {
226
+ for (let i = this.stackTop; i >= 0; i--) {
227
+ const tn = this.tagIDs[i];
228
+ switch (this.treeAdapter.getNamespaceURI(this.items[i])) {
229
+ case NS.HTML: {
230
+ if (NUMBERED_HEADERS.has(tn))
231
+ return true;
232
+ if (SCOPING_ELEMENTS_HTML.has(tn))
233
+ return false;
234
+ break;
235
+ }
236
+ case NS.SVG: {
237
+ if (SCOPING_ELEMENTS_SVG.has(tn))
238
+ return false;
239
+ break;
240
+ }
241
+ case NS.MATHML: {
242
+ if (SCOPING_ELEMENTS_MATHML.has(tn))
243
+ return false;
244
+ break;
245
+ }
246
+ }
247
+ }
248
+ return true;
249
+ }
250
+ hasInTableScope(tagName) {
251
+ for (let i = this.stackTop; i >= 0; i--) {
252
+ if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) {
253
+ continue;
254
+ }
255
+ switch (this.tagIDs[i]) {
256
+ case tagName: {
257
+ return true;
258
+ }
259
+ case $.TABLE:
260
+ case $.HTML: {
261
+ return false;
262
+ }
263
+ }
264
+ }
265
+ return true;
266
+ }
267
+ hasTableBodyContextInTableScope() {
268
+ for (let i = this.stackTop; i >= 0; i--) {
269
+ if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) {
270
+ continue;
271
+ }
272
+ switch (this.tagIDs[i]) {
273
+ case $.TBODY:
274
+ case $.THEAD:
275
+ case $.TFOOT: {
276
+ return true;
277
+ }
278
+ case $.TABLE:
279
+ case $.HTML: {
280
+ return false;
281
+ }
282
+ }
283
+ }
284
+ return true;
285
+ }
286
+ hasInSelectScope(tagName) {
287
+ for (let i = this.stackTop; i >= 0; i--) {
288
+ if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) {
289
+ continue;
290
+ }
291
+ switch (this.tagIDs[i]) {
292
+ case tagName: {
293
+ return true;
294
+ }
295
+ case $.OPTION:
296
+ case $.OPTGROUP: {
297
+ break;
298
+ }
299
+ default: {
300
+ return false;
301
+ }
302
+ }
303
+ }
304
+ return true;
305
+ }
306
+ //Implied end tags
307
+ generateImpliedEndTags() {
308
+ while (this.currentTagId !== undefined && IMPLICIT_END_TAG_REQUIRED.has(this.currentTagId)) {
309
+ this.pop();
310
+ }
311
+ }
312
+ generateImpliedEndTagsThoroughly() {
313
+ while (this.currentTagId !== undefined && IMPLICIT_END_TAG_REQUIRED_THOROUGHLY.has(this.currentTagId)) {
314
+ this.pop();
315
+ }
316
+ }
317
+ generateImpliedEndTagsWithExclusion(exclusionId) {
318
+ while (this.currentTagId !== undefined &&
319
+ this.currentTagId !== exclusionId &&
320
+ IMPLICIT_END_TAG_REQUIRED_THOROUGHLY.has(this.currentTagId)) {
321
+ this.pop();
322
+ }
323
+ }
324
+ }
@@ -0,0 +1,60 @@
1
+ import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface.js';
2
+ import { type DefaultTreeAdapterMap } from '../tree-adapters/default.js';
3
+ export interface SerializerOptions<T extends TreeAdapterTypeMap> {
4
+ /**
5
+ * Specifies input tree format.
6
+ *
7
+ * @default `treeAdapters.default`
8
+ */
9
+ treeAdapter?: TreeAdapter<T>;
10
+ /**
11
+ * The [scripting flag](https://html.spec.whatwg.org/multipage/parsing.html#scripting-flag). If set
12
+ * to `true`, `noscript` element content will not be escaped.
13
+ *
14
+ * @default `true`
15
+ */
16
+ scriptingEnabled?: boolean;
17
+ }
18
+ /**
19
+ * Serializes an AST node to an HTML string.
20
+ *
21
+ * @example
22
+ *
23
+ * ```js
24
+ * const parse5 = require('@cjser/parse5');
25
+ *
26
+ * const document = parse5.parse('<!DOCTYPE html><html><head></head><body>Hi there!</body></html>');
27
+ *
28
+ * // Serializes a document.
29
+ * const html = parse5.serialize(document);
30
+ *
31
+ * // Serializes the <html> element content.
32
+ * const str = parse5.serialize(document.childNodes[1]);
33
+ *
34
+ * console.log(str); //> '<head></head><body>Hi there!</body>'
35
+ * ```
36
+ *
37
+ * @param node Node to serialize.
38
+ * @param options Serialization options.
39
+ */
40
+ export declare function serialize<T extends TreeAdapterTypeMap = DefaultTreeAdapterMap>(node: T['parentNode'], options?: SerializerOptions<T>): string;
41
+ /**
42
+ * Serializes an AST element node to an HTML string, including the element node.
43
+ *
44
+ * @example
45
+ *
46
+ * ```js
47
+ * const parse5 = require('@cjser/parse5');
48
+ *
49
+ * const document = parse5.parseFragment('<div>Hello, <b>world</b>!</div>');
50
+ *
51
+ * // Serializes the <div> element.
52
+ * const str = parse5.serializeOuter(document.childNodes[0]);
53
+ *
54
+ * console.log(str); //> '<div>Hello, <b>world</b>!</div>'
55
+ * ```
56
+ *
57
+ * @param node Node to serialize.
58
+ * @param options Serialization options.
59
+ */
60
+ export declare function serializeOuter<T extends TreeAdapterTypeMap = DefaultTreeAdapterMap>(node: T['node'], options?: SerializerOptions<T>): string;
@@ -0,0 +1,168 @@
1
+ import { TAG_NAMES as $, NS, hasUnescapedText } from '../common/html.js';
2
+ import { escapeText, escapeAttribute } from '@cjser/entities/escape';
3
+ import { defaultTreeAdapter } from '../tree-adapters/default.js';
4
+ // Sets
5
+ const VOID_ELEMENTS = new Set([
6
+ $.AREA,
7
+ $.BASE,
8
+ $.BASEFONT,
9
+ $.BGSOUND,
10
+ $.BR,
11
+ $.COL,
12
+ $.EMBED,
13
+ $.FRAME,
14
+ $.HR,
15
+ $.IMG,
16
+ $.INPUT,
17
+ $.KEYGEN,
18
+ $.LINK,
19
+ $.META,
20
+ $.PARAM,
21
+ $.SOURCE,
22
+ $.TRACK,
23
+ $.WBR,
24
+ ]);
25
+ function isVoidElement(node, options) {
26
+ return (options.treeAdapter.isElementNode(node) &&
27
+ options.treeAdapter.getNamespaceURI(node) === NS.HTML &&
28
+ VOID_ELEMENTS.has(options.treeAdapter.getTagName(node)));
29
+ }
30
+ const defaultOpts = { treeAdapter: defaultTreeAdapter, scriptingEnabled: true };
31
+ /**
32
+ * Serializes an AST node to an HTML string.
33
+ *
34
+ * @example
35
+ *
36
+ * ```js
37
+ * const parse5 = require('@cjser/parse5');
38
+ *
39
+ * const document = parse5.parse('<!DOCTYPE html><html><head></head><body>Hi there!</body></html>');
40
+ *
41
+ * // Serializes a document.
42
+ * const html = parse5.serialize(document);
43
+ *
44
+ * // Serializes the <html> element content.
45
+ * const str = parse5.serialize(document.childNodes[1]);
46
+ *
47
+ * console.log(str); //> '<head></head><body>Hi there!</body>'
48
+ * ```
49
+ *
50
+ * @param node Node to serialize.
51
+ * @param options Serialization options.
52
+ */
53
+ export function serialize(node, options) {
54
+ const opts = { ...defaultOpts, ...options };
55
+ if (isVoidElement(node, opts)) {
56
+ return '';
57
+ }
58
+ return serializeChildNodes(node, opts);
59
+ }
60
+ /**
61
+ * Serializes an AST element node to an HTML string, including the element node.
62
+ *
63
+ * @example
64
+ *
65
+ * ```js
66
+ * const parse5 = require('@cjser/parse5');
67
+ *
68
+ * const document = parse5.parseFragment('<div>Hello, <b>world</b>!</div>');
69
+ *
70
+ * // Serializes the <div> element.
71
+ * const str = parse5.serializeOuter(document.childNodes[0]);
72
+ *
73
+ * console.log(str); //> '<div>Hello, <b>world</b>!</div>'
74
+ * ```
75
+ *
76
+ * @param node Node to serialize.
77
+ * @param options Serialization options.
78
+ */
79
+ export function serializeOuter(node, options) {
80
+ const opts = { ...defaultOpts, ...options };
81
+ return serializeNode(node, opts);
82
+ }
83
+ function serializeChildNodes(parentNode, options) {
84
+ let html = '';
85
+ // Get container of the child nodes
86
+ const container = options.treeAdapter.isElementNode(parentNode) &&
87
+ options.treeAdapter.getTagName(parentNode) === $.TEMPLATE &&
88
+ options.treeAdapter.getNamespaceURI(parentNode) === NS.HTML
89
+ ? options.treeAdapter.getTemplateContent(parentNode)
90
+ : parentNode;
91
+ const childNodes = options.treeAdapter.getChildNodes(container);
92
+ if (childNodes) {
93
+ for (const currentNode of childNodes) {
94
+ html += serializeNode(currentNode, options);
95
+ }
96
+ }
97
+ return html;
98
+ }
99
+ function serializeNode(node, options) {
100
+ if (options.treeAdapter.isElementNode(node)) {
101
+ return serializeElement(node, options);
102
+ }
103
+ if (options.treeAdapter.isTextNode(node)) {
104
+ return serializeTextNode(node, options);
105
+ }
106
+ if (options.treeAdapter.isCommentNode(node)) {
107
+ return serializeCommentNode(node, options);
108
+ }
109
+ if (options.treeAdapter.isDocumentTypeNode(node)) {
110
+ return serializeDocumentTypeNode(node, options);
111
+ }
112
+ // Return an empty string for unknown nodes
113
+ return '';
114
+ }
115
+ function serializeElement(node, options) {
116
+ const tn = options.treeAdapter.getTagName(node);
117
+ return `<${tn}${serializeAttributes(node, options)}>${isVoidElement(node, options) ? '' : `${serializeChildNodes(node, options)}</${tn}>`}`;
118
+ }
119
+ function serializeAttributes(node, { treeAdapter }) {
120
+ let html = '';
121
+ for (const attr of treeAdapter.getAttrList(node)) {
122
+ html += ' ';
123
+ if (attr.namespace) {
124
+ switch (attr.namespace) {
125
+ case NS.XML: {
126
+ html += `xml:${attr.name}`;
127
+ break;
128
+ }
129
+ case NS.XMLNS: {
130
+ if (attr.name !== 'xmlns') {
131
+ html += 'xmlns:';
132
+ }
133
+ html += attr.name;
134
+ break;
135
+ }
136
+ case NS.XLINK: {
137
+ html += `xlink:${attr.name}`;
138
+ break;
139
+ }
140
+ default: {
141
+ html += `${attr.prefix}:${attr.name}`;
142
+ }
143
+ }
144
+ }
145
+ else {
146
+ html += attr.name;
147
+ }
148
+ html += `="${escapeAttribute(attr.value)}"`;
149
+ }
150
+ return html;
151
+ }
152
+ function serializeTextNode(node, options) {
153
+ const { treeAdapter } = options;
154
+ const content = treeAdapter.getTextNodeContent(node);
155
+ const parent = treeAdapter.getParentNode(node);
156
+ const parentTn = parent && treeAdapter.isElementNode(parent) && treeAdapter.getTagName(parent);
157
+ return parentTn &&
158
+ treeAdapter.getNamespaceURI(parent) === NS.HTML &&
159
+ hasUnescapedText(parentTn, options.scriptingEnabled)
160
+ ? content
161
+ : escapeText(content);
162
+ }
163
+ function serializeCommentNode(node, { treeAdapter }) {
164
+ return `<!--${treeAdapter.getCommentNodeContent(node)}-->`;
165
+ }
166
+ function serializeDocumentTypeNode(node, { treeAdapter }) {
167
+ return `<!DOCTYPE ${treeAdapter.getDocumentTypeNodeName(node)}>`;
168
+ }