[6a3a178] | 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 | }
|
---|