1'use strict'; 2 3var DefaultTreeAdapter = require('../tree_adapters/default'), 4 Doctype = require('../common/doctype'), 5 Utils = require('../common/utils'), 6 HTML = require('../common/html'); 7 8//Aliases 9var $ = HTML.TAG_NAMES, 10 NS = HTML.NAMESPACES; 11 12//Default serializer options 13var DEFAULT_OPTIONS = { 14 encodeHtmlEntities: true 15}; 16 17//Escaping regexes 18var AMP_REGEX = /&/g, 19 NBSP_REGEX = /\u00a0/g, 20 DOUBLE_QUOTE_REGEX = /"/g, 21 LT_REGEX = /</g, 22 GT_REGEX = />/g; 23 24//Escape string 25function escapeString(str, attrMode) { 26 str = str 27 .replace(AMP_REGEX, '&') 28 .replace(NBSP_REGEX, ' '); 29 30 if (attrMode) 31 str = str.replace(DOUBLE_QUOTE_REGEX, '"'); 32 33 else { 34 str = str 35 .replace(LT_REGEX, '<') 36 .replace(GT_REGEX, '>'); 37 } 38 39 return str; 40} 41 42 43//Enquote doctype ID 44 45 46 47//Serializer 48var Serializer = module.exports = function (treeAdapter, options) { 49 this.treeAdapter = treeAdapter || DefaultTreeAdapter; 50 this.options = Utils.mergeOptions(DEFAULT_OPTIONS, options); 51}; 52 53 54//API 55Serializer.prototype.serialize = function (node) { 56 this.html = ''; 57 this._serializeChildNodes(node); 58 59 return this.html; 60}; 61 62 63//Internals 64Serializer.prototype._serializeChildNodes = function (parentNode) { 65 var childNodes = this.treeAdapter.getChildNodes(parentNode); 66 67 if (childNodes) { 68 for (var i = 0, cnLength = childNodes.length; i < cnLength; i++) { 69 var currentNode = childNodes[i]; 70 71 if (this.treeAdapter.isElementNode(currentNode)) 72 this._serializeElement(currentNode); 73 74 else if (this.treeAdapter.isTextNode(currentNode)) 75 this._serializeTextNode(currentNode); 76 77 else if (this.treeAdapter.isCommentNode(currentNode)) 78 this._serializeCommentNode(currentNode); 79 80 else if (this.treeAdapter.isDocumentTypeNode(currentNode)) 81 this._serializeDocumentTypeNode(currentNode); 82 } 83 } 84}; 85 86Serializer.prototype._serializeElement = function (node) { 87 var tn = this.treeAdapter.getTagName(node), 88 ns = this.treeAdapter.getNamespaceURI(node), 89 qualifiedTn = (ns === NS.HTML || ns === NS.SVG || ns === NS.MATHML) ? tn : (ns + ':' + tn); 90 91 this.html += '<' + qualifiedTn; 92 this._serializeAttributes(node); 93 this.html += '>'; 94 95 if (tn !== $.AREA && tn !== $.BASE && tn !== $.BASEFONT && tn !== $.BGSOUND && tn !== $.BR && tn !== $.BR && 96 tn !== $.COL && tn !== $.EMBED && tn !== $.FRAME && tn !== $.HR && tn !== $.IMG && tn !== $.INPUT && 97 tn !== $.KEYGEN && tn !== $.LINK && tn !== $.MENUITEM && tn !== $.META && tn !== $.PARAM && tn !== $.SOURCE && 98 tn !== $.TRACK && tn !== $.WBR) { 99 100 if (tn === $.PRE || tn === $.TEXTAREA || tn === $.LISTING) { 101 var firstChild = this.treeAdapter.getFirstChild(node); 102 103 if (firstChild && this.treeAdapter.isTextNode(firstChild)) { 104 var content = this.treeAdapter.getTextNodeContent(firstChild); 105 106 if (content[0] === '\n') 107 this.html += '\n'; 108 } 109 } 110 111 var childNodesHolder = tn === $.TEMPLATE && ns === NS.HTML ? 112 this.treeAdapter.getChildNodes(node)[0] : 113 node; 114 115 this._serializeChildNodes(childNodesHolder); 116 this.html += '</' + qualifiedTn + '>'; 117 } 118}; 119 120Serializer.prototype._serializeAttributes = function (node) { 121 var attrs = this.treeAdapter.getAttrList(node); 122 123 for (var i = 0, attrsLength = attrs.length; i < attrsLength; i++) { 124 var attr = attrs[i], 125 value = this.options.encodeHtmlEntities ? escapeString(attr.value, true) : attr.value; 126 127 this.html += ' '; 128 129 if (!attr.namespace) 130 this.html += attr.name; 131 132 else if (attr.namespace === NS.XML) 133 this.html += 'xml:' + attr.name; 134 135 else if (attr.namespace === NS.XMLNS) { 136 if (attr.name !== 'xmlns') 137 this.html += 'xmlns:'; 138 139 this.html += attr.name; 140 } 141 142 else if (attr.namespace === NS.XLINK) 143 this.html += 'xlink:' + attr.name; 144 145 else 146 this.html += attr.namespace + ':' + attr.name; 147 148 this.html += '="' + value + '"'; 149 } 150}; 151 152Serializer.prototype._serializeTextNode = function (node) { 153 var content = this.treeAdapter.getTextNodeContent(node), 154 parent = this.treeAdapter.getParentNode(node), 155 parentTn = void 0; 156 157 if (parent && this.treeAdapter.isElementNode(parent)) 158 parentTn = this.treeAdapter.getTagName(parent); 159 160 if (parentTn === $.STYLE || parentTn === $.SCRIPT || parentTn === $.XMP || parentTn === $.IFRAME || 161 parentTn === $.NOEMBED || parentTn === $.NOFRAMES || parentTn === $.PLAINTEXT || parentTn === $.NOSCRIPT) { 162 this.html += content; 163 } 164 165 else 166 this.html += this.options.encodeHtmlEntities ? escapeString(content, false) : content; 167}; 168 169Serializer.prototype._serializeCommentNode = function (node) { 170 this.html += '<!--' + this.treeAdapter.getCommentNodeContent(node) + '-->'; 171}; 172 173Serializer.prototype._serializeDocumentTypeNode = function (node) { 174 var name = this.treeAdapter.getDocumentTypeNodeName(node), 175 publicId = this.treeAdapter.getDocumentTypeNodePublicId(node), 176 systemId = this.treeAdapter.getDocumentTypeNodeSystemId(node); 177 178 this.html += '<' + Doctype.serializeContent(name, publicId, systemId) + '>'; 179}; 180