{"version":3,"file":"defined-names-CviWmtQg.mjs","names":[],"sources":["../src/workbook/defined-names.ts"],"sourcesContent":["// Workbook-level defined names. Models the OOXML schema's `<definedName>` element.\n//\n// A defined name binds an identifier to a formula-style value (`'Sheet\n// 1'!$A$1:$B$10`, `SUM(A:A)`, etc). Workbook-scope names omit `localSheetId`;\n// sheet-scope names use the 0-based sheet index. Excel reserves a handful of\n// names with the `_xlnm.` prefix for built-in uses (Print_Area, Print_Titles,\n// Sheet_Title, etc) — those round-trip here as plain DefinedName entries since\n// the value semantics are the same.\n\nimport { OpenXmlSchemaError } from '../utils/exceptions';\n\nexport interface DefinedName {\n  /** Identifier — `_xlnm.Print_Area` for built-ins, otherwise user-chosen. */\n  name: string;\n  /** The formula expression the name points at. */\n  value: string;\n  /** 0-based sheet index for sheet-scope names; undefined → workbook-scope. */\n  scope?: number;\n  /** Hidden from the Name Manager when true. */\n  hidden?: boolean;\n  /** Optional human-readable description. */\n  comment?: string;\n}\n\nexport function makeDefinedName(opts: Partial<DefinedName> & { name: string; value: string }): DefinedName {\n  return {\n    name: opts.name,\n    value: opts.value,\n    ...(opts.scope !== undefined ? { scope: opts.scope } : {}),\n    ...(opts.hidden !== undefined ? { hidden: opts.hidden } : {}),\n    ...(opts.comment !== undefined ? { comment: opts.comment } : {}),\n  };\n}\n\n// ---- Workbook ergonomic helpers -----------------------------------------\n\nimport { type CellRangeBoundaries, parseSheetRange } from '../utils/coordinate';\nimport type { Worksheet } from '../worksheet/worksheet';\nimport { getRangeAddress } from '../worksheet/worksheet';\nimport type { Workbook } from './workbook';\n\n/**\n * One parsed leg of a defined name's value. Defined-name values can be\n * comma-separated multi-range expressions (e.g. `_xlnm.Print_Titles` sets\n * `Sheet!$1:$1,Sheet!$A:$A`); this represents one such leg.\n */\nexport interface DefinedNameTarget {\n  sheet: string;\n  range: string;\n  bounds: CellRangeBoundaries;\n}\n\n/**\n * Add a workbook-scope or sheet-scope defined name. If a defined name with the\n * same `name` (and `scope`) already exists, it's replaced — Excel allows one\n * workbook-scope and one per-sheet-scope name, but not two with the same scope.\n * Returns the resulting `DefinedName`.\n */\nexport const addDefinedName = (\n  wb: Workbook,\n  opts: Partial<DefinedName> & { name: string; value: string },\n): DefinedName => {\n  const dn = makeDefinedName(opts);\n  // Replace any existing entry with the same name + scope.\n  const idx = wb.definedNames.findIndex((d) => d.name === dn.name && d.scope === dn.scope);\n  if (idx >= 0) {\n    wb.definedNames[idx] = dn;\n  } else {\n    wb.definedNames.push(dn);\n  }\n  return dn;\n};\n\n/**\n * High-level: register a defined name pointing at a worksheet range. Combines\n * {@link getRangeAddress} (sheet-qualified, properly quoted) with {@link\n * addDefinedName}, so the caller doesn't have to assemble the formula string by\n * hand.\n *\n * Pass `opts.localToSheet: true` to scope the name to the worksheet (instead of\n * the workbook). Re-using the same `name` + scope replaces the previous entry\n * (Excel's per-scope-uniqueness rule).\n *\n * Throws when `localToSheet: true` is set but the worksheet isn't on\n * `wb.sheets` — that would be a stale Worksheet reference.\n */\nexport const addDefinedNameForRange = (\n  wb: Workbook,\n  name: string,\n  ws: Worksheet,\n  range: string,\n  opts: { localToSheet?: boolean; hidden?: boolean; comment?: string } = {},\n): DefinedName => {\n  const value = getRangeAddress(ws, range);\n  let scope: number | undefined;\n  if (opts.localToSheet) {\n    const idx = wb.sheets.findIndex((s) => s.sheet === ws);\n    if (idx < 0) {\n      throw new OpenXmlSchemaError(\n        `addDefinedNameForRange: worksheet \"${ws.title}\" is not registered on this workbook`,\n      );\n    }\n    scope = idx;\n  }\n  return addDefinedName(wb, {\n    name,\n    value,\n    ...(scope !== undefined ? { scope } : {}),\n    ...(opts.hidden !== undefined ? { hidden: opts.hidden } : {}),\n    ...(opts.comment !== undefined ? { comment: opts.comment } : {}),\n  });\n};\n\n/** Look up a defined name by identifier and (optional) sheet scope. */\nexport const getDefinedName = (\n  wb: Workbook,\n  name: string,\n  scope?: number,\n): DefinedName | undefined => wb.definedNames.find((d) => d.name === name && d.scope === scope);\n\n/**\n * Resolve a defined name's `value` into one or more {@link DefinedNameTarget}s.\n * Comma-separated values (e.g. `_xlnm.Print_Titles` typically sets\n * `Sheet!$1:$1,Sheet!$A:$A`) yield one entry per leg; a plain `Sheet!A1:B5`\n * yields a single-element array.\n *\n * Returns `undefined` when the name doesn't exist; throws when the value can't\n * be parsed (e.g. a constant or a non-range formula — defined names are\n * sometimes used for things like `=42` or `=SUM(A:A)` which aren't ranges).\n */\nexport const getDefinedNameTarget = (\n  wb: Workbook,\n  name: string,\n  scope?: number,\n): DefinedNameTarget[] | undefined => {\n  const dn = getDefinedName(wb, name, scope);\n  if (!dn) return undefined;\n  // Defined-name values use `,` as the leg separator. Sheet titles can\n  // themselves contain commas inside `'...'` quotes — split on commas that\n  // aren't inside an unbalanced single-quoted segment.\n  const legs: string[] = [];\n  let current = '';\n  let inQuote = false;\n  for (let i = 0; i < dn.value.length; i++) {\n    const c = dn.value[i];\n    if (c === \"'\") {\n      // Doubled `''` inside a quoted run is the escape for a literal apostrophe\n      // — skip the second one without flipping the state.\n      if (inQuote && dn.value[i + 1] === \"'\") {\n        current += \"''\";\n        i++;\n        continue;\n      }\n      inQuote = !inQuote;\n      current += c;\n      continue;\n    }\n    if (c === ',' && !inQuote) {\n      legs.push(current);\n      current = '';\n      continue;\n    }\n    current += c;\n  }\n  if (current.length > 0) legs.push(current);\n  return legs.map((leg) => parseSheetRange(leg));\n};\n\n/**\n * Remove a defined name by identifier + scope. Returns true if any entry was\n * removed.\n */\nexport const removeDefinedName = (wb: Workbook, name: string, scope?: number): boolean => {\n  const idx = wb.definedNames.findIndex((d) => d.name === name && d.scope === scope);\n  if (idx < 0) return false;\n  wb.definedNames.splice(idx, 1);\n  return true;\n};\n\n/**\n * Read-only snapshot of every defined name. Pass `{ scope }` to narrow to\n * workbook-scope (`scope: undefined`) or one specific sheet (`scope: 0`) — omit\n * the option entirely to list all.\n */\nexport const listDefinedNames = (\n  wb: Workbook,\n  opts: { scope?: number | 'workbook' | 'all' } = {},\n): ReadonlyArray<DefinedName> => {\n  const scope = opts.scope ?? 'all';\n  if (scope === 'all') return wb.definedNames;\n  if (scope === 'workbook') return wb.definedNames.filter((d) => d.scope === undefined);\n  return wb.definedNames.filter((d) => d.scope === scope);\n};\n\n/**\n * Bulk-remove every defined name matching `predicate`. Returns the count\n * removed. Mirrors {@link removeDataValidations} on worksheets.\n */\nexport const removeDefinedNames = (\n  wb: Workbook,\n  predicate: (d: DefinedName) => boolean,\n): number => {\n  const before = wb.definedNames.length;\n  wb.definedNames = wb.definedNames.filter((d) => !predicate(d));\n  return before - wb.definedNames.length;\n};\n\n/**\n * Rename a defined name, scoped or workbook-scope. Returns `true` when an entry\n * was renamed. Throws when `newName` is already taken with the same scope\n * (Excel forbids duplicates within a scope).\n */\nexport const renameDefinedName = (\n  wb: Workbook,\n  oldName: string,\n  newName: string,\n  scope?: number,\n): boolean => {\n  const idx = wb.definedNames.findIndex((d) => d.name === oldName && d.scope === scope);\n  if (idx < 0) return false;\n  const conflict = wb.definedNames.findIndex((d, i) => i !== idx && d.name === newName && d.scope === scope);\n  if (conflict >= 0) {\n    throw new OpenXmlSchemaError(`renameDefinedName: \"${newName}\" is already in use at the same scope`);\n  }\n  const existing = wb.definedNames[idx];\n  if (!existing) return false;\n  wb.definedNames[idx] = { ...existing, name: newName };\n  return true;\n};\n\n/**\n * Read-only snapshot of every `_xlnm.Print_Area` defined name. Each entry is\n * the raw DefinedName carrying `scope` (sheet index) and `value` (the\n * print-area expression like `'Sheet1'!$A$1:$D$10`).\n */\nexport const listPrintAreas = (wb: Workbook): ReadonlyArray<DefinedName> =>\n  wb.definedNames.filter((d) => d.name === '_xlnm.Print_Area');\n\n/**\n * Read-only snapshot of every `_xlnm.Print_Titles` defined name. Each entry's\n * `value` is the title-row / title-col expression Excel re-uses on every\n * printed page.\n */\nexport const listPrintTitles = (wb: Workbook): ReadonlyArray<DefinedName> =>\n  wb.definedNames.filter((d) => d.name === '_xlnm.Print_Titles');\n\n/**\n * Define the print-area for a given sheet. Excel uses the built-in\n * `_xlnm.Print_Area` defined name with sheet scope.\n */\nexport const setPrintArea = (wb: Workbook, sheetIndex: number, ref: string): DefinedName => {\n  return addDefinedName(wb, {\n    name: '_xlnm.Print_Area',\n    value: ref,\n    scope: sheetIndex,\n  });\n};\n\n/**\n * Define print-title rows / columns on a sheet. Excel uses the\n * `_xlnm.Print_Titles` defined name. Pass `rows` (\"$1:$1\") to repeat row 1 on\n * every printed page; `cols` (\"$A:$A\") to repeat column A.\n */\nexport const setPrintTitles = (\n  wb: Workbook,\n  sheetIndex: number,\n  opts: { rows?: string; cols?: string; sheetName: string },\n): DefinedName => {\n  const parts: string[] = [];\n  // The wire form is \"Sheet!$1:$1,Sheet!$A:$A\"; both refs share the sheet\n  // prefix.\n  if (opts.cols !== undefined) parts.push(`'${opts.sheetName}'!${opts.cols}`);\n  if (opts.rows !== undefined) parts.push(`'${opts.sheetName}'!${opts.rows}`);\n  if (parts.length === 0) {\n    throw new OpenXmlSchemaError('setPrintTitles: at least one of rows or cols must be set');\n  }\n  return addDefinedName(wb, {\n    name: '_xlnm.Print_Titles',\n    value: parts.join(','),\n    scope: sheetIndex,\n  });\n};\n"],"mappings":";;AAwBA,SAAgB,gBAAgB,MAA2E;CACzG,OAAO;EACL,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,GAAI,KAAK,UAAU,KAAA,IAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;EACxD,GAAI,KAAK,WAAW,KAAA,IAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;EAC3D,GAAI,KAAK,YAAY,KAAA,IAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;CAChE;AACF;;;;;;;AA0BA,MAAa,kBACX,IACA,SACgB;CAChB,MAAM,KAAK,gBAAgB,IAAI;CAE/B,MAAM,MAAM,GAAG,aAAa,WAAW,MAAM,EAAE,SAAS,GAAG,QAAQ,EAAE,UAAU,GAAG,KAAK;CACvF,IAAI,OAAO,GACT,GAAG,aAAa,OAAO;MAEvB,GAAG,aAAa,KAAK,EAAE;CAEzB,OAAO;AACT;;AA2CA,MAAa,kBACX,IACA,MACA,UAC4B,GAAG,aAAa,MAAM,MAAM,EAAE,SAAS,QAAQ,EAAE,UAAU,KAAK;;;;;;;;;;;AAY9F,MAAa,wBACX,IACA,MACA,UACoC;CACpC,MAAM,KAAK,eAAe,IAAI,MAAM,KAAK;CACzC,IAAI,CAAC,IAAI,OAAO,KAAA;CAIhB,MAAM,OAAiB,CAAC;CACxB,IAAI,UAAU;CACd,IAAI,UAAU;CACd,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,MAAM,QAAQ,KAAK;EACxC,MAAM,IAAI,GAAG,MAAM;EACnB,IAAI,MAAM,KAAK;GAGb,IAAI,WAAW,GAAG,MAAM,IAAI,OAAO,KAAK;IACtC,WAAW;IACX;IACA;GACF;GACA,UAAU,CAAC;GACX,WAAW;GACX;EACF;EACA,IAAI,MAAM,OAAO,CAAC,SAAS;GACzB,KAAK,KAAK,OAAO;GACjB,UAAU;GACV;EACF;EACA,WAAW;CACb;CACA,IAAI,QAAQ,SAAS,GAAG,KAAK,KAAK,OAAO;CACzC,OAAO,KAAK,KAAK,QAAQ,gBAAgB,GAAG,CAAC;AAC/C;;;;;AAMA,MAAa,qBAAqB,IAAc,MAAc,UAA4B;CACxF,MAAM,MAAM,GAAG,aAAa,WAAW,MAAM,EAAE,SAAS,QAAQ,EAAE,UAAU,KAAK;CACjF,IAAI,MAAM,GAAG,OAAO;CACpB,GAAG,aAAa,OAAO,KAAK,CAAC;CAC7B,OAAO;AACT;;;;;;AAOA,MAAa,oBACX,IACA,OAAgD,CAAC,MAClB;CAC/B,MAAM,QAAQ,KAAK,SAAS;CAC5B,IAAI,UAAU,OAAO,OAAO,GAAG;CAC/B,IAAI,UAAU,YAAY,OAAO,GAAG,aAAa,QAAQ,MAAM,EAAE,UAAU,KAAA,CAAS;CACpF,OAAO,GAAG,aAAa,QAAQ,MAAM,EAAE,UAAU,KAAK;AACxD"}