'use strict'; | |
var DefaultTreeAdapter = require('../tree_adapters/default'), | |
Doctype = require('../common/doctype'), | |
Utils = require('../common/utils'), | |
HTML = require('../common/html'); | |
//Aliases | |
var $ = HTML.TAG_NAMES, | |
NS = HTML.NAMESPACES; | |
//Default serializer options | |
var DEFAULT_OPTIONS = { | |
encodeHtmlEntities: true | |
}; | |
//Escaping regexes | |
var AMP_REGEX = /&/g, | |
NBSP_REGEX = /\u00a0/g, | |
DOUBLE_QUOTE_REGEX = /"/g, | |
LT_REGEX = /</g, | |
GT_REGEX = />/g; | |
//Escape string | |
function escapeString(str, attrMode) { | |
str = str | |
.replace(AMP_REGEX, '&') | |
.replace(NBSP_REGEX, ' '); | |
if (attrMode) | |
str = str.replace(DOUBLE_QUOTE_REGEX, '"'); | |
else { | |
str = str | |
.replace(LT_REGEX, '<') | |
.replace(GT_REGEX, '>'); | |
} | |
return str; | |
} | |
//Enquote doctype ID | |
//Serializer | |
var Serializer = module.exports = function (treeAdapter, options) { | |
this.treeAdapter = treeAdapter || DefaultTreeAdapter; | |
this.options = Utils.mergeOptions(DEFAULT_OPTIONS, options); | |
}; | |
//API | |
Serializer.prototype.serialize = function (node) { | |
this.html = ''; | |
this._serializeChildNodes(node); | |
return this.html; | |
}; | |
//Internals | |
Serializer.prototype._serializeChildNodes = function (parentNode) { | |
var childNodes = this.treeAdapter.getChildNodes(parentNode); | |
if (childNodes) { | |
for (var i = 0, cnLength = childNodes.length; i < cnLength; i++) { | |
var currentNode = childNodes[i]; | |
if (this.treeAdapter.isElementNode(currentNode)) | |
this._serializeElement(currentNode); | |
else if (this.treeAdapter.isTextNode(currentNode)) | |
this._serializeTextNode(currentNode); | |
else if (this.treeAdapter.isCommentNode(currentNode)) | |
this._serializeCommentNode(currentNode); | |
else if (this.treeAdapter.isDocumentTypeNode(currentNode)) | |
this._serializeDocumentTypeNode(currentNode); | |
} | |
} | |
}; | |
Serializer.prototype._serializeElement = function (node) { | |
var tn = this.treeAdapter.getTagName(node), | |
ns = this.treeAdapter.getNamespaceURI(node); | |
this.html += '<' + tn; | |
this._serializeAttributes(node); | |
this.html += '>'; | |
if (tn !== $.AREA && tn !== $.BASE && tn !== $.BASEFONT && tn !== $.BGSOUND && tn !== $.BR && tn !== $.BR && | |
tn !== $.COL && tn !== $.EMBED && tn !== $.FRAME && tn !== $.HR && tn !== $.IMG && tn !== $.INPUT && | |
tn !== $.KEYGEN && tn !== $.LINK && tn !== $.MENUITEM && tn !== $.META && tn !== $.PARAM && tn !== $.SOURCE && | |
tn !== $.TRACK && tn !== $.WBR) { | |
if (tn === $.PRE || tn === $.TEXTAREA || tn === $.LISTING) { | |
var firstChild = this.treeAdapter.getFirstChild(node); | |
if (firstChild && this.treeAdapter.isTextNode(firstChild)) { | |
var content = this.treeAdapter.getTextNodeContent(firstChild); | |
if (content[0] === '\n') | |
this.html += '\n'; | |
} | |
} | |
var childNodesHolder = tn === $.TEMPLATE && ns === NS.HTML ? | |
this.treeAdapter.getChildNodes(node)[0] : | |
node; | |
this._serializeChildNodes(childNodesHolder); | |
this.html += '</' + tn + '>'; | |
} | |
}; | |
Serializer.prototype._serializeAttributes = function (node) { | |
var attrs = this.treeAdapter.getAttrList(node); | |
for (var i = 0, attrsLength = attrs.length; i < attrsLength; i++) { | |
var attr = attrs[i], | |
value = this.options.encodeHtmlEntities ? escapeString(attr.value, true) : attr.value; | |
this.html += ' '; | |
if (!attr.namespace) | |
this.html += attr.name; | |
else if (attr.namespace === NS.XML) | |
this.html += 'xml:' + attr.name; | |
else if (attr.namespace === NS.XMLNS) { | |
if (attr.name !== 'xmlns') | |
this.html += 'xmlns:'; | |
this.html += attr.name; | |
} | |
else if (attr.namespace === NS.XLINK) | |
this.html += 'xlink:' + attr.name; | |
else | |
this.html += attr.namespace + ':' + attr.name; | |
this.html += '="' + value + '"'; | |
} | |
}; | |
Serializer.prototype._serializeTextNode = function (node) { | |
var content = this.treeAdapter.getTextNodeContent(node), | |
parent = this.treeAdapter.getParentNode(node), | |
parentTn = void 0; | |
if (parent && this.treeAdapter.isElementNode(parent)) | |
parentTn = this.treeAdapter.getTagName(parent); | |
if (parentTn === $.STYLE || parentTn === $.SCRIPT || parentTn === $.XMP || parentTn === $.IFRAME || | |
parentTn === $.NOEMBED || parentTn === $.NOFRAMES || parentTn === $.PLAINTEXT || parentTn === $.NOSCRIPT) { | |
this.html += content; | |
} | |
else | |
this.html += this.options.encodeHtmlEntities ? escapeString(content, false) : content; | |
}; | |
Serializer.prototype._serializeCommentNode = function (node) { | |
this.html += '<!--' + this.treeAdapter.getCommentNodeContent(node) + '-->'; | |
}; | |
Serializer.prototype._serializeDocumentTypeNode = function (node) { | |
var name = this.treeAdapter.getDocumentTypeNodeName(node), | |
publicId = this.treeAdapter.getDocumentTypeNodePublicId(node), | |
systemId = this.treeAdapter.getDocumentTypeNodeSystemId(node); | |
this.html += '<' + Doctype.serializeContent(name, publicId, systemId) + '>'; | |
}; |