attributes.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. "use strict";
  2. const xnv = require("xml-name-validator");
  3. const { NAMESPACES } = require("./constants");
  4. function generatePrefix(map, newNamespace, prefixIndex) {
  5. const generatedPrefix = "ns" + prefixIndex;
  6. map[newNamespace] = [generatedPrefix];
  7. return generatedPrefix;
  8. }
  9. function preferredPrefixString(map, ns, preferredPrefix) {
  10. const candidateList = map[ns];
  11. if (!candidateList) {
  12. return null;
  13. }
  14. if (candidateList.includes(preferredPrefix)) {
  15. return preferredPrefix;
  16. }
  17. return candidateList[candidateList.length - 1];
  18. }
  19. function serializeAttributeValue(value/* , requireWellFormed*/) {
  20. if (value === null) {
  21. return "";
  22. }
  23. // TODO: Check well-formedness
  24. return value
  25. .replace(/&/g, "&")
  26. .replace(/"/g, """)
  27. .replace(/</g, "&lt;")
  28. .replace(/>/g, "&gt;")
  29. .replace(/\t/g, "&#x9;")
  30. .replace(/\n/g, "&#xA;")
  31. .replace(/\r/g, "&#xD;");
  32. }
  33. function serializeAttributes(
  34. element,
  35. map,
  36. localPrefixes,
  37. ignoreNamespaceDefAttr,
  38. requireWellFormed,
  39. refs
  40. ) {
  41. let result = "";
  42. const namespaceLocalnames = Object.create(null);
  43. for (const attr of element.attributes) {
  44. if (
  45. requireWellFormed &&
  46. namespaceLocalnames[attr.namespaceURI] &&
  47. namespaceLocalnames[attr.namespaceURI].has(attr.localName)
  48. ) {
  49. throw new Error("Found duplicated attribute");
  50. }
  51. if (!namespaceLocalnames[attr.namespaceURI]) {
  52. namespaceLocalnames[attr.namespaceURI] = new Set();
  53. }
  54. namespaceLocalnames[attr.namespaceURI].add(attr.localName);
  55. const attributeNamespace = attr.namespaceURI;
  56. let candidatePrefix = null;
  57. if (attributeNamespace !== null) {
  58. candidatePrefix = preferredPrefixString(
  59. map,
  60. attributeNamespace,
  61. attr.prefix
  62. );
  63. if (attributeNamespace === NAMESPACES.XMLNS) {
  64. if (
  65. attr.value === NAMESPACES.XML ||
  66. (attr.prefix === null && ignoreNamespaceDefAttr) ||
  67. (attr.prefix !== null &&
  68. localPrefixes[attr.localName] !== attr.value &&
  69. map[attr.value].includes(attr.localName))
  70. ) {
  71. continue;
  72. }
  73. if (requireWellFormed && attr.value === NAMESPACES.XMLNS) {
  74. throw new Error(
  75. "The XMLNS namespace is reserved and cannot be applied as an element's namespace via XML parsing"
  76. );
  77. }
  78. if (requireWellFormed && attr.value === "") {
  79. throw new Error(
  80. "Namespace prefix declarations cannot be used to undeclare a namespace"
  81. );
  82. }
  83. if (attr.prefix === "xmlns") {
  84. candidatePrefix = "xmlns";
  85. }
  86. } else if (candidatePrefix === null) {
  87. candidatePrefix = generatePrefix(
  88. map,
  89. attributeNamespace,
  90. refs.prefixIndex++
  91. );
  92. result += ` xmlns:${candidatePrefix}="${serializeAttributeValue(
  93. attributeNamespace,
  94. requireWellFormed
  95. )}"`;
  96. }
  97. }
  98. result += " ";
  99. if (candidatePrefix !== null) {
  100. result += candidatePrefix + ":";
  101. }
  102. if (
  103. requireWellFormed &&
  104. (attr.localName.includes(":") ||
  105. !xnv.name(attr.localName) ||
  106. (attr.localName === "xmlns" && attributeNamespace === null))
  107. ) {
  108. throw new Error("Invalid attribute localName value");
  109. }
  110. result += `${attr.localName}="${serializeAttributeValue(
  111. attr.value,
  112. requireWellFormed
  113. )}"`;
  114. }
  115. return result;
  116. }
  117. module.exports.preferredPrefixString = preferredPrefixString;
  118. module.exports.generatePrefix = generatePrefix;
  119. module.exports.serializeAttributeValue = serializeAttributeValue;
  120. module.exports.serializeAttributes = serializeAttributes;