|  | 
 | /** | 
 |  * Module dependencies. | 
 |  */ | 
 |  | 
 | var extend = require('extend'); | 
 | var encode = require('ent/encode'); | 
 | var CustomEvent = require('custom-event'); | 
 | var voidElements = require('void-elements'); | 
 |  | 
 | /** | 
 |  * Module exports. | 
 |  */ | 
 |  | 
 | exports = module.exports = serialize; | 
 | exports.serializeElement = serializeElement; | 
 | exports.serializeAttribute = serializeAttribute; | 
 | exports.serializeText = serializeText; | 
 | exports.serializeComment = serializeComment; | 
 | exports.serializeDocument = serializeDocument; | 
 | exports.serializeDoctype = serializeDoctype; | 
 | exports.serializeDocumentFragment = serializeDocumentFragment; | 
 | exports.serializeNodeList = serializeNodeList; | 
 |  | 
 | /** | 
 |  * Serializes any DOM node. Returns a string. | 
 |  * | 
 |  * @param {Node} node - DOM Node to serialize | 
 |  * @param {String} [context] - optional arbitrary "context" string to use (useful for event listeners) | 
 |  * @param {Function} [fn] - optional callback function to use in the "serialize" event for this call | 
 |  * @param {EventTarget} [eventTarget] - optional EventTarget instance to emit the "serialize" event on (defaults to `node`) | 
 |  * return {String} | 
 |  * @public | 
 |  */ | 
 |  | 
 | function serialize (node, context, fn, eventTarget) { | 
 |   if (!node) return ''; | 
 |   if ('function' === typeof context) { | 
 |     fn = context; | 
 |     context = null; | 
 |   } | 
 |   if (!context) context = null; | 
 |  | 
 |   var rtn; | 
 |   var nodeType = node.nodeType; | 
 |  | 
 |   if (!nodeType && 'number' === typeof node.length) { | 
 |     // assume it's a NodeList or Array of Nodes | 
 |     rtn = exports.serializeNodeList(node, context, fn); | 
 |   } else { | 
 |  | 
 |     if ('function' === typeof fn) { | 
 |       // one-time "serialize" event listener | 
 |       node.addEventListener('serialize', fn, false); | 
 |     } | 
 |  | 
 |     // emit a custom "serialize" event on `node`, in case there | 
 |     // are event listeners for custom serialization of this node | 
 |     var e = new CustomEvent('serialize', { | 
 |       bubbles: true, | 
 |       cancelable: true, | 
 |       detail: { | 
 |         serialize: null, | 
 |         context: context | 
 |       } | 
 |     }); | 
 |  | 
 |     e.serializeTarget = node; | 
 |  | 
 |     var target = eventTarget || node; | 
 |     var cancelled = !target.dispatchEvent(e); | 
 |  | 
 |     // `e.detail.serialize` can be set to a: | 
 |     //   String - returned directly | 
 |     //   Node   - goes through serializer logic instead of `node` | 
 |     //   Anything else - get Stringified first, and then returned directly | 
 |     var s = e.detail.serialize; | 
 |     if (s != null) { | 
 |       if ('string' === typeof s) { | 
 |         rtn = s; | 
 |       } else if ('number' === typeof s.nodeType) { | 
 |         // make it go through the serialization logic | 
 |         rtn = serialize(s, context, null, target); | 
 |       } else { | 
 |         rtn = String(s); | 
 |       } | 
 |     } else if (!cancelled) { | 
 |       // default serialization logic | 
 |       switch (nodeType) { | 
 |         case 1 /* element */: | 
 |           rtn = exports.serializeElement(node, context, eventTarget); | 
 |           break; | 
 |         case 2 /* attribute */: | 
 |           rtn = exports.serializeAttribute(node); | 
 |           break; | 
 |         case 3 /* text */: | 
 |           rtn = exports.serializeText(node); | 
 |           break; | 
 |         case 8 /* comment */: | 
 |           rtn = exports.serializeComment(node); | 
 |           break; | 
 |         case 9 /* document */: | 
 |           rtn = exports.serializeDocument(node, context, eventTarget); | 
 |           break; | 
 |         case 10 /* doctype */: | 
 |           rtn = exports.serializeDoctype(node); | 
 |           break; | 
 |         case 11 /* document fragment */: | 
 |           rtn = exports.serializeDocumentFragment(node, context, eventTarget); | 
 |           break; | 
 |       } | 
 |     } | 
 |  | 
 |     if ('function' === typeof fn) { | 
 |       node.removeEventListener('serialize', fn, false); | 
 |     } | 
 |   } | 
 |  | 
 |   return rtn || ''; | 
 | } | 
 |  | 
 | /** | 
 |  * Serialize an Attribute node. | 
 |  */ | 
 |  | 
 | function serializeAttribute (node, opts) { | 
 |   return node.name + '="' + encode(node.value, extend({ | 
 |     named: true | 
 |   }, opts)) + '"'; | 
 | } | 
 |  | 
 | /** | 
 |  * Serialize a DOM element. | 
 |  */ | 
 |  | 
 | function serializeElement (node, context, eventTarget) { | 
 |   var c, i, l; | 
 |   var name = node.nodeName.toLowerCase(); | 
 |  | 
 |   // opening tag | 
 |   var r = '<' + name; | 
 |  | 
 |   // attributes | 
 |   for (i = 0, c = node.attributes, l = c.length; i < l; i++) { | 
 |     r += ' ' + exports.serializeAttribute(c[i]); | 
 |   } | 
 |  | 
 |   r += '>'; | 
 |  | 
 |   // child nodes | 
 |   r += exports.serializeNodeList(node.childNodes, context, null, eventTarget); | 
 |  | 
 |   // closing tag, only for non-void elements | 
 |   if (!voidElements[name]) { | 
 |     r += '</' + name + '>'; | 
 |   } | 
 |  | 
 |   return r; | 
 | } | 
 |  | 
 | /** | 
 |  * Serialize a text node. | 
 |  */ | 
 |  | 
 | function serializeText (node, opts) { | 
 |   return encode(node.nodeValue, extend({ | 
 |     named: true, | 
 |     special: { '<': true, '>': true, '&': true } | 
 |   }, opts)); | 
 | } | 
 |  | 
 | /** | 
 |  * Serialize a comment node. | 
 |  */ | 
 |  | 
 | function serializeComment (node) { | 
 |   return '<!--' + node.nodeValue + '-->'; | 
 | } | 
 |  | 
 | /** | 
 |  * Serialize a Document node. | 
 |  */ | 
 |  | 
 | function serializeDocument (node, context, eventTarget) { | 
 |   return exports.serializeNodeList(node.childNodes, context, null, eventTarget); | 
 | } | 
 |  | 
 | /** | 
 |  * Serialize a DOCTYPE node. | 
 |  * See: http://stackoverflow.com/a/10162353 | 
 |  */ | 
 |  | 
 | function serializeDoctype (node) { | 
 |   var r = '<!DOCTYPE ' + node.name; | 
 |  | 
 |   if (node.publicId) { | 
 |     r += ' PUBLIC "' + node.publicId + '"'; | 
 |   } | 
 |  | 
 |   if (!node.publicId && node.systemId) { | 
 |     r += ' SYSTEM'; | 
 |   } | 
 |  | 
 |   if (node.systemId) { | 
 |     r += ' "' + node.systemId + '"'; | 
 |   } | 
 |  | 
 |   r += '>'; | 
 |   return r; | 
 | } | 
 |  | 
 | /** | 
 |  * Serialize a DocumentFragment instance. | 
 |  */ | 
 |  | 
 | function serializeDocumentFragment (node, context, eventTarget) { | 
 |   return exports.serializeNodeList(node.childNodes, context, null, eventTarget); | 
 | } | 
 |  | 
 | /** | 
 |  * Serialize a NodeList/Array of nodes. | 
 |  */ | 
 |  | 
 | function serializeNodeList (list, context, fn, eventTarget) { | 
 |   var r = ''; | 
 |   for (var i = 0, l = list.length; i < l; i++) { | 
 |     r += serialize(list[i], context, fn, eventTarget); | 
 |   } | 
 |   return r; | 
 | } |