/** * XML building and parsing utilities for the Spok SmartSuite TCP API. * Uses regex-based parsing to avoid external XML dependencies. */ import { SpokResponse } from "./types"; /** XML namespace for Amcom request elements. */ export const REQUEST_NS = "http://xml.amcomsoft.com/api/request"; /** * Escape special XML characters. */ export function escapeXml(str: string): string { return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } /** * Unescape XML entities. */ export function unescapeXml(str: string): string { return str .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, '"') .replace(/'/g, "'"); } /** * Build an Amcom XML request body. * @param method - Amcom API method name (e.g. "GetListingInfo"). * @param params - Optional parameter name-value pairs. * @returns XML request string. */ export function buildRequestXml(method: string, params?: Record): string { let xml = ``; if (params) { for (const [name, value] of Object.entries(params)) { if (value === null || value === undefined) { xml += ``; } else { xml += `${escapeXml(String(value))}`; } } } xml += ""; return xml; } /** * Extract parameter name-value pairs from an XML fragment. */ function extractParameters(xml: string): Record { const params: Record = {}; const re = /]*\bname="([^"]*)"[^>]*>([\s\S]*?)<\/parameter>/g; let m: RegExpExecArray | null; while ((m = re.exec(xml)) !== null) { params[m[1]] = m[2].trim(); } return params; } /** * Extract top-level child elements from an XML fragment using depth-aware scanning. * Handles same-named nested elements (e.g. X). */ function extractChildren(xml: string): { tag: string; xml: string }[] { const children: { tag: string; xml: string }[] = []; let pos = 0; while (pos < xml.length) { // Find next opening tag const openIdx = xml.indexOf("<", pos); if (openIdx < 0) break; // Skip comments, CDATA, processing instructions if (xml.startsWith("