serializer.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. /*
  2. * Copyright 2018 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * SingleFile is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * SingleFile is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License
  18. * along with SingleFile. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. /* global Node */
  21. this.serializer = this.serializer || (() => {
  22. return {
  23. process(doc, compressHTML) {
  24. return getDoctype(doc) + (compressHTML ? serialize(doc.documentElement) : doc.documentElement.outerHTML);
  25. }
  26. };
  27. function getDoctype(doc) {
  28. const docType = doc.doctype;
  29. let docTypeString;
  30. if (docType) {
  31. docTypeString = "<!DOCTYPE " + docType.nodeName;
  32. if (docType.publicId) {
  33. docTypeString += " PUBLIC \"" + docType.publicId + "\"";
  34. if (docType.systemId)
  35. docTypeString += " \"" + docType.systemId + "\"";
  36. } else if (docType.systemId)
  37. docTypeString += " SYSTEM \"" + docType.systemId + "\"";
  38. if (docType.internalSubset)
  39. docTypeString += " [" + docType.internalSubset + "]";
  40. return docTypeString + ">\n";
  41. }
  42. return "";
  43. }
  44. function serialize(node) {
  45. let content = "";
  46. if (node.nodeType == Node.TEXT_NODE) {
  47. content += serializeTextNode(node);
  48. } else if (node.nodeType == Node.COMMENT_NODE) {
  49. content += serializeCommentNode(node);
  50. } else if (node.nodeType == Node.ELEMENT_NODE) {
  51. content += serializeElement(node);
  52. }
  53. return content;
  54. }
  55. function serializeTextNode(textNode) {
  56. return textNode.textContent;
  57. }
  58. function serializeCommentNode(commentNode) {
  59. return "<!--" + commentNode.textContent + "-->";
  60. }
  61. function serializeElement(element) {
  62. const tagName = element.tagName.toLowerCase();
  63. let content = "<" + tagName;
  64. Array.from(element.attributes).forEach(attribute => {
  65. let value = attribute.value;
  66. if (attribute.name == "class") {
  67. value = element.classList.toString().trim();
  68. }
  69. value = value.replace(/&/g, "&amp;").replace(/\u00a0/g, "&nbsp;").replace(/"/g, "&quot;");
  70. const validUnquotedValue = value.match(/^[^ \t\n\f\r"'`=<>]+$/);
  71. content += " "; //+ attribute.name + "=";
  72. if (!attribute.namespace) {
  73. content += attribute.name;
  74. } else if (attribute.namespaceURI == "http://www.w3.org/XML/1998/namespace") {
  75. content += "xml:" + attribute.name;
  76. } else if (attribute.namespaceURI == "http://www.w3.org/2000/xmlns/") {
  77. if (attribute.name !== "xmlns") {
  78. content += "xmlns:";
  79. }
  80. content += attribute.name;
  81. } else if (attribute.namespaceURI == "http://www.w3.org/1999/xlink") {
  82. content += "xlink:" + attribute.name;
  83. } else {
  84. content += attribute.name;
  85. }
  86. content += "=";
  87. if (!validUnquotedValue) {
  88. content += "\"";
  89. }
  90. content += value;
  91. if (!validUnquotedValue) {
  92. content += "\"";
  93. }
  94. });
  95. content += ">";
  96. Array.from(element.childNodes).forEach(childNode => {
  97. content += serialize(childNode);
  98. });
  99. if (!["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"].includes(tagName)) {
  100. content += "</" + tagName + ">";
  101. }
  102. return content;
  103. }
  104. })();