| |
| /** |
| * 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; |
| } |