You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
3.6 KiB
128 lines
3.6 KiB
"use strict"; |
|
|
|
const xnv = require("xml-name-validator"); |
|
|
|
const { NAMESPACES } = require("./constants"); |
|
|
|
function generatePrefix(map, newNamespace, prefixIndex) { |
|
const generatedPrefix = "ns" + prefixIndex; |
|
map[newNamespace] = [generatedPrefix]; |
|
return generatedPrefix; |
|
} |
|
|
|
function preferredPrefixString(map, ns, preferredPrefix) { |
|
const candidateList = map[ns]; |
|
if (!candidateList) { |
|
return null; |
|
} |
|
if (candidateList.includes(preferredPrefix)) { |
|
return preferredPrefix; |
|
} |
|
return candidateList[candidateList.length - 1]; |
|
} |
|
|
|
function serializeAttributeValue(value/* , requireWellFormed*/) { |
|
if (value === null) { |
|
return ""; |
|
} |
|
// TODO: Check well-formedness |
|
return value |
|
.replace(/&/g, "&") |
|
.replace(/"/g, """) |
|
.replace(/</g, "<") |
|
.replace(/>/g, ">") |
|
.replace(/\t/g, "	") |
|
.replace(/\n/g, "
") |
|
.replace(/\r/g, "
"); |
|
} |
|
|
|
function serializeAttributes( |
|
element, |
|
map, |
|
localPrefixes, |
|
ignoreNamespaceDefAttr, |
|
requireWellFormed, |
|
refs |
|
) { |
|
let result = ""; |
|
const namespaceLocalnames = Object.create(null); |
|
for (const attr of element.attributes) { |
|
if ( |
|
requireWellFormed && |
|
namespaceLocalnames[attr.namespaceURI] && |
|
namespaceLocalnames[attr.namespaceURI].has(attr.localName) |
|
) { |
|
throw new Error("Found duplicated attribute"); |
|
} |
|
if (!namespaceLocalnames[attr.namespaceURI]) { |
|
namespaceLocalnames[attr.namespaceURI] = new Set(); |
|
} |
|
namespaceLocalnames[attr.namespaceURI].add(attr.localName); |
|
const attributeNamespace = attr.namespaceURI; |
|
let candidatePrefix = null; |
|
if (attributeNamespace !== null) { |
|
candidatePrefix = preferredPrefixString( |
|
map, |
|
attributeNamespace, |
|
attr.prefix |
|
); |
|
if (attributeNamespace === NAMESPACES.XMLNS) { |
|
if ( |
|
attr.value === NAMESPACES.XML || |
|
(attr.prefix === null && ignoreNamespaceDefAttr) || |
|
(attr.prefix !== null && |
|
localPrefixes[attr.localName] !== attr.value && |
|
map[attr.value].includes(attr.localName)) |
|
) { |
|
continue; |
|
} |
|
if (requireWellFormed && attr.value === NAMESPACES.XMLNS) { |
|
throw new Error( |
|
"The XMLNS namespace is reserved and cannot be applied as an element's namespace via XML parsing" |
|
); |
|
} |
|
if (requireWellFormed && attr.value === "") { |
|
throw new Error( |
|
"Namespace prefix declarations cannot be used to undeclare a namespace" |
|
); |
|
} |
|
if (attr.prefix === "xmlns") { |
|
candidatePrefix = "xmlns"; |
|
} |
|
} else if (candidatePrefix === null) { |
|
candidatePrefix = generatePrefix( |
|
map, |
|
attributeNamespace, |
|
refs.prefixIndex++ |
|
); |
|
result += ` xmlns:${candidatePrefix}="${serializeAttributeValue( |
|
attributeNamespace, |
|
requireWellFormed |
|
)}"`; |
|
} |
|
} |
|
|
|
result += " "; |
|
if (candidatePrefix !== null) { |
|
result += candidatePrefix + ":"; |
|
} |
|
if ( |
|
requireWellFormed && |
|
(attr.localName.includes(":") || |
|
!xnv.name(attr.localName) || |
|
(attr.localName === "xmlns" && attributeNamespace === null)) |
|
) { |
|
throw new Error("Invalid attribute localName value"); |
|
} |
|
result += `${attr.localName}="${serializeAttributeValue( |
|
attr.value, |
|
requireWellFormed |
|
)}"`; |
|
} |
|
return result; |
|
} |
|
|
|
module.exports.preferredPrefixString = preferredPrefixString; |
|
module.exports.generatePrefix = generatePrefix; |
|
module.exports.serializeAttributeValue = serializeAttributeValue; |
|
module.exports.serializeAttributes = serializeAttributes;
|
|
|