shelving
Version:
Toolkit for using data in JavaScript.
55 lines (54 loc) • 2.03 kB
JavaScript
import { RequiredError } from "../error/RequiredError.js";
import { getDataProps, isData } from "./data.js";
import { requireString } from "./string.js";
import { isDefined } from "./undefined.js";
/**
* Escape a string for safe inclusion in XML text content or attribute values.
*
* @param value The raw string value.
* @returns The escaped XML-safe string.
*
* @example
* escapeXML(`Tom & "Jerry"`)
*/
export function escapeXML(value) {
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
}
/**
* Build an XML string from a data object.
*
* - Only data objects can be converted directly because XML requires named root and child elements.
* - `undefined` values are omitted from the output.
* - Nested data objects become nested XML elements.
*
* @param data The data object to serialize.
* @param caller The calling function for error context.
* @returns The serialized XML string.
*
* @throws {RequiredError} If a key is not a valid XML element name.
* @throws {RequiredError} If a value cannot be converted to XML.
*
* @example
* getXML({ user: { name: "Alice", age: 30 } })
*/
export function getXML(data, caller = getXML) {
return Array.from(_yieldXML(data, caller)).join("");
}
function* _yieldXML(data, caller) {
for (const [key, value] of getDataProps(data)) {
if (!R_XML_KEY.test(key))
throw new RequiredError("Invalid XML key", { received: key, caller });
if (isDefined(value))
yield `<${key}>${_getXMLValue(value, caller)}</${key}>`;
}
}
function _getXMLValue(value, caller) {
if (typeof value === "string")
return escapeXML(value);
if (typeof value === "number" || typeof value === "boolean")
return requireString(value, caller);
if (isData(value))
return getXML(value, caller);
throw new RequiredError("Value cannot be converted to XML", { received: value, caller });
}
const R_XML_KEY = /^[a-z][a-z0-9]*$/i;