import type { ShardInfo, UrlEntry } from './types'; const XML_HEADER = ''; const SITEMAP_SCHEMA = 'http://www.sitemaps.org/schemas/sitemap/0.9'; const TANGLED_EPOCH = new Date('2025-01-01T00:00:00Z'); export const PROTOCOL_MAX_URLS = 50000; export const PROTOCOL_MAX_BYTES = 50 * 1024 * 1024; // 50 MB uncompressed export function escapeXml(str: string): string { return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } export function unescapeXml(str: string): string { return str .replace(/'/g, "'") .replace(/"/g, '"') .replace(/>/g, '>') .replace(/</g, '<') .replace(/&/g, '&'); } export function sanitizeLastmod(rawDate?: string, referenceTime = new Date()): string { if (!rawDate) { return referenceTime.toISOString().split('T')[0]; } const parsed = new Date(rawDate); if (isNaN(parsed.getTime()) || parsed > referenceTime || parsed < TANGLED_EPOCH) { return referenceTime.toISOString().split('T')[0]; } return parsed.toISOString().split('T')[0]; } export function measureXmlBytes(xml: string): number { return new TextEncoder().encode(xml).byteLength; } export function buildUrlsetXml(urls: UrlEntry[]): string { const body = urls .map((u) => { const parts = [` ${escapeXml(u.loc)}`]; if (u.lastmod) parts.push(` ${escapeXml(u.lastmod)}`); if (u.changefreq) parts.push(` ${u.changefreq}`); if (u.priority !== undefined) parts.push(` ${u.priority.toFixed(1)}`); return ` \n${parts.join('\n')}\n `; }) .join('\n'); return `${XML_HEADER}\n\n${body}\n\n`; } export function buildSitemapIndexXml(shards: ShardInfo[], baseUrl: string): string { const body = shards .map((shard) => { const loc = `${baseUrl.replace(/\/+$/, '')}/sitemaps/${shard.filename}`; return ` \n ${escapeXml(loc)}\n ${escapeXml(shard.lastmod)}\n `; }) .join('\n'); return `${XML_HEADER}\n\n${body}\n\n`; } export async function gzipCompress(text: string): Promise { const blob = new Blob([text]); const stream = blob.stream().pipeThrough(new CompressionStream('gzip')); const buffer = await new Response(stream).arrayBuffer(); return new Uint8Array(buffer); } export async function gzipDecompress(data: ArrayBuffer | Uint8Array): Promise { const blob = new Blob([data as any]); const stream = blob.stream().pipeThrough(new DecompressionStream('gzip')); return await new Response(stream).text(); }