xmlbuilder2
Version:
An XML builder for node.js
1,057 lines • 71.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseWriter = void 0;
const interfaces_1 = require("@oozcitak/dom/lib/dom/interfaces");
const LocalNameSet_1 = require("@oozcitak/dom/lib/serializer/LocalNameSet");
const NamespacePrefixMap_1 = require("@oozcitak/dom/lib/serializer/NamespacePrefixMap");
const infra_1 = require("@oozcitak/infra");
const algorithm_1 = require("@oozcitak/dom/lib/algorithm");
const constants_1 = require("../constants");
/**
* Pre-serializes XML nodes.
*/
class BaseWriter {
static _VoidElementNames = new Set(['area', 'base', 'basefont',
'bgsound', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input', 'keygen',
'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr']);
_builderOptions;
_writerOptions;
/**
* Initializes a new instance of `BaseWriter`.
*
* @param builderOptions - XML builder options
*/
constructor(builderOptions) {
this._builderOptions = builderOptions;
}
/**
* Used by derived classes to serialize the XML declaration.
*
* @param version - a version number string
* @param encoding - encoding declaration
* @param standalone - standalone document declaration
*/
declaration(version, encoding, standalone) { }
/**
* Used by derived classes to serialize a DocType node.
*
* @param name - node name
* @param publicId - public identifier
* @param systemId - system identifier
*/
docType(name, publicId, systemId) { }
/**
* Used by derived classes to serialize a comment node.
*
* @param data - node data
*/
comment(data) { }
/**
* Used by derived classes to serialize a text node.
*
* @param data - node data
*/
text(data) { }
/**
* Used by derived classes to serialize a processing instruction node.
*
* @param target - instruction target
* @param data - node data
*/
instruction(target, data) { }
/**
* Used by derived classes to serialize a CData section node.
*
* @param data - node data
*/
cdata(data) { }
/**
* Used by derived classes to serialize the beginning of the opening tag of an
* element node.
*
* @param name - node name
*/
openTagBegin(name) { }
/**
* Used by derived classes to serialize the ending of the opening tag of an
* element node.
*
* @param name - node name
* @param selfClosing - whether the element node is self closing
* @param voidElement - whether the element node is a HTML void element
*/
openTagEnd(name, selfClosing, voidElement) { }
/**
* Used by derived classes to serialize the closing tag of an element node.
*
* @param name - node name
*/
closeTag(name) { }
/**
* Used by derived classes to serialize attributes or namespace declarations.
*
* @param attributes - attribute array
*/
attributes(attributes) {
for (const attr of attributes) {
this.attribute(attr[1] === null ? attr[2] : attr[1] + ':' + attr[2], attr[3]);
}
}
/**
* Used by derived classes to serialize an attribute or namespace declaration.
*
* @param name - node name
* @param value - node value
*/
attribute(name, value) { }
/**
* Used by derived classes to perform any pre-processing steps before starting
* serializing an element node.
*
* @param name - node name
*/
beginElement(name) { }
/**
* Used by derived classes to perform any post-processing steps after
* completing serializing an element node.
*
* @param name - node name
*/
endElement(name) { }
/**
* Gets the current depth of the XML tree.
*/
level = 0;
/**
* Gets the current XML node.
*/
currentNode;
/**
* Produces an XML serialization of the given node. The pre-serializer inserts
* namespace declarations where necessary and produces qualified names for
* nodes and attributes.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
serializeNode(node, requireWellFormed) {
const hasNamespaces = (node._nodeDocument !== undefined && node._nodeDocument._hasNamespaces);
this.level = 0;
this.currentNode = node;
if (hasNamespaces) {
/** From: https://w3c.github.io/DOM-Parsing/#xml-serialization
*
* 1. Let namespace be a context namespace with value null.
* The context namespace tracks the XML serialization algorithm's current
* default namespace. The context namespace is changed when either an Element
* Node has a default namespace declaration, or the algorithm generates a
* default namespace declaration for the Element Node to match its own
* namespace. The algorithm assumes no namespace (null) to start.
* 2. Let prefix map be a new namespace prefix map.
* 3. Add the XML namespace with prefix value "xml" to prefix map.
* 4. Let prefix index be a generated namespace prefix index with value 1.
* The generated namespace prefix index is used to generate a new unique
* prefix value when no suitable existing namespace prefix is available to
* serialize a node's namespaceURI (or the namespaceURI of one of node's
* attributes). See the generate a prefix algorithm.
*/
let namespace = null;
const prefixMap = new NamespacePrefixMap_1.NamespacePrefixMap();
prefixMap.set("xml", infra_1.namespace.XML);
const prefixIndex = { value: 1 };
/**
* 5. Return the result of running the XML serialization algorithm on node
* passing the context namespace namespace, namespace prefix map prefix map,
* generated namespace prefix index reference to prefix index, and the
* flag require well-formed. If an exception occurs during the execution
* of the algorithm, then catch that exception and throw an
* "InvalidStateError" DOMException.
*/
this._serializeNodeNS(node, namespace, prefixMap, prefixIndex, requireWellFormed);
}
else {
this._serializeNode(node, requireWellFormed);
}
}
/**
* Produces an XML serialization of a node.
*
* @param node - node to serialize
* @param namespace - context namespace
* @param prefixMap - namespace prefix map
* @param prefixIndex - generated namespace prefix index
* @param requireWellFormed - whether to check conformance
*/
_serializeNodeNS(node, namespace, prefixMap, prefixIndex, requireWellFormed) {
this.currentNode = node;
switch (node.nodeType) {
case interfaces_1.NodeType.Element:
this._serializeElementNS(node, namespace, prefixMap, prefixIndex, requireWellFormed);
break;
case interfaces_1.NodeType.Document:
this._serializeDocumentNS(node, namespace, prefixMap, prefixIndex, requireWellFormed);
break;
case interfaces_1.NodeType.Comment:
this._serializeComment(node, requireWellFormed);
break;
case interfaces_1.NodeType.Text:
this._serializeText(node, requireWellFormed);
break;
case interfaces_1.NodeType.DocumentFragment:
this._serializeDocumentFragmentNS(node, namespace, prefixMap, prefixIndex, requireWellFormed);
break;
case interfaces_1.NodeType.DocumentType:
this._serializeDocumentType(node, requireWellFormed);
break;
case interfaces_1.NodeType.ProcessingInstruction:
this._serializeProcessingInstruction(node, requireWellFormed);
break;
case interfaces_1.NodeType.CData:
this._serializeCData(node, requireWellFormed);
break;
default:
throw new Error(`Unknown node type: ${node.nodeType}`);
}
}
/**
* Produces an XML serialization of a node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
_serializeNode(node, requireWellFormed) {
this.currentNode = node;
switch (node.nodeType) {
case interfaces_1.NodeType.Element:
this._serializeElement(node, requireWellFormed);
break;
case interfaces_1.NodeType.Document:
this._serializeDocument(node, requireWellFormed);
break;
case interfaces_1.NodeType.Comment:
this._serializeComment(node, requireWellFormed);
break;
case interfaces_1.NodeType.Text:
this._serializeText(node, requireWellFormed);
break;
case interfaces_1.NodeType.DocumentFragment:
this._serializeDocumentFragment(node, requireWellFormed);
break;
case interfaces_1.NodeType.DocumentType:
this._serializeDocumentType(node, requireWellFormed);
break;
case interfaces_1.NodeType.ProcessingInstruction:
this._serializeProcessingInstruction(node, requireWellFormed);
break;
case interfaces_1.NodeType.CData:
this._serializeCData(node, requireWellFormed);
break;
default:
throw new Error(`Unknown node type: ${node.nodeType}`);
}
}
/**
* Produces an XML serialization of an element node.
*
* @param node - node to serialize
* @param namespace - context namespace
* @param prefixMap - namespace prefix map
* @param prefixIndex - generated namespace prefix index
* @param requireWellFormed - whether to check conformance
*/
_serializeElementNS(node, namespace, prefixMap, prefixIndex, requireWellFormed) {
const attributes = [];
/**
* From: https://w3c.github.io/DOM-Parsing/#xml-serializing-an-element-node
*
* 1. If the require well-formed flag is set (its value is true), and this
* node's localName attribute contains the character ":" (U+003A COLON) or
* does not match the XML Name production, then throw an exception; the
* serialization of this node would not be a well-formed element.
*/
if (requireWellFormed && (node.localName.indexOf(":") !== -1 ||
!(0, algorithm_1.xml_isName)(node.localName))) {
throw new Error("Node local name contains invalid characters (well-formed required).");
}
/**
* 2. Let markup be the string "<" (U+003C LESS-THAN SIGN).
* 3. Let qualified name be an empty string.
* 4. Let skip end tag be a boolean flag with value false.
* 5. Let ignore namespace definition attribute be a boolean flag with value
* false.
* 6. Given prefix map, copy a namespace prefix map and let map be the
* result.
* 7. Let local prefixes map be an empty map. The map has unique Node prefix
* strings as its keys, with corresponding namespaceURI Node values as the
* map's key values (in this map, the null namespace is represented by the
* empty string).
*
* _Note:_ This map is local to each element. It is used to ensure there
* are no conflicting prefixes should a new namespace prefix attribute need
* to be generated. It is also used to enable skipping of duplicate prefix
* definitions when writing an element's attributes: the map allows the
* algorithm to distinguish between a prefix in the namespace prefix map
* that might be locally-defined (to the current Element) and one that is
* not.
* 8. Let local default namespace be the result of recording the namespace
* information for node given map and local prefixes map.
*
* _Note:_ The above step will update map with any found namespace prefix
* definitions, add the found prefix definitions to the local prefixes map
* and return a local default namespace value defined by a default namespace
* attribute if one exists. Otherwise it returns null.
* 9. Let inherited ns be a copy of namespace.
* 10. Let ns be the value of node's namespaceURI attribute.
*/
let qualifiedName = '';
let skipEndTag = false;
let ignoreNamespaceDefinitionAttribute = false;
let map = prefixMap.copy();
let localPrefixesMap = {};
let localDefaultNamespace = this._recordNamespaceInformation(node, map, localPrefixesMap);
let inheritedNS = namespace;
let ns = node.namespaceURI;
/** 11. If inherited ns is equal to ns, then: */
if (inheritedNS === ns) {
/**
* 11.1. If local default namespace is not null, then set ignore
* namespace definition attribute to true.
*/
if (localDefaultNamespace !== null) {
ignoreNamespaceDefinitionAttribute = true;
}
/**
* 11.2. If ns is the XML namespace, then append to qualified name the
* concatenation of the string "xml:" and the value of node's localName.
* 11.3. Otherwise, append to qualified name the value of node's
* localName. The node's prefix if it exists, is dropped.
*/
if (ns === infra_1.namespace.XML) {
qualifiedName = 'xml:' + node.localName;
}
else {
qualifiedName = node.localName;
}
/** 11.4. Append the value of qualified name to markup. */
this.beginElement(qualifiedName);
this.openTagBegin(qualifiedName);
}
else {
/**
* 12. Otherwise, inherited ns is not equal to ns (the node's own
* namespace is different from the context namespace of its parent).
* Run these sub-steps:
*
* 12.1. Let prefix be the value of node's prefix attribute.
* 12.2. Let candidate prefix be the result of retrieving a preferred
* prefix string prefix from map given namespace ns. The above may return
* null if no namespace key ns exists in map.
*/
let prefix = node.prefix;
/**
* We don't need to run "retrieving a preferred prefix string" algorithm if
* the element has no prefix and its namespace matches to the default
* namespace.
* See: https://github.com/web-platform-tests/wpt/pull/16703
*/
let candidatePrefix = null;
if (prefix !== null || ns !== localDefaultNamespace) {
candidatePrefix = map.get(prefix, ns);
}
/**
* 12.3. If the value of prefix matches "xmlns", then run the following
* steps:
*/
if (prefix === "xmlns") {
/**
* 12.3.1. If the require well-formed flag is set, then throw an error.
* An Element with prefix "xmlns" will not legally round-trip in a
* conforming XML parser.
*/
if (requireWellFormed) {
throw new Error("An element cannot have the 'xmlns' prefix (well-formed required).");
}
/**
* 12.3.2. Let candidate prefix be the value of prefix.
*/
candidatePrefix = prefix;
}
/**
* 12.4.Found a suitable namespace prefix: if candidate prefix is not
* null (a namespace prefix is defined which maps to ns), then:
*/
if (candidatePrefix !== null) {
/**
* The following may serialize a different prefix than the Element's
* existing prefix if it already had one. However, the retrieving a
* preferred prefix string algorithm already tried to match the
* existing prefix if possible.
*
* 12.4.1. Append to qualified name the concatenation of candidate
* prefix, ":" (U+003A COLON), and node's localName. There exists on
* this node or the node's ancestry a namespace prefix definition that
* defines the node's namespace.
* 12.4.2. If the local default namespace is not null (there exists a
* locally-defined default namespace declaration attribute) and its
* value is not the XML namespace, then let inherited ns get the value
* of local default namespace unless the local default namespace is the
* empty string in which case let it get null (the context namespace
* is changed to the declared default, rather than this node's own
* namespace).
*
* _Note:_ Any default namespace definitions or namespace prefixes that
* define the XML namespace are omitted when serializing this node's
* attributes.
*/
qualifiedName = candidatePrefix + ':' + node.localName;
if (localDefaultNamespace !== null && localDefaultNamespace !== infra_1.namespace.XML) {
inheritedNS = localDefaultNamespace || null;
}
/**
* 12.4.3. Append the value of qualified name to markup.
*/
this.beginElement(qualifiedName);
this.openTagBegin(qualifiedName);
/** 12.5. Otherwise, if prefix is not null, then: */
}
else if (prefix !== null) {
/**
* _Note:_ By this step, there is no namespace or prefix mapping
* declaration in this node (or any parent node visited by this
* algorithm) that defines prefix otherwise the step labelled Found
* a suitable namespace prefix would have been followed. The sub-steps
* that follow will create a new namespace prefix declaration for prefix
* and ensure that prefix does not conflict with an existing namespace
* prefix declaration of the same localName in node's attribute list.
*
* 12.5.1. If the local prefixes map contains a key matching prefix,
* then let prefix be the result of generating a prefix providing as
* input map, ns, and prefix index.
*/
if (prefix in localPrefixesMap) {
prefix = this._generatePrefix(ns, map, prefixIndex);
}
/**
* 12.5.2. Add prefix to map given namespace ns.
* 12.5.3. Append to qualified name the concatenation of prefix, ":"
* (U+003A COLON), and node's localName.
* 12.5.4. Append the value of qualified name to markup.
*/
map.set(prefix, ns);
qualifiedName += prefix + ':' + node.localName;
this.beginElement(qualifiedName);
this.openTagBegin(qualifiedName);
/**
* 12.5.5. Append the following to markup, in the order listed:
*
* _Note:_ The following serializes a namespace prefix declaration for
* prefix which was just added to the map.
*
* 12.5.5.1. " " (U+0020 SPACE);
* 12.5.5.2. The string "xmlns:";
* 12.5.5.3. The value of prefix;
* 12.5.5.4. "="" (U+003D EQUALS SIGN, U+0022 QUOTATION MARK);
* 12.5.5.5. The result of serializing an attribute value given ns and
* the require well-formed flag as input;
* 12.5.5.6. """ (U+0022 QUOTATION MARK).
*/
attributes.push([null, 'xmlns', prefix,
this._serializeAttributeValue(ns, requireWellFormed)]);
/**
* 12.5.5.7. If local default namespace is not null (there exists a
* locally-defined default namespace declaration attribute), then
* let inherited ns get the value of local default namespace unless the
* local default namespace is the empty string in which case let it get
* null.
*/
if (localDefaultNamespace !== null) {
inheritedNS = localDefaultNamespace || null;
}
/**
* 12.6. Otherwise, if local default namespace is null, or local
* default namespace is not null and its value is not equal to ns, then:
*/
}
else if (localDefaultNamespace === null ||
(localDefaultNamespace !== null && localDefaultNamespace !== ns)) {
/**
* _Note:_ At this point, the namespace for this node still needs to be
* serialized, but there's no prefix (or candidate prefix) available; the
* following uses the default namespace declaration to define the
* namespace--optionally replacing an existing default declaration
* if present.
*
* 12.6.1. Set the ignore namespace definition attribute flag to true.
* 12.6.2. Append to qualified name the value of node's localName.
* 12.6.3. Let the value of inherited ns be ns.
*
* _Note:_ The new default namespace will be used in the serialization
* to define this node's namespace and act as the context namespace for
* its children.
*/
ignoreNamespaceDefinitionAttribute = true;
qualifiedName += node.localName;
inheritedNS = ns;
/**
* 12.6.4. Append the value of qualified name to markup.
*/
this.beginElement(qualifiedName);
this.openTagBegin(qualifiedName);
/**
* 12.6.5. Append the following to markup, in the order listed:
*
* _Note:_ The following serializes the new (or replacement) default
* namespace definition.
*
* 12.6.5.1. " " (U+0020 SPACE);
* 12.6.5.2. The string "xmlns";
* 12.6.5.3. "="" (U+003D EQUALS SIGN, U+0022 QUOTATION MARK);
* 12.6.5.4. The result of serializing an attribute value given ns
* and the require well-formed flag as input;
* 12.6.5.5. """ (U+0022 QUOTATION MARK).
*/
attributes.push([null, null, 'xmlns',
this._serializeAttributeValue(ns, requireWellFormed)]);
/**
* 12.7. Otherwise, the node has a local default namespace that matches
* ns. Append to qualified name the value of node's localName, let the
* value of inherited ns be ns, and append the value of qualified name
* to markup.
*/
}
else {
qualifiedName += node.localName;
inheritedNS = ns;
this.beginElement(qualifiedName);
this.openTagBegin(qualifiedName);
}
}
/**
* 13. Append to markup the result of the XML serialization of node's
* attributes given map, prefix index, local prefixes map, ignore namespace
* definition attribute flag, and require well-formed flag.
*/
attributes.push(...this._serializeAttributesNS(node, map, prefixIndex, localPrefixesMap, ignoreNamespaceDefinitionAttribute, requireWellFormed));
this.attributes(attributes);
/**
* 14. If ns is the HTML namespace, and the node's list of children is
* empty, and the node's localName matches any one of the following void
* elements: "area", "base", "basefont", "bgsound", "br", "col", "embed",
* "frame", "hr", "img", "input", "keygen", "link", "menuitem", "meta",
* "param", "source", "track", "wbr"; then append the following to markup,
* in the order listed:
* 14.1. " " (U+0020 SPACE);
* 14.2. "/" (U+002F SOLIDUS).
* and set the skip end tag flag to true.
* 15. If ns is not the HTML namespace, and the node's list of children is
* empty, then append "/" (U+002F SOLIDUS) to markup and set the skip end
* tag flag to true.
* 16. Append ">" (U+003E GREATER-THAN SIGN) to markup.
*/
const isHTML = (ns === infra_1.namespace.HTML);
if (isHTML && node.childNodes.length === 0 &&
BaseWriter._VoidElementNames.has(node.localName)) {
this.openTagEnd(qualifiedName, true, true);
this.endElement(qualifiedName);
skipEndTag = true;
}
else if (!isHTML && node.childNodes.length === 0) {
this.openTagEnd(qualifiedName, true, false);
this.endElement(qualifiedName);
skipEndTag = true;
}
else {
this.openTagEnd(qualifiedName, false, false);
}
/**
* 17. If the value of skip end tag is true, then return the value of markup
* and skip the remaining steps. The node is a leaf-node.
*/
if (skipEndTag)
return;
/**
* 18. If ns is the HTML namespace, and the node's localName matches the
* string "template", then this is a template element. Append to markup the
* result of XML serializing a DocumentFragment node given the template
* element's template contents (a DocumentFragment), providing inherited
* ns, map, prefix index, and the require well-formed flag.
*
* _Note:_ This allows template content to round-trip, given the rules for
* parsing XHTML documents.
*
* 19. Otherwise, append to markup the result of running the XML
* serialization algorithm on each of node's children, in tree order,
* providing inherited ns, map, prefix index, and the require well-formed
* flag.
*/
if (isHTML && node.localName === "template") {
// TODO: serialize template contents
}
else {
for (const childNode of node.childNodes) {
this.level++;
this._serializeNodeNS(childNode, inheritedNS, map, prefixIndex, requireWellFormed);
this.level--;
}
}
/**
* 20. Append the following to markup, in the order listed:
* 20.1. "</" (U+003C LESS-THAN SIGN, U+002F SOLIDUS);
* 20.2. The value of qualified name;
* 20.3. ">" (U+003E GREATER-THAN SIGN).
* 21. Return the value of markup.
*/
this.closeTag(qualifiedName);
this.endElement(qualifiedName);
}
/**
* Produces an XML serialization of an element node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
_serializeElement(node, requireWellFormed) {
/**
* From: https://w3c.github.io/DOM-Parsing/#xml-serializing-an-element-node
*
* 1. If the require well-formed flag is set (its value is true), and this
* node's localName attribute contains the character ":" (U+003A COLON) or
* does not match the XML Name production, then throw an exception; the
* serialization of this node would not be a well-formed element.
*/
if (requireWellFormed && (node.localName.indexOf(":") !== -1 ||
!(0, algorithm_1.xml_isName)(node.localName))) {
throw new Error("Node local name contains invalid characters (well-formed required).");
}
/**
* 2. Let markup be the string "<" (U+003C LESS-THAN SIGN).
* 3. Let qualified name be an empty string.
* 4. Let skip end tag be a boolean flag with value false.
* 5. Let ignore namespace definition attribute be a boolean flag with value
* false.
* 6. Given prefix map, copy a namespace prefix map and let map be the
* result.
* 7. Let local prefixes map be an empty map. The map has unique Node prefix
* strings as its keys, with corresponding namespaceURI Node values as the
* map's key values (in this map, the null namespace is represented by the
* empty string).
*
* _Note:_ This map is local to each element. It is used to ensure there
* are no conflicting prefixes should a new namespace prefix attribute need
* to be generated. It is also used to enable skipping of duplicate prefix
* definitions when writing an element's attributes: the map allows the
* algorithm to distinguish between a prefix in the namespace prefix map
* that might be locally-defined (to the current Element) and one that is
* not.
* 8. Let local default namespace be the result of recording the namespace
* information for node given map and local prefixes map.
*
* _Note:_ The above step will update map with any found namespace prefix
* definitions, add the found prefix definitions to the local prefixes map
* and return a local default namespace value defined by a default namespace
* attribute if one exists. Otherwise it returns null.
* 9. Let inherited ns be a copy of namespace.
* 10. Let ns be the value of node's namespaceURI attribute.
*/
let skipEndTag = false;
/** 11. If inherited ns is equal to ns, then: */
/**
* 11.1. If local default namespace is not null, then set ignore
* namespace definition attribute to true.
*/
/**
* 11.2. If ns is the XML namespace, then append to qualified name the
* concatenation of the string "xml:" and the value of node's localName.
* 11.3. Otherwise, append to qualified name the value of node's
* localName. The node's prefix if it exists, is dropped.
*/
const qualifiedName = node.localName;
/** 11.4. Append the value of qualified name to markup. */
this.beginElement(qualifiedName);
this.openTagBegin(qualifiedName);
/**
* 13. Append to markup the result of the XML serialization of node's
* attributes given map, prefix index, local prefixes map, ignore namespace
* definition attribute flag, and require well-formed flag.
*/
const attributes = this._serializeAttributes(node, requireWellFormed);
this.attributes(attributes);
/**
* 14. If ns is the HTML namespace, and the node's list of children is
* empty, and the node's localName matches any one of the following void
* elements: "area", "base", "basefont", "bgsound", "br", "col", "embed",
* "frame", "hr", "img", "input", "keygen", "link", "menuitem", "meta",
* "param", "source", "track", "wbr"; then append the following to markup,
* in the order listed:
* 14.1. " " (U+0020 SPACE);
* 14.2. "/" (U+002F SOLIDUS).
* and set the skip end tag flag to true.
* 15. If ns is not the HTML namespace, and the node's list of children is
* empty, then append "/" (U+002F SOLIDUS) to markup and set the skip end
* tag flag to true.
* 16. Append ">" (U+003E GREATER-THAN SIGN) to markup.
*/
if (!node.hasChildNodes()) {
this.openTagEnd(qualifiedName, true, false);
this.endElement(qualifiedName);
skipEndTag = true;
}
else {
this.openTagEnd(qualifiedName, false, false);
}
/**
* 17. If the value of skip end tag is true, then return the value of markup
* and skip the remaining steps. The node is a leaf-node.
*/
if (skipEndTag)
return;
/**
* 18. If ns is the HTML namespace, and the node's localName matches the
* string "template", then this is a template element. Append to markup the
* result of XML serializing a DocumentFragment node given the template
* element's template contents (a DocumentFragment), providing inherited
* ns, map, prefix index, and the require well-formed flag.
*
* _Note:_ This allows template content to round-trip, given the rules for
* parsing XHTML documents.
*
* 19. Otherwise, append to markup the result of running the XML
* serialization algorithm on each of node's children, in tree order,
* providing inherited ns, map, prefix index, and the require well-formed
* flag.
*/
for (const childNode of node._children) {
this.level++;
this._serializeNode(childNode, requireWellFormed);
this.level--;
}
/**
* 20. Append the following to markup, in the order listed:
* 20.1. "</" (U+003C LESS-THAN SIGN, U+002F SOLIDUS);
* 20.2. The value of qualified name;
* 20.3. ">" (U+003E GREATER-THAN SIGN).
* 21. Return the value of markup.
*/
this.closeTag(qualifiedName);
this.endElement(qualifiedName);
}
/**
* Produces an XML serialization of a document node.
*
* @param node - node to serialize
* @param namespace - context namespace
* @param prefixMap - namespace prefix map
* @param prefixIndex - generated namespace prefix index
* @param requireWellFormed - whether to check conformance
*/
_serializeDocumentNS(node, namespace, prefixMap, prefixIndex, requireWellFormed) {
/**
* If the require well-formed flag is set (its value is true), and this node
* has no documentElement (the documentElement attribute's value is null),
* then throw an exception; the serialization of this node would not be a
* well-formed document.
*/
if (requireWellFormed && node.documentElement === null) {
throw new Error("Missing document element (well-formed required).");
}
/**
* Otherwise, run the following steps:
* 1. Let serialized document be an empty string.
* 2. For each child child of node, in tree order, run the XML
* serialization algorithm on the child passing along the provided
* arguments, and append the result to serialized document.
*
* _Note:_ This will serialize any number of ProcessingInstruction and
* Comment nodes both before and after the Document's documentElement node,
* including at most one DocumentType node. (Text nodes are not allowed as
* children of the Document.)
*
* 3. Return the value of serialized document.
*/
for (const childNode of node.childNodes) {
this._serializeNodeNS(childNode, namespace, prefixMap, prefixIndex, requireWellFormed);
}
}
/**
* Produces an XML serialization of a document node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
_serializeDocument(node, requireWellFormed) {
/**
* If the require well-formed flag is set (its value is true), and this node
* has no documentElement (the documentElement attribute's value is null),
* then throw an exception; the serialization of this node would not be a
* well-formed document.
*/
if (requireWellFormed && node.documentElement === null) {
throw new Error("Missing document element (well-formed required).");
}
/**
* Otherwise, run the following steps:
* 1. Let serialized document be an empty string.
* 2. For each child child of node, in tree order, run the XML
* serialization algorithm on the child passing along the provided
* arguments, and append the result to serialized document.
*
* _Note:_ This will serialize any number of ProcessingInstruction and
* Comment nodes both before and after the Document's documentElement node,
* including at most one DocumentType node. (Text nodes are not allowed as
* children of the Document.)
*
* 3. Return the value of serialized document.
*/
for (const childNode of node._children) {
this._serializeNode(childNode, requireWellFormed);
}
}
/**
* Produces an XML serialization of a comment node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
_serializeComment(node, requireWellFormed) {
/**
* If the require well-formed flag is set (its value is true), and node's
* data contains characters that are not matched by the XML Char production
* or contains "--" (two adjacent U+002D HYPHEN-MINUS characters) or that
* ends with a "-" (U+002D HYPHEN-MINUS) character, then throw an exception;
* the serialization of this node's data would not be well-formed.
*/
if (requireWellFormed && (!(0, algorithm_1.xml_isLegalChar)(node.data) ||
node.data.indexOf("--") !== -1 || node.data.endsWith("-"))) {
throw new Error("Comment data contains invalid characters (well-formed required).");
}
/**
* Otherwise, return the concatenation of "<!--", node's data, and "-->".
*/
this.comment(node.data);
}
/**
* Produces an XML serialization of a text node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
* @param level - current depth of the XML tree
*/
_serializeText(node, requireWellFormed) {
/**
* 1. If the require well-formed flag is set (its value is true), and
* node's data contains characters that are not matched by the XML Char
* production, then throw an exception; the serialization of this node's
* data would not be well-formed.
*/
if (requireWellFormed && !(0, algorithm_1.xml_isLegalChar)(node.data)) {
throw new Error("Text data contains invalid characters (well-formed required).");
}
/**
* 2. Let markup be the value of node's data.
* 3. Replace any occurrences of "&" in markup by "&".
* 4. Replace any occurrences of "<" in markup by "<".
* 5. Replace any occurrences of ">" in markup by ">".
* 6. Return the value of markup.
*/
const markup = node.data.replace(constants_1.nonEntityAmpersandRegex, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
this.text(markup);
}
/**
* Produces an XML serialization of a document fragment node.
*
* @param node - node to serialize
* @param namespace - context namespace
* @param prefixMap - namespace prefix map
* @param prefixIndex - generated namespace prefix index
* @param requireWellFormed - whether to check conformance
*/
_serializeDocumentFragmentNS(node, namespace, prefixMap, prefixIndex, requireWellFormed) {
/**
* 1. Let markup the empty string.
* 2. For each child child of node, in tree order, run the XML serialization
* algorithm on the child given namespace, prefix map, a reference to prefix
* index, and flag require well-formed. Concatenate the result to markup.
* 3. Return the value of markup.
*/
for (const childNode of node.childNodes) {
this._serializeNodeNS(childNode, namespace, prefixMap, prefixIndex, requireWellFormed);
}
}
/**
* Produces an XML serialization of a document fragment node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
_serializeDocumentFragment(node, requireWellFormed) {
/**
* 1. Let markup the empty string.
* 2. For each child child of node, in tree order, run the XML serialization
* algorithm on the child given namespace, prefix map, a reference to prefix
* index, and flag require well-formed. Concatenate the result to markup.
* 3. Return the value of markup.
*/
for (const childNode of node._children) {
this._serializeNode(childNode, requireWellFormed);
}
}
/**
* Produces an XML serialization of a document type node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
_serializeDocumentType(node, requireWellFormed) {
/**
* 1. If the require well-formed flag is true and the node's publicId
* attribute contains characters that are not matched by the XML PubidChar
* production, then throw an exception; the serialization of this node
* would not be a well-formed document type declaration.
*/
if (requireWellFormed && !(0, algorithm_1.xml_isPubidChar)(node.publicId)) {
throw new Error("DocType public identifier does not match PubidChar construct (well-formed required).");
}
/**
* 2. If the require well-formed flag is true and the node's systemId
* attribute contains characters that are not matched by the XML Char
* production or that contains both a """ (U+0022 QUOTATION MARK) and a
* "'" (U+0027 APOSTROPHE), then throw an exception; the serialization
* of this node would not be a well-formed document type declaration.
*/
if (requireWellFormed &&
(!(0, algorithm_1.xml_isLegalChar)(node.systemId) ||
(node.systemId.indexOf('"') !== -1 && node.systemId.indexOf("'") !== -1))) {
throw new Error("DocType system identifier contains invalid characters (well-formed required).");
}
/**
* 3. Let markup be an empty string.
* 4. Append the string "<!DOCTYPE" to markup.
* 5. Append " " (U+0020 SPACE) to markup.
* 6. Append the value of the node's name attribute to markup. For a node
* belonging to an HTML document, the value will be all lowercase.
* 7. If the node's publicId is not the empty string then append the
* following, in the order listed, to markup:
* 7.1. " " (U+0020 SPACE);
* 7.2. The string "PUBLIC";
* 7.3. " " (U+0020 SPACE);
* 7.4. """ (U+0022 QUOTATION MARK);
* 7.5. The value of the node's publicId attribute;
* 7.6. """ (U+0022 QUOTATION MARK).
* 8. If the node's systemId is not the empty string and the node's publicId
* is set to the empty string, then append the following, in the order
* listed, to markup:
* 8.1. " " (U+0020 SPACE);
* 8.2. The string "SYSTEM".
* 9. If the node's systemId is not the empty string then append the
* following, in the order listed, to markup:
* 9.2. " " (U+0020 SPACE);
* 9.3. """ (U+0022 QUOTATION MARK);
* 9.3. The value of the node's systemId attribute;
* 9.4. """ (U+0022 QUOTATION MARK).
* 10. Append ">" (U+003E GREATER-THAN SIGN) to markup.
* 11. Return the value of markup.
*/
this.docType(node.name, node.publicId, node.systemId);
}
/**
* Produces an XML serialization of a processing instruction node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
_serializeProcessingInstruction(node, requireWellFormed) {
/**
* 1. If the require well-formed flag is set (its value is true), and node's
* target contains a ":" (U+003A COLON) character or is an ASCII
* case-insensitive match for the string "xml", then throw an exception;
* the serialization of this node's target would not be well-formed.
*/
if (requireWellFormed && (node.target.indexOf(":") !== -1 || (/^xml$/i).test(node.target))) {
throw new Error("Processing instruction target contains invalid characters (well-formed required).");
}
/**
* 2. If the require well-formed flag is set (its value is true), and node's
* data contains characters that are not matched by the XML Char production
* or contains the string "?>" (U+003F QUESTION MARK,
* U+003E GREATER-THAN SIGN), then throw an exception; the serialization of
* this node's data would not be well-formed.
*/
if (requireWellFormed && (!(0, algorithm_1.xml_isLegalChar)(node.data) ||
node.data.indexOf("?>") !== -1)) {
throw new Error("Processing instruction data contains invalid characters (well-formed required).");
}
/**
* 3. Let markup be the concatenation of the following, in the order listed:
* 3.1. "<?" (U+003C LESS-THAN SIGN, U+003F QUESTION MARK);
* 3.2. The value of node's target;
* 3.3. " " (U+0020 SPACE);
* 3.4. The value of node's data;
* 3.5. "?>" (U+003F QUESTION MARK, U+003E GREATER-THAN SIGN).
* 4. Return the value of markup.
*/
this.instruction(node.target, node.data);
}
/**
* Produces an XML serialization of a CDATA node.
*
* @param node - node to serialize
* @param requireWellFormed - whether to check conformance
*/
_serializeCData(node, requireWellFormed) {
if (requireWellFormed && (node.data.indexOf("]]>") !== -1)) {
throw new Error("CDATA contains invalid characters (well-formed required).");
}
this.cdata(node.data);
}
/**
* Produces an XML serialization of the attributes of an element node.
*
* @param node - node to serialize
* @param map - namespace prefix map
* @param prefixIndex - generated namespace prefix index
* @param localPrefixesMap - local prefixes map
* @param ignoreNamespaceDefinitionAttribute - whether to ignore namespace
* attributes
* @param requireWellFormed - whether to check conformance
*/
_serializeAttributesNS(node, map, prefixIndex, localPrefixesMap, ignoreNamespaceDefinitionAttribute, requireWellFormed) {
/**
* 1. Let result be the empty string.
* 2. Let localname set be a new empty namespace localname set. This
* localname set will contain tuples of unique attribute namespaceURI and
* localName pairs, and is populated as each attr is processed. This set is
* used to [optionally] enforce the well-formed constraint that an element
* cannot have two attributes with the same namespaceURI and localName.
* This can occur when two otherwise identical attributes on the same
* element differ only by their prefix values.
*/
const result = [];
const localNameSet = requireWellFormed ? new LocalNameSet_1.LocalNameSet() : undefined;
/**
* 3. Loop: For each attribute attr in element's attributes, in the order
* they are specified in the element's attribute list:
*/
for (const attr of node.attributes) {
// Optimize common case
if (!requireWellFormed && !ignoreNamespaceDefinitionAttribute && attr.namespaceURI === null) {
result.push([null, null, attr.localName,
this._serializeAttributeValue(attr.value, requireWellFormed)]);
continue;
}
/**
* 3.1. If the require well-formed flag is set (its value is true), and the
* localname set contains a tuple whose values match those of a new tuple
* consisting of attr's namespaceURI attribute and localName attribute,
* then throw an exception; the serialization of this attr would fail to
* produce a well-formed element serialization.
*/
if (requireWellFormed && localNameSet && localNameSet.has(attr.namespaceURI, attr.localName)) {
throw new Error("Element contains duplicate attributes (well-formed required).");
}
/**
* 3.2. Create a new tuple consisting of attr's namespaceURI attribute and
* localName attribute, and add it to the localname set.
* 3.3. Let attribute namespace be the value of attr's namespaceURI value.
* 3.4. Let candidate pref