index.js 5.5 KB

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