/** * Parse a Sitecore layout XML string back into a structural `Layout` shape. * * This is the inverse of `emitLayoutXml` (`./emit.ts`): where the emitter * turns a recipe-level `Layout` into the wire string Sitecore stores on the * `__Renderings` / `__Final Renderings` field, `parseLayoutXml` walks that * wire string and reconstructs the `Layout` — the seam `read-current.ts` * uses to reverse-project the layout-bearing recipe kinds (partial-design, * page-design, page). * * ## Both wire forms * * `emitLayoutXml` emits two forms; this parser auto-detects and handles * both off the root element's namespace declarations: * * - **canonical** — ` * `. The default form; Page Design * items round-trip it byte-for-byte. * * - **delta** — SXA Partial Design form: ` * * `. The Partial Design Layout pipeline normalises canonical * input into this form on first write. * * The two forms differ only in attribute prefixes (`s:ph` vs `ph`; legacy `placeh` accepted), * the always-present `s:par=""` in delta, the `` directive * element delta carries, and the per-placement anchor attributes * (`p:before` / `p:after`) delta uses instead of document order. Either * way the parser reads the `` rendering elements in document order — * which is the placement order both emitters write — so placement ordering * is preserved without interpreting the delta anchors. (The delta anchors * are themselves derived from document order by `emitDelta`, so document * order is the faithful signal.) * * ## What a parsed placement carries * * Each `` rendering element yields a `ParsedPlacement`: * * id → renderingGuid (bare 32-hex, lower-case) * ph → placeholderKey (the `Layout.placeholders` dictionary key) * ds → datasourceGuid (bare hex) — or a `local:` sentinel * par → variant + params (URL-decoded; the `FieldNames` param lifts * out to `variant`, the rest stay in `params`) * uid → placementUid (bare hex; informational — placement identity * is positional, not uid-derived, on the reverse path) * * GUID→handle resolution is NOT done here: this module is a pure * wire-format decoder with no knowledge of the recipe handle space. * `read-current.ts` owns the GUID→handle index and maps * `ParsedPlacement` → `ComponentPlacement`. */ /** * A single rendering placement decoded from one `` element, before * GUID→handle resolution. `read-current.ts` turns this into a recipe-level * `ComponentPlacement`. */ export interface ParsedPlacement { /** Rendering item GUID, bare 32-hex lower-case (the `id` attribute). */ renderingGuid: string; /** Placeholder key — the `Layout.placeholders` dictionary key. */ placeholderKey: string; /** Placement UID, bare hex. Informational; placement identity is positional. */ uid: string; /** * Datasource the placement binds to. Absent when the `` element had * no `ds` attribute (a `kind: "none"` config-driven rendering). * * - `{ kind: "guid" }` — a real Sitecore item GUID (bare hex). The * caller resolves it to a `shared` (content-item) or `scoped` * (page-local) datasource ref. * - `{ kind: "local" }` — the `local:` sentinel `emitLayoutXml` * writes for an unresolved scoped ref. Maps straight to a * `kind: "scoped"` datasourceRef with that slot. */ datasource?: { kind: "guid"; guid: string; } | { kind: "local"; slot: string; }; /** SXA Rendering Variant — the `FieldNames` rendering parameter, if present. */ variant?: string; /** Remaining rendering parameters (everything in `par` except `FieldNames`). */ params?: Record; } /** * The structural result of parsing a layout XML string. `placeholders` * preserves placement order per key — array order is render order, the * same contract `LayoutSchema` carries. */ export interface ParsedLayout { placeholders: Record; /** * The wire form the input was in — `"canonical"` or `"delta"`. Returned * for diagnostics; callers reconstructing a `Layout` ignore it (the * recipe `Layout` shape is form-agnostic). */ mode: "canonical" | "delta"; /** * SXA JSON Layout definition GUID (bare hex), recovered from the device * element's `l="{…}"` attribute when present. Only canonical layouts * carry it (a page-template `__Standard Values` shell); `undefined` * otherwise. */ layoutId?: string; } /** * Decode a `par` blob (URL-encoded `key=value&key=value` pairs) into a * `{ variant, params }` split. The SXA Rendering Variant selection rides * as the `FieldNames` parameter (see `emit.ts` `resolvePlacement`), so it * is lifted out to `variant`; every other pair stays in `params`. * * Exported for direct unit testing — it is the trickiest decode in the * round-trip and the one place a `+`-vs-`%20` or empty-value quirk would * bite. * * `params` is `undefined` (not `{}`) when no non-FieldNames pairs remain, * so the reconstructed placement omits an empty `params` rather than * fabricating one — the lossy-projection contract `read-current.ts` keeps. */ export declare const decodeParBlob: (par: string) => { variant?: string; params?: Record; }; /** * Parse a Sitecore layout XML string into a `ParsedLayout`. * * Auto-detects the wire form (`canonical` vs `delta`) from the root `` * element's namespace declarations — `xmlns:p` / `p:p` is the delta * marker. Reads every `` rendering element inside the single device * element in document order, groups them by `placeh`, and preserves * per-placeholder placement order. * * Returns an empty-placeholders `ParsedLayout` for an empty string or a * device shell with no renderings (a page-template standard-values layout) * — an empty layout is valid, not an error. * * Throws `INPUT_INVALID` only for genuinely malformed input: a string that * isn't a layout XML root, or an `` element with no rendering id. */ export declare function parseLayoutXml(xml: string): ParsedLayout; /** * Structural equality of two layout XML strings — the seam the planner * uses to decide whether a layout field genuinely drifted. * * Sitecore's layout pipeline normalises layout XML on write: canonical * input is rewritten to SXA delta form, and a versioned `__Final * Renderings` picks up extra `` baseline directives. A naive * string compare of "what the recipe emits" vs "what the tenant stores" * then reports a phantom update on every re-push. Parsing both sides and * comparing the recovered structure makes the diff wire-form-agnostic — * same placeholders, same placements (rendering, datasource, variant, * params) in the same order ⇒ equivalent. * * Placement `uid`s are excluded (placement identity is positional, and * the uid is a deterministic derivation either way); `mode` is excluded * (it IS the wire-form difference being normalised away); `layoutId` IS * compared — it's semantically meaningful. * * Falls back to strict string equality when either side fails to parse, * so genuinely malformed input is never silently treated as equal. */ export declare const layoutXmlEquivalent: (a: string, b: string) => boolean; /** * Variant of `layoutXmlEquivalent` that takes pre-parsed layouts — * use it when the caller already parsed both sides (the planner's * `computeFieldDrift` parses once for the equivalence check AND * once-per-side for hashing — passing the pre-parsed values dedupes * the regex-driven parse work on the hot path). */ export declare const layoutXmlEquivalentFromParsed: (a: ParsedLayout, b: ParsedLayout) => boolean;