import { XMLParser } from "fast-xml-parser"; export const xmlParser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_", textNodeName: "#text", cdataPropName: "__cdata", trimValues: true, parseTagValue: false, parseAttributeValue: false, processEntities: true, htmlEntities: true, removeNSPrefix: false, }); export function parseXml(text: string): Record { // Strip BOM and leading junk some feeds emit before the prolog. const cleaned = text.replace(/^/, "").replace(/^[^<]+/, ""); return xmlParser.parse(cleaned) as Record; } export function asArray(v: T | T[] | undefined | null): T[] { if (v === undefined || v === null) return []; return Array.isArray(v) ? v : [v]; } /** Text of an XML node that may be string, cdata object or {#text}. */ export function textOf(v: unknown): string { if (v === undefined || v === null) return ""; if (typeof v === "string") return v.trim(); if (typeof v === "number" || typeof v === "boolean") return String(v); if (typeof v === "object") { const o = v as Record; if (typeof o.__cdata === "string") return o.__cdata.trim(); if (typeof o["#text"] === "string") return (o["#text"] as string).trim(); if (o.__cdata && typeof o.__cdata === "object") return textOf(o.__cdata); if (typeof o["@_href"] === "string") return o["@_href"].trim(); } return ""; } export function stripHtml(s: string): string { return s .replace(//gi, " ") .replace(//gi, " ") .replace(/<[^>]+>/g, " ") .replace(/ /g, " ") .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, '"') .replace(/'|'/g, "'") .replace(/\s+/g, " ") .trim(); } export function parseDate(v: unknown): Date | null { const s = textOf(v); if (!s) return null; const d = new Date(s); if (!Number.isNaN(d.getTime())) return d; // RFC 822 variants with odd zones like "PST"/"EDT" are handled by Date; try trimming const d2 = new Date(s.replace(/\s+[A-Z]{3,4}$/, "")); return Number.isNaN(d2.getTime()) ? null : d2; }