| '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), |
| qualifiedTn = (ns === NS.HTML || ns === NS.SVG || ns === NS.MATHML) ? tn : (ns + ':' + tn); |
| |
| this.html += '<' + qualifiedTn; |
| 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 += '</' + qualifiedTn + '>'; |
| } |
| }; |
| |
| 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) + '>'; |
| }; |