1 |
|
---|
2 | /**
|
---|
3 | * Module dependencies.
|
---|
4 | */
|
---|
5 |
|
---|
6 | var extend = require('extend');
|
---|
7 | var encode = require('ent/encode');
|
---|
8 | var CustomEvent = require('custom-event');
|
---|
9 | var voidElements = require('void-elements');
|
---|
10 |
|
---|
11 | /**
|
---|
12 | * Module exports.
|
---|
13 | */
|
---|
14 |
|
---|
15 | exports = module.exports = serialize;
|
---|
16 | exports.serializeElement = serializeElement;
|
---|
17 | exports.serializeAttribute = serializeAttribute;
|
---|
18 | exports.serializeText = serializeText;
|
---|
19 | exports.serializeComment = serializeComment;
|
---|
20 | exports.serializeDocument = serializeDocument;
|
---|
21 | exports.serializeDoctype = serializeDoctype;
|
---|
22 | exports.serializeDocumentFragment = serializeDocumentFragment;
|
---|
23 | exports.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 |
|
---|
36 | function 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 |
|
---|
126 | function 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 |
|
---|
136 | function 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 |
|
---|
165 | function 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 |
|
---|
176 | function serializeComment (node) {
|
---|
177 | return '<!--' + node.nodeValue + '-->';
|
---|
178 | }
|
---|
179 |
|
---|
180 | /**
|
---|
181 | * Serialize a Document node.
|
---|
182 | */
|
---|
183 |
|
---|
184 | function 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 |
|
---|
193 | function 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 |
|
---|
216 | function 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 |
|
---|
224 | function 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 | }
|
---|