{"version":3,"file":"utils.mjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import type { Element } from \"./types\";\n\n/**\n * Standard OOXML XML declaration used in all Office Open XML documents.\n *\n * The declaration specifies UTF-8 encoding and standalone=\"yes\" as required\n * by the OOXML specification. All XML parts in .docx, .pptx, and .xlsx files\n * use this declaration.\n */\nexport const OOXML_XML_DECLARATION = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>';\n\n/**\n * A readonly array guaranteed to have at least one element. Use with\n * {@link isNonEmpty} to narrow `T[]` so indexed access (e.g. `arr[0]`)\n * returns `T` instead of `T | undefined` under `noUncheckedIndexedAccess`.\n */\nexport type NonEmptyArray<T> = readonly [T, ...ReadonlyArray<T>];\n\n/**\n * User-defined type guard narrowing an array to {@link NonEmptyArray}.\n * Prefer this over a bare `arr.length > 0` check, which TypeScript does\n * not reliably narrow into a non-empty tuple at the read site.\n */\nexport const isNonEmpty = <T>(arr: readonly T[]): arr is NonEmptyArray<T> => arr.length > 0;\n\n/**\n * Find the first direct child element with the given name.\n */\nexport function findChild(parent: Element | undefined, name: string): Element | undefined {\n  return parent?.elements?.find((e) => e.name === name);\n}\n\n/**\n * Get all direct child elements matching the given name.\n */\nexport function children(parent: Element | undefined, name: string): Element[] {\n  return parent?.elements?.filter((e) => e.name === name) ?? [];\n}\n\n/**\n * Get all direct child elements.\n */\nexport function allChildren(parent: Element | undefined): Element[] {\n  return parent?.elements ?? [];\n}\n\n/**\n * Get text content of the first child element with the given name.\n */\nexport function childText(parent: Element | undefined, name: string): string {\n  const child = findChild(parent, name);\n  return textOf(child);\n}\n\n/**\n * Get text content of an element.\n * Handles cases where text may be directly on .text or in a child element.\n */\nexport function textOf(element: Element | undefined): string {\n  if (!element) return \"\";\n  if (element.text !== undefined && typeof element.text === \"string\") return element.text;\n  if (element.elements && element.elements.length > 0) {\n    let text = \"\";\n    for (const e of element.elements) {\n      if (typeof e.text === \"string\") text += e.text;\n    }\n    return text;\n  }\n  return \"\";\n}\n\n/**\n * Collect text from all direct text nodes within an element.\n */\nexport function collectText(element: Element | undefined): string {\n  if (!element) return \"\";\n  const parts: string[] = [];\n  collectTextRecursive(element, parts);\n  return parts.join(\"\");\n}\n\nfunction collectTextRecursive(element: Element | undefined, parts: string[]): void {\n  if (!element) return;\n  if (element.text !== undefined && typeof element.text === \"string\") {\n    parts.push(element.text);\n  }\n  if (element.elements) {\n    for (const child of element.elements) {\n      collectTextRecursive(child, parts);\n    }\n  }\n}\n\n/**\n * Get an attribute value as a string.\n */\nexport function attr(element: Element | undefined, name: string): string | undefined {\n  const v = element?.attributes?.[name];\n  return v !== undefined ? String(v) : undefined;\n}\n\n/**\n * Get an attribute value as a number.\n */\nexport function attrNum(element: Element | undefined, name: string): number | undefined {\n  const v = element?.attributes?.[name];\n  if (v === undefined) return undefined;\n  const n = Number(v);\n  return isNaN(n) ? undefined : n;\n}\n\n/**\n * Get a measurement attribute as a number or a verbatim measure/percent string.\n *\n * Counterpart to {@link attrNum} for XSD attribute unions of a decimal number\n * and UniversalMeasure/Percentage (ST_TwipsMeasure, ST_MeasurementOrPercent,\n * CT_TblWidth/@w, CT_Height/@val): a plain numeric token yields a number, while\n * UniversalMeasure (\"5mm\") and Percentage (\"50%\") stay verbatim so they\n * round-trip with the stringify-side value helpers in @office-open/core.\n *\n * For CT_TblWidth with type=\"pct\", the fiftieths token (\"5000\" = 100%) is a\n * plain numeric token and is returned as the number 5000; the stringify side\n * emits it verbatim (never \"5000%\", which is a different XSD branch).\n */\nexport function attrMeasure(\n  element: Element | undefined,\n  name: string,\n): number | string | undefined {\n  const v = element?.attributes?.[name];\n  if (v === undefined) return undefined;\n  const raw = String(v);\n  const n = Number(raw);\n  return Number.isNaN(n) ? raw : n;\n}\n\n/**\n * Get an attribute value as a boolean.\n */\nexport function attrBool(element: Element | undefined, name: string): boolean | undefined {\n  const v = element?.attributes?.[name];\n  if (v === undefined) return undefined;\n  if (typeof v === \"boolean\") return v;\n  const lower = String(v).toLowerCase();\n  // ST_OnOff across editions: transitional uses xsd:boolean (true/false/1/0),\n  // the original 2006 enumeration added on/off plus the single-letter t/f\n  // Word 2007 wrote — accept the full union so old files parse losslessly.\n  if (lower === \"true\" || lower === \"1\" || lower === \"on\" || lower === \"t\") return true;\n  if (lower === \"false\" || lower === \"0\" || lower === \"off\" || lower === \"f\") return false;\n  return undefined;\n}\n\n/**\n * Get a hex color attribute, handling nativeTypeValue coercion.\n * nativeTypeAttributes converts \"000000\" → 0 (number); this recovers\n * the original 6-digit hex string by zero-padding numeric values.\n */\nexport function colorAttr(element: Element | undefined, name: string): string | undefined {\n  const raw = element?.attributes?.[name];\n  if (raw === undefined || raw === \"\") return undefined;\n  if (typeof raw === \"boolean\") return undefined;\n  if (typeof raw === \"number\") {\n    return String(raw).padStart(6, \"0\");\n  }\n  if (raw === \"auto\") return \"auto\";\n  if (/^[0-9A-Fa-f]{6}$/.test(raw)) return raw;\n  return raw;\n}\n\n/**\n * Check if an element has a specific child element.\n */\nexport function hasChild(parent: Element | undefined, name: string): boolean {\n  return parent?.elements?.some((e) => e.name === name) ?? false;\n}\n\n/**\n * Find deep descendant elements matching the given name.\n */\nexport function findDeep(parent: Element | undefined, name: string): Element[] {\n  const result: Element[] = [];\n  collectDeep(parent, name, result);\n  return result;\n}\n\nfunction collectDeep(parent: Element | undefined, name: string, result: Element[]): void {\n  if (!parent) return;\n  for (const child of parent.elements ?? []) {\n    if (child.name === name) result.push(child);\n    collectDeep(child, name, result);\n  }\n}\n\n/**\n * Find the first descendant element with the given name (depth-first pre-order).\n * Short-circuits at the first match — prefer this over `findDeep(parent, name)[0]`\n * to avoid traversing the whole subtree and allocating the full results array.\n */\nexport function findFirst(parent: Element | undefined, name: string): Element | undefined {\n  if (!parent) return undefined;\n  for (const child of parent.elements ?? []) {\n    if (child.name === name) return child;\n    const found = findFirst(child, name);\n    if (found) return found;\n  }\n  return undefined;\n}\n\n/**\n * Get the number of direct child elements.\n */\nexport function childCount(parent: Element | undefined): number {\n  return parent?.elements?.length ?? 0;\n}\n"],"mappings":";;;;;;;;AASA,MAAa,wBAAwB;;;;;;AAcrC,MAAa,cAAiB,QAA+C,IAAI,SAAS;;;;AAK1F,SAAgB,UAAU,QAA6B,MAAmC;CACxF,OAAO,QAAQ,UAAU,MAAM,MAAM,EAAE,SAAS,IAAI;AACtD;;;;AAKA,SAAgB,SAAS,QAA6B,MAAyB;CAC7E,OAAO,QAAQ,UAAU,QAAQ,MAAM,EAAE,SAAS,IAAI,KAAK,CAAC;AAC9D;;;;AAKA,SAAgB,YAAY,QAAwC;CAClE,OAAO,QAAQ,YAAY,CAAC;AAC9B;;;;AAKA,SAAgB,UAAU,QAA6B,MAAsB;CAE3E,OAAO,OADO,UAAU,QAAQ,IACd,CAAC;AACrB;;;;;AAMA,SAAgB,OAAO,SAAsC;CAC3D,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,QAAQ,SAAS,KAAA,KAAa,OAAO,QAAQ,SAAS,UAAU,OAAO,QAAQ;CACnF,IAAI,QAAQ,YAAY,QAAQ,SAAS,SAAS,GAAG;EACnD,IAAI,OAAO;EACX,KAAK,MAAM,KAAK,QAAQ,UACtB,IAAI,OAAO,EAAE,SAAS,UAAU,QAAQ,EAAE;EAE5C,OAAO;CACT;CACA,OAAO;AACT;;;;AAKA,SAAgB,YAAY,SAAsC;CAChE,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,QAAkB,CAAC;CACzB,qBAAqB,SAAS,KAAK;CACnC,OAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,qBAAqB,SAA8B,OAAuB;CACjF,IAAI,CAAC,SAAS;CACd,IAAI,QAAQ,SAAS,KAAA,KAAa,OAAO,QAAQ,SAAS,UACxD,MAAM,KAAK,QAAQ,IAAI;CAEzB,IAAI,QAAQ,UACV,KAAK,MAAM,SAAS,QAAQ,UAC1B,qBAAqB,OAAO,KAAK;AAGvC;;;;AAKA,SAAgB,KAAK,SAA8B,MAAkC;CACnF,MAAM,IAAI,SAAS,aAAa;CAChC,OAAO,MAAM,KAAA,IAAY,OAAO,CAAC,IAAI,KAAA;AACvC;;;;AAKA,SAAgB,QAAQ,SAA8B,MAAkC;CACtF,MAAM,IAAI,SAAS,aAAa;CAChC,IAAI,MAAM,KAAA,GAAW,OAAO,KAAA;CAC5B,MAAM,IAAI,OAAO,CAAC;CAClB,OAAO,MAAM,CAAC,IAAI,KAAA,IAAY;AAChC;;;;;;;;;;;;;;AAeA,SAAgB,YACd,SACA,MAC6B;CAC7B,MAAM,IAAI,SAAS,aAAa;CAChC,IAAI,MAAM,KAAA,GAAW,OAAO,KAAA;CAC5B,MAAM,MAAM,OAAO,CAAC;CACpB,MAAM,IAAI,OAAO,GAAG;CACpB,OAAO,OAAO,MAAM,CAAC,IAAI,MAAM;AACjC;;;;AAKA,SAAgB,SAAS,SAA8B,MAAmC;CACxF,MAAM,IAAI,SAAS,aAAa;CAChC,IAAI,MAAM,KAAA,GAAW,OAAO,KAAA;CAC5B,IAAI,OAAO,MAAM,WAAW,OAAO;CACnC,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,YAAY;CAIpC,IAAI,UAAU,UAAU,UAAU,OAAO,UAAU,QAAQ,UAAU,KAAK,OAAO;CACjF,IAAI,UAAU,WAAW,UAAU,OAAO,UAAU,SAAS,UAAU,KAAK,OAAO;AAErF;;;;;;AAOA,SAAgB,UAAU,SAA8B,MAAkC;CACxF,MAAM,MAAM,SAAS,aAAa;CAClC,IAAI,QAAQ,KAAA,KAAa,QAAQ,IAAI,OAAO,KAAA;CAC5C,IAAI,OAAO,QAAQ,WAAW,OAAO,KAAA;CACrC,IAAI,OAAO,QAAQ,UACjB,OAAO,OAAO,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG;CAEpC,IAAI,QAAQ,QAAQ,OAAO;CAC3B,IAAI,mBAAmB,KAAK,GAAG,GAAG,OAAO;CACzC,OAAO;AACT;;;;AAKA,SAAgB,SAAS,QAA6B,MAAuB;CAC3E,OAAO,QAAQ,UAAU,MAAM,MAAM,EAAE,SAAS,IAAI,KAAK;AAC3D;;;;AAKA,SAAgB,SAAS,QAA6B,MAAyB;CAC7E,MAAM,SAAoB,CAAC;CAC3B,YAAY,QAAQ,MAAM,MAAM;CAChC,OAAO;AACT;AAEA,SAAS,YAAY,QAA6B,MAAc,QAAyB;CACvF,IAAI,CAAC,QAAQ;CACb,KAAK,MAAM,SAAS,OAAO,YAAY,CAAC,GAAG;EACzC,IAAI,MAAM,SAAS,MAAM,OAAO,KAAK,KAAK;EAC1C,YAAY,OAAO,MAAM,MAAM;CACjC;AACF;;;;;;AAOA,SAAgB,UAAU,QAA6B,MAAmC;CACxF,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,KAAK,MAAM,SAAS,OAAO,YAAY,CAAC,GAAG;EACzC,IAAI,MAAM,SAAS,MAAM,OAAO;EAChC,MAAM,QAAQ,UAAU,OAAO,IAAI;EACnC,IAAI,OAAO,OAAO;CACpB;AAEF;;;;AAKA,SAAgB,WAAW,QAAqC;CAC9D,OAAO,QAAQ,UAAU,UAAU;AACrC"}