/**
* Pure XML parsing utilities for the national CAP 1.2 alert feeds — SACHET
* (India, NDMA), PAGASA (Philippines), and BMKG (Indonesia).
*
* Zero I/O, zero logging: XML strings in, typed records (or a thrown, fixed,
* sanitized message) out. The service layer owns fetching, caching, and
* deciding garnish-vs-contract failure handling; this module owns every
* shape/size constant and every parsing decision that follows from field
* names, never from position.
*
* Load-bearing behaviours, verified live and in scratch on 2026-08-23:
* - `fast-xml-parser`'s own parser is lenient — `parse('')` returns
* `{"a":{"b":""}}` without throwing — so it must never be the
* well-formedness check. `XMLValidator.validate` is the check, but it too
* has gaps: it *accepts* two distinct self-closing roots
* (`…`, ``) and it *accepts* two self-closing
* roots that share a tag name (``, which the parser then
* silently coalesces into a single `alert` key holding an array). Only a
* post-parse structural check — exactly one non-PI root key, and that
* key's value is not an array — catches every case actually seen.
* - SACHET documents use a `cap:` namespace prefix throughout and carry no
* ``/Atom `` may parse as a plain string or as
* `{ '#text': string, '@_...': string }` when the tag also carries
* attributes (SACHET's `…`) — every index
* field is read through a text-or-`#text` accessor, never a bare cast.
* - An HTTP 200 HTML error page is a *shape* failure, not a DOCTYPE failure —
* it gets its own message so a caller doesn't have to pattern-match on the
* word "DOCTYPE" to recognise "this wasn't even trying to be CAP".
*
* Every thrown message here is fixed text — never the input XML, never a
* raw parser error — so an oversize or malformed feed can't leak upstream
* content into logs or user-facing errors.
*/
import type { CapAlertDocument, CapAlertInfo, CapIndexEntry, NationalCapFeed, NationalCapWarning, NormalizedCapIndexEntry } from '../types/cap.js';
/** Maximum accepted byte size of a fetched CAP document or feed index. */
export declare const MAX_DOCUMENT_BYTES = 2000000;
/** Maximum number of entries kept from one feed index. */
export declare const MAX_INDEX_ITEMS = 200;
/** Maximum number of polygon rings kept for one warning's geometry. */
export declare const MAX_RINGS_PER_WARNING = 256;
/** Maximum number of coordinate pairs kept for one polygon ring. */
export declare const MAX_POINTS_PER_RING = 10000;
/**
* Parse an XML document into an untyped structural tree, applying every
* pre-parse guard in a fixed order and throwing a fixed, sanitized message —
* never the input text, never a raw parser error — for any failure.
*/
export declare function parseXml(xml: string): unknown;
/**
* Parse a CAP feed index (RSS or Atom) into flat entries, by field name
* only. Root/envelope checks require an **exact** root↔kind pairing and a
* non-null envelope object — an rss-configured feed that returns Atom (or
* vice versa), or any other unexpected top shape, throws rather than
* returning `[]`: a silent empty list on safety data is a fabricated
* all-clear. A recognised envelope with no `item`/`entry` array is an
* honest empty and returns normally.
*/
export declare function parseCapIndex(xml: string, kind: 'rss' | 'atom'): {
entries: CapIndexEntry[];
trimmed: boolean;
};
/**
* Drop and count any index entry lacking a non-empty trimmed `identifier` or
* `documentUrl`; dedupe duplicate identifiers, first wins. Only validated
* values may become cache keys downstream.
*/
export declare function normalizeIndexEntries(entries: CapIndexEntry[]): {
entries: NormalizedCapIndexEntry[];
dropped: number;
};
/**
* Pure SSRF guard for every feed-supplied URL (index links, CAP document
* URLs, SACHET `Polygon URL` parameters alike): `https:` only, hostname
* **exactly** in the feed's allowlist (no port, no userinfo — a `user@host`
* form is rejected), path starting with one of the feed's allowed prefixes.
* Never throws.
*/
export declare function isAllowedFeedUrl(url: string, feed: Pick): boolean;
/**
* Parse a CAP alert document. Root must be `alert` (any namespace prefix
* already stripped) — else throws. `info[]`/`area[]`/`parameter[]`/
* `polygon[]` are always arrays, never `undefined`, whether the source XML
* had zero, one, or many.
*/
export declare function parseCapDocument(xml: string): CapAlertDocument;
/**
* Parse SACHET's separate linked-polygon document: `
* ……` siblings. Each ring
* through `parseCapPolygon`; invalid rings are dropped silently — this may
* legitimately return `rings: []` (the *service* decides what that means:
* geometry unavailable). More than `MAX_RINGS_PER_WARNING` valid rings
* keeps none and reports `trimmed: true`.
*/
export declare function parsePolygonDocument(xml: string): {
rings: Array>;
trimmed: boolean;
failed: number;
};
/**
* Parse one CAP polygon string: whitespace-separated `"lat,lon"` pairs.
* Never throws — returns `null` for fewer than 4 points, a non-finite
* number, an out-of-range lat/lon, an **unclosed** ring (first point ≠ last
* point — never auto-closed), or more than `MAX_POINTS_PER_RING` points.
*/
export declare function parseCapPolygon(text: string): Array<[number, number]> | null;
/**
* Select the language variant to render: the first block whose `language`
* starts (case-insensitively) with `preferLanguage`, else the first block.
* Generalised from `selectEnglishInfo` in `src/services/meteoalarm.ts:116`.
* No script detection — a mislabelled `language` renders unmodified, same
* as MeteoAlarm.
*/
export declare function selectPreferredInfo(info: CapAlertInfo[] | undefined, preferLanguage: string): CapAlertInfo | undefined;
/**
* Extract the referenced identifiers from a CAP `references` value: a
* space-separated list of `sender,identifier,sent` triples. Entries without
* a comma are taken as bare identifiers (permissive — feeds vary).
*
* Deliberate copy of `parseReferences` in `src/services/meteoalarm.ts`
* (lines 145-161) rather than a shared import: this is a pure util and must
* not import a service module, and the MeteoAlarm path — including its
* existing tests — is locked and must not change to accommodate this
* feature.
*/
export declare function parseReferences(references: string | undefined): string[];
/** The selected info block's `parameter` whose `valueName === 'Polygon URL'` (SACHET). */
export declare function linkedPolygonUrl(info: CapAlertInfo): string | undefined;
/**
* Flatten a parsed CAP document to a single-language-variant warning.
* Returns `undefined` when there is no identifier or no info block — a
* document-shape failure the caller must count as unavailable, not an
* honest empty.
*
* `polygonSource: 'inline'` reads rings from every `area[].polygon[]` in the
* *selected* info block. `polygonSource: 'linked-parameter'` sets
* `linkedPolygonUrl` and leaves `polygons: []` for the service to fill after
* fetching that URL.
*
* `polygonUnavailable`/`geometryTrimmed` are normally the service's call,
* with one exception handled here: an inline document whose ``
* elements *all* failed to parse, or whose ring count exceeded
* `MAX_RINGS_PER_WARNING`, sets `polygonUnavailable: true` (geometry was
* published but is unusable/trimmed) — the trim case also sets
* `geometryTrimmed: true`.
*/
export declare function flattenCapAlert(doc: CapAlertDocument, feed: Pick, countryCode: string): NationalCapWarning | undefined;
/**
* Read-time filter pipeline, in this exact order:
* 1. drop `status` present and ≠ `'Actual'`;
* 2. drop `msgType === 'Cancel'`;
* 3. drop expired `expires` (unparseable/missing → keep);
* 4. build the superseded set from every *surviving* `msgType === 'Update'`
* — including `AllClear` ones;
* 5. drop superseded;
* 6. **last**, drop `responseType` containing `'AllClear'`.
*
* Steps 4-6 must run in this order: dropping AllClear before building the
* superseded set (step 4) would leave a PAGASA-cancelled-but-still-unexpired
* advisory live — PAGASA's "Final" advisory is itself `msgType: 'Update'` +
* `responseType: ['AllClear']`, and *it* is the message that retires the
* prior advisory via `references`. On SACHET, supersession is a no-op:
* SACHET republishes a warning under a new numeric identifier with
* `references` pointing at the prior version, but that prior version was
* never independently indexed, so steps 4-5 never have anything to match —
* only step 3 (expiry) or step 6 (an explicit AllClear) retires a SACHET
* warning.
*/
export declare function filterActiveCapWarnings(warnings: NationalCapWarning[], now: Date): NationalCapWarning[];
//# sourceMappingURL=capParse.d.ts.map