• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, '&amp;')
28        .replace(NBSP_REGEX, '&nbsp;');
29
30    if (attrMode)
31        str = str.replace(DOUBLE_QUOTE_REGEX, '&quot;');
32
33    else {
34        str = str
35            .replace(LT_REGEX, '&lt;')
36            .replace(GT_REGEX, '&gt;');
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