// author: simon-pierre boucher export interface SitemapUrl { readonly loc: string; readonly lastModified?: string; } export interface ParsedSitemap { readonly urls: SitemapUrl[]; readonly childSitemaps: string[]; } function decodeXml(s: string): string { return s .replace(//g, "$1") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, '"') .replace(/'/g, "'") .replace(/'/g, "'") .replace(/&/g, "&") .trim(); } function tag(block: string, name: string): string | undefined { const m = new RegExp(`<${name}[^>]*>([\\s\\S]*?)`, "i").exec(block); return m?.[1] !== undefined ? decodeXml(m[1]) : undefined; } function blocks(xml: string, name: string): string[] { const out: string[] = []; const re = new RegExp(`<${name}[^>]*>([\\s\\S]*?)`, "gi"); let m: RegExpExecArray | null; while ((m = re.exec(xml)) !== null) { if (m[1] !== undefined) out.push(m[1]); } return out; } /** * Parse a sitemap or sitemap index (ยง14.3). Returns child sitemap URLs (for * recursion through indexes) and page URLs with optional lastmod. */ export function parseSitemap(xml: string): ParsedSitemap { const childSitemaps: string[] = []; for (const block of blocks(xml, "sitemap")) { const loc = tag(block, "loc"); if (loc !== undefined && loc !== "") childSitemaps.push(loc); } const urls: SitemapUrl[] = []; for (const block of blocks(xml, "url")) { const loc = tag(block, "loc"); if (loc === undefined || loc === "") continue; const lastmod = tag(block, "lastmod"); urls.push(lastmod !== undefined ? { loc, lastModified: lastmod } : { loc }); } if (urls.length === 0 && childSitemaps.length === 0) { const re = /]*>([\s\S]*?)<\/loc>/gi; let m: RegExpExecArray | null; while ((m = re.exec(xml)) !== null) { const loc = m[1] !== undefined ? decodeXml(m[1]) : ""; if (loc !== "") urls.push({ loc }); } } return { urls, childSitemaps }; }