/**
* 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;