UNPKG

5.64 kBJavaScriptView Raw
1
2/**
3 * Module dependencies.
4 */
5
6var extend = require('extend');
7var encode = require('ent/encode');
8var CustomEvent = require('custom-event');
9var voidElements = require('void-elements');
10
11/**
12 * Module exports.
13 */
14
15exports = module.exports = serialize;
16exports.serializeElement = serializeElement;
17exports.serializeAttribute = serializeAttribute;
18exports.serializeText = serializeText;
19exports.serializeComment = serializeComment;
20exports.serializeDocument = serializeDocument;
21exports.serializeDoctype = serializeDoctype;
22exports.serializeDocumentFragment = serializeDocumentFragment;
23exports.serializeNodeList = serializeNodeList;
24
25/**
26 * Serializes any DOM node. Returns a string.
27 *
28 * @param {Node} node - DOM Node to serialize
29 * @param {String} [context] - optional arbitrary "context" string to use (useful for event listeners)
30 * @param {Function} [fn] - optional callback function to use in the "serialize" event for this call
31 * @param {EventTarget} [eventTarget] - optional EventTarget instance to emit the "serialize" event on (defaults to `node`)
32 * return {String}
33 * @public
34 */
35
36function serialize (node, context, fn, eventTarget) {
37 if (!node) return '';
38 if ('function' === typeof context) {
39 fn = context;
40 context = null;
41 }
42 if (!context) context = null;
43
44 var rtn;
45 var nodeType = node.nodeType;
46
47 if (!nodeType && 'number' === typeof node.length) {
48 // assume it's a NodeList or Array of Nodes
49 rtn = exports.serializeNodeList(node, context, fn);
50 } else {
51
52 if ('function' === typeof fn) {
53 // one-time "serialize" event listener
54 node.addEventListener('serialize', fn, false);
55 }
56
57 // emit a custom "serialize" event on `node`, in case there
58 // are event listeners for custom serialization of this node
59 var e = new CustomEvent('serialize', {
60 bubbles: true,
61 cancelable: true,
62 detail: {
63 serialize: null,
64 context: context
65 }
66 });
67
68 e.serializeTarget = node;
69
70 var target = eventTarget || node;
71 var cancelled = !target.dispatchEvent(e);
72
73 // `e.detail.serialize` can be set to a:
74 // String - returned directly
75 // Node - goes through serializer logic instead of `node`
76 // Anything else - get Stringified first, and then returned directly
77 var s = e.detail.serialize;
78 if (s != null) {
79 if ('string' === typeof s) {
80 rtn = s;
81 } else if ('number' === typeof s.nodeType) {
82 // make it go through the serialization logic
83 rtn = serialize(s, context, null, target);
84 } else {
85 rtn = String(s);
86 }
87 } else if (!cancelled) {
88 // default serialization logic
89 switch (nodeType) {
90 case 1 /* element */:
91 rtn = exports.serializeElement(node, context, eventTarget);
92 break;
93 case 2 /* attribute */:
94 rtn = exports.serializeAttribute(node);
95 break;
96 case 3 /* text */:
97 rtn = exports.serializeText(node);
98 break;
99 case 8 /* comment */:
100 rtn = exports.serializeComment(node);
101 break;
102 case 9 /* document */:
103 rtn = exports.serializeDocument(node, context, eventTarget);
104 break;
105 case 10 /* doctype */:
106 rtn = exports.serializeDoctype(node);
107 break;
108 case 11 /* document fragment */:
109 rtn = exports.serializeDocumentFragment(node, context, eventTarget);
110 break;
111 }
112 }
113
114 if ('function' === typeof fn) {
115 node.removeEventListener('serialize', fn, false);
116 }
117 }
118
119 return rtn || '';
120}
121
122/**
123 * Serialize an Attribute node.
124 */
125
126function serializeAttribute (node, opts) {
127 return node.name + '="' + encode(node.value, extend({
128 named: true
129 }, opts)) + '"';
130}
131
132/**
133 * Serialize a DOM element.
134 */
135
136function serializeElement (node, context, eventTarget) {
137 var c, i, l;
138 var name = node.nodeName.toLowerCase();
139
140 // opening tag
141 var r = '<' + name;
142
143 // attributes
144 for (i = 0, c = node.attributes, l = c.length; i < l; i++) {
145 r += ' ' + exports.serializeAttribute(c[i]);
146 }
147
148 r += '>';
149
150 // child nodes
151 r += exports.serializeNodeList(node.childNodes, context, null, eventTarget);
152
153 // closing tag, only for non-void elements
154 if (!voidElements[name]) {
155 r += '</' + name + '>';
156 }
157
158 return r;
159}
160
161/**
162 * Serialize a text node.
163 */
164
165function serializeText (node, opts) {
166 return encode(node.nodeValue, extend({
167 named: true,
168 special: { '<': true, '>': true, '&': true }
169 }, opts));
170}
171
172/**
173 * Serialize a comment node.
174 */
175
176function serializeComment (node) {
177 return '<!--' + node.nodeValue + '-->';
178}
179
180/**
181 * Serialize a Document node.
182 */
183
184function serializeDocument (node, context, eventTarget) {
185 return exports.serializeNodeList(node.childNodes, context, null, eventTarget);
186}
187
188/**
189 * Serialize a DOCTYPE node.
190 * See: http://stackoverflow.com/a/10162353
191 */
192
193function serializeDoctype (node) {
194 var r = '<!DOCTYPE ' + node.name;
195
196 if (node.publicId) {
197 r += ' PUBLIC "' + node.publicId + '"';
198 }
199
200 if (!node.publicId && node.systemId) {
201 r += ' SYSTEM';
202 }
203
204 if (node.systemId) {
205 r += ' "' + node.systemId + '"';
206 }
207
208 r += '>';
209 return r;
210}
211
212/**
213 * Serialize a DocumentFragment instance.
214 */
215
216function serializeDocumentFragment (node, context, eventTarget) {
217 return exports.serializeNodeList(node.childNodes, context, null, eventTarget);
218}
219
220/**
221 * Serialize a NodeList/Array of nodes.
222 */
223
224function serializeNodeList (list, context, fn, eventTarget) {
225 var r = '';
226 for (var i = 0, l = list.length; i < l; i++) {
227 r += serialize(list[i], context, fn, eventTarget);
228 }
229 return r;
230}