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.
		
		
		
		
			
				
					129 lines
				
				3.6 KiB
			
		
		
			
		
	
	
					129 lines
				
				3.6 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								"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;
							 |