-
-
Notifications
You must be signed in to change notification settings - Fork 179
/
XMLSerializer.ts
95 lines (83 loc) · 2.93 KB
/
XMLSerializer.ts
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
import Element from '../nodes/element/Element';
import Node from '../nodes/node/Node';
import VoidElements from '../config/VoidElements';
import DocumentType from '../nodes/document-type/DocumentType';
import { escape } from 'he';
import INode from '../nodes/node/INode';
import IElement from '../nodes/element/IElement';
import IHTMLTemplateElement from '../nodes/html-template-element/IHTMLTemplateElement';
/**
* Utility for converting an element to string.
*/
export default class XMLSerializer {
/**
* Renders an element as HTML.
*
* @param root Root element.
* @param [options] Options.
* @param [options.includeShadowRoots] Set to "true" to include shadow roots.
* @returns Result.
*/
public serializeToString(root: INode, options?: { includeShadowRoots?: boolean }): string {
switch (root.nodeType) {
case Node.ELEMENT_NODE:
const element = <Element>root;
const tagName = element.tagName.toLowerCase();
if (VoidElements.includes(tagName)) {
return `<${tagName}${this._getAttributes(element)}>`;
}
const childNodes =
element.tagName === 'TEMPLATE'
? (<IHTMLTemplateElement>root).content.childNodes
: root.childNodes;
let innerHTML = '';
for (const node of childNodes) {
innerHTML += this.serializeToString(node, options);
}
if (options?.includeShadowRoots && element.shadowRoot) {
innerHTML += `<template shadowroot="${element.shadowRoot.mode}">`;
for (const node of element.shadowRoot.childNodes) {
innerHTML += this.serializeToString(node, options);
}
innerHTML += '</template>';
}
return `<${tagName}${this._getAttributes(element)}>${innerHTML}</${tagName}>`;
case Node.DOCUMENT_FRAGMENT_NODE:
case Node.DOCUMENT_NODE:
let html = '';
for (const node of root.childNodes) {
html += this.serializeToString(node, options);
}
return html;
case Node.COMMENT_NODE:
return `<!--${root.textContent}-->`;
case Node.TEXT_NODE:
return root['textContent'];
case Node.DOCUMENT_TYPE_NODE:
const doctype = <DocumentType>root;
const identifier = doctype.publicId ? ' PUBLIC' : doctype.systemId ? ' SYSTEM' : '';
const publicId = doctype.publicId ? ` "${doctype.publicId}"` : '';
const systemId = doctype.systemId ? ` "${doctype.systemId}"` : '';
return `<!DOCTYPE ${doctype.name}${identifier}${publicId}${systemId}>`;
}
return '';
}
/**
* Returns attributes as a string.
*
* @param element Element.
* @returns Attributes.
*/
private _getAttributes(element: IElement): string {
let attributeString = '';
if (!(<Element>element)._attributes.is && (<Element>element)._isValue) {
attributeString += ' is="' + escape((<Element>element)._isValue) + '"';
}
for (const attribute of Object.values((<Element>element)._attributes)) {
if (attribute.value !== null) {
attributeString += ' ' + attribute.name + '="' + escape(attribute.value) + '"';
}
}
return attributeString;
}
}