@lucio-ai/docxtor 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["getNextRelationshipId","removeContentTypeOverride","_acceptAllRevisions","_rejectAllRevisions","_getCoreProperties","_scrubMetadata","_findParagraphById","_findParagraphByParaId","_findTableById","_validatePatch"],"sources":["../src/zip/reader.ts","../src/zip/writer.ts","../src/xml/serializer.ts","../src/xml/parser.ts","../src/xml/helpers.ts","../src/normalizer/paragraph.ts","../src/normalizer/table.ts","../src/normalizer/header-footer.ts","../src/patches/numbering.ts","../src/normalizer/para-id.ts","../src/normalizer/document.ts","../src/patches/text-segments.ts","../src/patches/validator.ts","../src/mapping/text-to-runs.ts","../src/patches/run-splitter.ts","../src/patches/revision.ts","../src/patches/markdown-parser.ts","../src/patches/text-ops.ts","../src/patches/table-ops.ts","../src/patches/format-ops.ts","../src/patches/style-ops.ts","../src/patches/field-codes.ts","../src/patches/header-footer-ops.ts","../src/patches/container-utils.ts","../src/patches/list-ops.ts","../src/patches/paragraph-ops.ts","../src/patches/part-utils.ts","../src/patches/comment-ops.ts","../src/patches/engine.ts","../src/projection/formats.ts","../src/projection/llm.ts","../src/revisions/accept-reject.ts","../src/metadata/properties.ts","../src/index.ts"],"sourcesContent":["/**\n * ZIP reader: extracts XML parts from a DOCX buffer using JSZip.\n *\n * DOCX files are ZIP archives. We extract the XML files we need to normalize\n * and keep the JSZip instance alive so we can write modifications back.\n */\n\nimport JSZip from \"jszip\";\n\n/** All the XML parts we extract from a DOCX file. */\nexport interface DocxZipEntries {\n /** The raw document.xml content. */\n documentXml: string;\n /** Header XML files keyed by filename (e.g., \"word/header1.xml\"). */\n headers: Map<string, string>;\n /** Footer XML files keyed by filename (e.g., \"word/footer1.xml\"). */\n footers: Map<string, string>;\n /** The relationships file for the main document part. */\n documentRels?: string;\n /** The content types file. */\n contentTypes?: string;\n /** The styles.xml file (may not be present). */\n stylesXml?: string;\n /** The settings.xml file (may not be present). */\n settingsXml?: string;\n /** The numbering.xml file (may not be present). */\n numberingXml?: string;\n /** The footnotes.xml file (may not be present). */\n footnotesXml?: string;\n /** The endnotes.xml file (may not be present). */\n endnotesXml?: string;\n /** The comments.xml file (may not be present). */\n commentsXml?: string;\n /** docProps/core.xml (may not be present). */\n coreXml?: string;\n /** docProps/app.xml (may not be present). */\n appXml?: string;\n /** _rels/.rels — package-level relationships (may not be present). */\n rootRels?: string;\n /** The JSZip instance (kept for writing back). */\n zip: JSZip;\n}\n\n/**\n * Read a DOCX buffer and extract all relevant XML parts.\n */\nexport async function readDocx(\n input: Buffer | ArrayBuffer | Uint8Array,\n): Promise<DocxZipEntries> {\n const zip = await JSZip.loadAsync(input);\n\n // document.xml is required\n const documentXml = await readEntry(zip, \"word/document.xml\");\n if (documentXml === undefined) {\n throw new Error(\n \"Invalid DOCX: missing word/document.xml\",\n );\n }\n\n // Read document relationships to discover headers/footers\n const documentRels = await readEntry(\n zip,\n \"word/_rels/document.xml.rels\",\n );\n const contentTypes = await readEntry(zip, \"[Content_Types].xml\");\n const stylesXml = await readEntry(zip, \"word/styles.xml\");\n const settingsXml = await readEntry(zip, \"word/settings.xml\");\n const numberingXml = await readEntry(zip, \"word/numbering.xml\");\n const footnotesXml = await readEntry(zip, \"word/footnotes.xml\");\n const endnotesXml = await readEntry(zip, \"word/endnotes.xml\");\n const commentsXml = await readEntry(zip, \"word/comments.xml\");\n const coreXml = await readEntry(zip, \"docProps/core.xml\");\n const appXml = await readEntry(zip, \"docProps/app.xml\");\n const rootRels = await readEntry(zip, \"_rels/.rels\");\n\n // Extract header and footer files\n const headers = new Map<string, string>();\n const footers = new Map<string, string>();\n\n // Scan zip for header/footer files\n for (const path of Object.keys(zip.files)) {\n if (/^word\\/header\\d+\\.xml$/i.test(path)) {\n const content = await readEntry(zip, path);\n if (content !== undefined) {\n headers.set(path, content);\n }\n } else if (/^word\\/footer\\d+\\.xml$/i.test(path)) {\n const content = await readEntry(zip, path);\n if (content !== undefined) {\n footers.set(path, content);\n }\n }\n }\n\n return {\n documentXml,\n headers,\n footers,\n documentRels,\n contentTypes,\n stylesXml,\n settingsXml,\n numberingXml,\n footnotesXml,\n endnotesXml,\n commentsXml,\n coreXml,\n appXml,\n rootRels,\n zip,\n };\n}\n\n/** Read a single zip entry as a string. Returns undefined if not found. */\nasync function readEntry(\n zip: JSZip,\n path: string,\n): Promise<string | undefined> {\n const file = zip.file(path);\n if (!file) return undefined;\n return file.async(\"string\");\n}\n","/**\n * ZIP writer: takes modified XML strings and writes them back into\n * a DOCX buffer using the original JSZip instance.\n *\n * Unmodified entries (images, styles, fonts, etc.) pass through untouched\n * because JSZip lazily decompresses — only entries we replace are reprocessed.\n */\n\nimport type JSZip from \"jszip\";\n\nexport interface WriteDocxOptions {\n /** The original JSZip instance from reading. */\n zip: JSZip;\n /** The modified document.xml content. */\n documentXml: string;\n /** Modified header XML files keyed by zip path. */\n headers?: Map<string, string>;\n /** Modified footer XML files keyed by zip path. */\n footers?: Map<string, string>;\n /** Modified document.xml.rels content. */\n documentRelsXml?: string;\n /** Modified [Content_Types].xml content. */\n contentTypesXml?: string;\n /** Modified word/settings.xml content. */\n settingsXml?: string;\n /** Modified word/numbering.xml content. */\n numberingXml?: string;\n /** Modified word/footnotes.xml content. */\n footnotesXml?: string;\n /** Modified word/endnotes.xml content. */\n endnotesXml?: string;\n /** Modified word/comments.xml content. */\n commentsXml?: string;\n /** Modified docProps/core.xml content. */\n coreXml?: string;\n /** Modified docProps/app.xml content. */\n appXml?: string;\n /** Modified _rels/.rels content. */\n rootRelsXml?: string;\n /** Zip paths to remove from the archive (e.g., deleted header/footer parts). */\n removedParts?: string[];\n}\n\n/** Fixed zip path for each optional XML part. */\nconst PART_PATHS: Array<[keyof WriteDocxOptions, string]> = [\n [\"documentRelsXml\", \"word/_rels/document.xml.rels\"],\n [\"contentTypesXml\", \"[Content_Types].xml\"],\n [\"settingsXml\", \"word/settings.xml\"],\n [\"numberingXml\", \"word/numbering.xml\"],\n [\"footnotesXml\", \"word/footnotes.xml\"],\n [\"endnotesXml\", \"word/endnotes.xml\"],\n [\"commentsXml\", \"word/comments.xml\"],\n [\"coreXml\", \"docProps/core.xml\"],\n [\"appXml\", \"docProps/app.xml\"],\n [\"rootRelsXml\", \"_rels/.rels\"],\n];\n\n/**\n * Write modified XML back into a DOCX buffer.\n *\n * Only the entries we explicitly set are recompressed.\n * Everything else (images, styles, fonts, etc.) passes through from the\n * original zip with minimal overhead.\n */\nexport async function writeDocx(\n options: WriteDocxOptions,\n): Promise<Buffer> {\n const { zip, documentXml, headers, footers, removedParts } = options;\n\n // Remove deleted parts (e.g., header/footer XML files after remove_header/remove_footer)\n if (removedParts) {\n for (const path of removedParts) {\n zip.remove(path);\n }\n }\n\n // Replace document.xml\n zip.file(\"word/document.xml\", documentXml);\n\n // Replace modified headers\n if (headers) {\n for (const [path, content] of headers) {\n zip.file(path, content);\n }\n }\n\n // Replace modified footers\n if (footers) {\n for (const [path, content] of footers) {\n zip.file(path, content);\n }\n }\n\n // Replace every other part that was supplied\n for (const [key, path] of PART_PATHS) {\n const content = options[key];\n if (typeof content === \"string\") {\n zip.file(path, content);\n }\n }\n\n // Generate the output buffer\n const buffer = await zip.generateAsync({\n type: \"nodebuffer\",\n compression: \"DEFLATE\",\n compressionOptions: { level: 6 },\n });\n\n return buffer;\n}\n","/**\n * XML serializer: converts a FXP ordered array back to an XML string.\n *\n * Uses the same options as the parser to ensure lossless roundtripping.\n * The FXP object passed here may have been mutated by the patch engine —\n * that's the whole point. We serialize the mutated state.\n */\n\nimport { XMLBuilder } from \"fast-xml-parser\";\nimport type { FXPNode } from \"../types.js\";\n\n/** Builder options must match parser options for correct roundtripping. */\nconst BUILDER_OPTIONS = {\n preserveOrder: true,\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n commentPropName: \"#comment\",\n cdataPropName: \"#cdata\",\n processEntities: false,\n // Produce well-formed XML\n format: false,\n suppressEmptyNode: false,\n suppressBooleanAttributes: false,\n};\n\nconst builder = new XMLBuilder(BUILDER_OPTIONS);\n\n/**\n * Serialize a FXP ordered array back to an XML string.\n */\nexport function serializeXml(nodes: FXPNode[]): string {\n return builder.build(nodes) as string;\n}\n","/**\n * XML parser: wraps fast-xml-parser with the correct options for\n * lossless OOXML roundtripping.\n *\n * Uses `preserveOrder: true` which produces an ordered array structure.\n * This is critical for DOCX because sibling element order matters\n * (e.g., w:pPr must come before w:r inside w:p).\n */\n\nimport { XMLParser } from \"fast-xml-parser\";\nimport type { FXPNode } from \"../types.js\";\n\n/** Parser options tuned for lossless OOXML roundtripping. */\nconst PARSER_OPTIONS = {\n preserveOrder: true,\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n // Preserve everything\n commentPropName: \"#comment\",\n cdataPropName: \"#cdata\",\n // Don't mangle values\n processEntities: false,\n trimValues: false,\n parseTagValue: false,\n parseAttributeValue: false,\n // Keep declarations\n ignoreDeclaration: false,\n ignorePiTags: false,\n};\n\nconst parser = new XMLParser(PARSER_OPTIONS);\n\n/**\n * Parse an XML string into a FXP ordered array.\n *\n * The returned structure is an array of FXPNode objects where\n * element order is preserved. This IS the mutable XML representation —\n * modifying these objects and serializing them back produces the output XML.\n */\nexport function parseXml(xml: string): FXPNode[] {\n return parser.parse(xml) as FXPNode[];\n}\n","/**\n * Utility functions for navigating fast-xml-parser's preserveOrder structure.\n *\n * With `preserveOrder: true`, FXP produces arrays of single-key objects:\n *\n * [{ \"w:p\": [...children...], \":@\": { \"@_w:rsidR\": \"00A7\" } }]\n *\n * These helpers abstract over that structure so the rest of the codebase\n * doesn't need to deal with the verbosity directly.\n */\n\nimport type { FXPNode } from \"../types.js\";\n\n// =============================================================================\n// Tag name extraction\n// =============================================================================\n\n/**\n * Get the tag name of an FXP node.\n * The tag name is the first key that isn't \":@\" (attributes) or \"#text\".\n */\nexport function getTagName(node: FXPNode): string | undefined {\n for (const key of Object.keys(node)) {\n if (key !== \":@\" && key !== \"#text\") {\n return key;\n }\n }\n return undefined;\n}\n\n/**\n * Check if a node has a specific tag name.\n */\nexport function isTag(node: FXPNode, tagName: string): boolean {\n return tagName in node;\n}\n\n/**\n * Check if a node is a text node.\n */\nexport function isTextNode(node: FXPNode): boolean {\n return \"#text\" in node;\n}\n\n// =============================================================================\n// Child navigation\n// =============================================================================\n\n/**\n * Get the children array of a node (the value under the tag name key).\n */\nexport function getChildren(node: FXPNode): FXPNode[] {\n const tagName = getTagName(node);\n if (!tagName) return [];\n const children = node[tagName];\n if (Array.isArray(children)) return children;\n return [];\n}\n\n/**\n * Find the first child with a specific tag name.\n */\nexport function findChild(\n node: FXPNode,\n tagName: string,\n): FXPNode | undefined {\n const children = getChildren(node);\n return children.find((child) => tagName in child);\n}\n\n/**\n * Find all children with a specific tag name.\n */\nexport function findChildren(\n node: FXPNode,\n tagName: string,\n): FXPNode[] {\n const children = getChildren(node);\n return children.filter((child) => tagName in child);\n}\n\n/**\n * Find the first descendant matching a path of tag names.\n * E.g., findByPath(node, [\"w:body\", \"w:p\"]) finds the first w:p inside w:body.\n */\nexport function findByPath(\n node: FXPNode,\n path: string[],\n): FXPNode | undefined {\n let current: FXPNode | undefined = node;\n for (const tag of path) {\n if (!current) return undefined;\n current = findChild(current, tag);\n }\n return current;\n}\n\n// =============================================================================\n// Attribute access\n// =============================================================================\n\n/**\n * Get an attribute value from a node.\n * Attribute names should NOT include the \"@_\" prefix — it's added automatically.\n */\nexport function getAttr(\n node: FXPNode,\n attrName: string,\n): string | undefined {\n const attrs = node[\":@\"] as Record<string, string> | undefined;\n if (!attrs) return undefined;\n return attrs[`@_${attrName}`];\n}\n\n/**\n * Set an attribute value on a node.\n */\nexport function setAttr(\n node: FXPNode,\n attrName: string,\n value: string,\n): void {\n if (!node[\":@\"]) {\n node[\":@\"] = {};\n }\n (node[\":@\"] as Record<string, string>)[`@_${attrName}`] = value;\n}\n\n/**\n * Remove an attribute from a node.\n */\nexport function removeAttr(node: FXPNode, attrName: string): void {\n const attrs = node[\":@\"] as Record<string, string> | undefined;\n if (attrs) {\n delete attrs[`@_${attrName}`];\n }\n}\n\n/**\n * Get all attributes as a plain object (without the \"@_\" prefix).\n */\nexport function getAttrs(\n node: FXPNode,\n): Record<string, string> {\n const attrs = node[\":@\"] as Record<string, string> | undefined;\n if (!attrs) return {};\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(attrs)) {\n if (key.startsWith(\"@_\")) {\n result[key.slice(2)] = value;\n }\n }\n return result;\n}\n\n// =============================================================================\n// Text access\n// =============================================================================\n\n/**\n * Escape special XML characters in text content.\n *\n * Required because fast-xml-parser with processEntities: false does NOT\n * auto-escape on build. Entities from the original XML survive as-is,\n * but any NEW text we inject must be manually escaped.\n */\nexport function escapeXmlText(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\n/**\n * Unescape XML entities in text content for display/comparison.\n *\n * Since processEntities: false keeps entities as-is during parse,\n * we unescape when extracting text for the normalized view so that\n * the paragraph text reads naturally (e.g., \"A & B\" not \"A &amp; B\").\n */\nexport function unescapeXmlText(text: string): string {\n return text\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\");\n}\n\n/**\n * Get the text content of a text node.\n */\nexport function getTextContent(node: FXPNode): string {\n if (\"#text\" in node) {\n return String(node[\"#text\"]);\n }\n return \"\";\n}\n\n/**\n * Set the text content of a text node.\n * Escapes XML special characters automatically.\n */\nexport function setTextContent(node: FXPNode, text: string): void {\n node[\"#text\"] = escapeXmlText(text);\n}\n\n/**\n * Get all text from w:t elements inside a w:r run node.\n * Unescapes XML entities so the returned text is natural.\n * Converts w:br (line break) elements to \\n characters.\n * Structural breaks — page breaks (w:br w:type=\"page\") and column\n * breaks (w:br w:type=\"column\") — are skipped (not included in text).\n */\nexport function getRunText(runNode: FXPNode): string {\n const children = getChildren(runNode);\n let text = \"\";\n for (const child of children) {\n if (\"w:t\" in child) {\n const tChildren = child[\"w:t\"];\n if (Array.isArray(tChildren)) {\n for (const tChild of tChildren) {\n if (\"#text\" in tChild) {\n text += unescapeXmlText(String(tChild[\"#text\"]));\n }\n }\n }\n } else if (\"w:br\" in child) {\n // Page breaks (w:type=\"page\") and column breaks (w:type=\"column\")\n // are structural breaks, not text — skip them\n const brType = getAttr(child, \"w:type\");\n if (!brType || brType === \"textWrapping\") {\n // Regular line break\n text += \"\\n\";\n }\n // page/column breaks produce no text\n }\n }\n return text;\n}\n\n/**\n * Find the w:t node inside a w:r run node.\n * Returns the first w:t element found.\n */\nexport function findTextNode(runNode: FXPNode): FXPNode | undefined {\n const children = getChildren(runNode);\n return children.find((child) => \"w:t\" in child);\n}\n\n/**\n * Check if a run contains a page break.\n *\n * Detects two OOXML mechanisms:\n * - `<w:br w:type=\"page\"/>` — explicit page break inserted by the user\n * - `<w:lastRenderedPageBreak/>` — rendering hint marking where Word\n * paginated the document on last save\n */\nexport function runHasPageBreak(runNode: FXPNode): boolean {\n const children = getChildren(runNode);\n for (const child of children) {\n if (\"w:br\" in child) {\n const brType = getAttr(child, \"w:type\");\n if (brType === \"page\") return true;\n }\n if (\"w:lastRenderedPageBreak\" in child) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Find the character offset within a run's text where a page break occurs.\n *\n * Walks the run's children in document order, accumulating text from w:t\n * elements. When a page break element is found (w:br w:type=\"page\" or\n * w:lastRenderedPageBreak), returns the number of text characters seen so\n * far — i.e., the break position relative to the run's own text.\n *\n * Returns -1 if no page break is found in this run.\n */\nexport function getRunPageBreakOffset(runNode: FXPNode): number {\n const children = getChildren(runNode);\n let charsSeen = 0;\n\n for (const child of children) {\n if (\"w:t\" in child) {\n const tChildren = child[\"w:t\"];\n if (Array.isArray(tChildren)) {\n for (const tChild of tChildren) {\n if (\"#text\" in tChild) {\n charsSeen += unescapeXmlText(String(tChild[\"#text\"])).length;\n }\n }\n }\n } else if (\"w:br\" in child) {\n const brType = getAttr(child, \"w:type\");\n if (!brType || brType === \"textWrapping\") {\n // Regular line break counts as one char (\\n)\n charsSeen += 1;\n } else if (brType === \"page\") {\n return charsSeen;\n }\n } else if (\"w:lastRenderedPageBreak\" in child) {\n return charsSeen;\n }\n }\n\n return -1;\n}\n\n/**\n * Check if a paragraph contains a page break in any of its runs,\n * or has the `w:pageBreakBefore` property in its `w:pPr`.\n *\n * Covers three OOXML mechanisms:\n * 1. `<w:br w:type=\"page\"/>` in a run — explicit page break\n * 2. `<w:lastRenderedPageBreak/>` in a run — Word rendering hint\n * 3. `<w:pageBreakBefore/>` in `w:pPr` — paragraph-level \"page break before\"\n */\nexport function paragraphHasPageBreak(paragraphNode: FXPNode): boolean {\n // Check paragraph property: w:pPr > w:pageBreakBefore\n const pPr = findChild(paragraphNode, \"w:pPr\");\n if (pPr) {\n const pbb = findChild(pPr, \"w:pageBreakBefore\");\n if (pbb) {\n // <w:pageBreakBefore/> means on; <w:pageBreakBefore w:val=\"0\"/> means off\n const val = getAttr(pbb, \"w:val\");\n if (val !== \"0\" && val !== \"false\") {\n return true;\n }\n }\n }\n\n // Check runs for w:br type=\"page\" or w:lastRenderedPageBreak\n return _scanForPageBreak(getChildren(paragraphNode));\n}\n\nfunction _scanForPageBreak(children: FXPNode[]): boolean {\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n if (runHasPageBreak(child)) return true;\n } else if (tag === \"w:hyperlink\" || tag === \"w:ins\") {\n if (_scanForPageBreak(getChildren(child))) return true;\n }\n }\n return false;\n}\n\n// =============================================================================\n// Node manipulation\n// =============================================================================\n\n/**\n * Deep clone an FXP node.\n * Creates a completely independent copy with no shared references.\n */\nexport function cloneNode(node: FXPNode): FXPNode {\n return JSON.parse(JSON.stringify(node)) as FXPNode;\n}\n\n/**\n * Insert a child node after a reference child in a parent's children array.\n * Returns the index where the new child was inserted.\n */\nexport function insertAfter(\n parent: FXPNode,\n refChild: FXPNode,\n newChild: FXPNode,\n): number {\n const children = getChildren(parent);\n const index = children.indexOf(refChild);\n if (index === -1) {\n throw new Error(\"Reference child not found in parent\");\n }\n children.splice(index + 1, 0, newChild);\n return index + 1;\n}\n\n/**\n * Insert a child node before a reference child in a parent's children array.\n * Returns the index where the new child was inserted.\n */\nexport function insertBefore(\n parent: FXPNode,\n refChild: FXPNode,\n newChild: FXPNode,\n): number {\n const children = getChildren(parent);\n const index = children.indexOf(refChild);\n if (index === -1) {\n throw new Error(\"Reference child not found in parent\");\n }\n children.splice(index, 0, newChild);\n return index;\n}\n\n/**\n * Remove a child node from a parent's children array.\n * Returns true if the child was found and removed.\n */\nexport function removeChild(\n parent: FXPNode,\n child: FXPNode,\n): boolean {\n const children = getChildren(parent);\n const index = children.indexOf(child);\n if (index === -1) return false;\n children.splice(index, 1);\n return true;\n}\n\n/**\n * Insert a child node into a parent's children array at a specific index.\n */\nexport function insertChildAt(\n parent: FXPNode,\n index: number,\n newChild: FXPNode,\n): void {\n const children = getChildren(parent);\n children.splice(index, 0, newChild);\n}\n\n/**\n * Create a new FXP element node.\n */\nexport function createElement(\n tagName: string,\n children: FXPNode[] = [],\n attrs?: Record<string, string>,\n): FXPNode {\n const node: FXPNode = { [tagName]: children };\n if (attrs && Object.keys(attrs).length > 0) {\n const fxpAttrs: Record<string, string> = {};\n for (const [key, value] of Object.entries(attrs)) {\n fxpAttrs[`@_${key}`] = value;\n }\n node[\":@\"] = fxpAttrs;\n }\n return node;\n}\n\n/**\n * Create a text node.\n * Escapes XML special characters automatically.\n */\nexport function createTextNode(text: string): FXPNode {\n return { \"#text\": escapeXmlText(text) } as unknown as FXPNode;\n}\n\n/**\n * Create a w:t element with text content, preserving spaces.\n */\nexport function createWt(text: string): FXPNode {\n const node: FXPNode = {\n \"w:t\": [createTextNode(text)],\n };\n // Add xml:space=\"preserve\" if text has leading/trailing whitespace\n if (needsSpacePreserve(text)) {\n setAttr(node, \"xml:space\", \"preserve\");\n }\n return node;\n}\n\n/**\n * Check if text needs xml:space=\"preserve\" on a w:t element.\n * Returns true if text has leading/trailing whitespace or double spaces.\n */\nexport function needsSpacePreserve(text: string): boolean {\n return text.length > 0 && (text !== text.trim() || text.includes(\" \"));\n}\n\n/**\n * Create a w:r run element with text and optional formatting.\n * If rPrNode is provided, it's cloned and used as the run properties.\n *\n * Handles newlines (\\n) by interleaving w:t and w:br elements,\n * matching the behavior of getRunText() which converts w:br → \\n.\n */\nexport function createRun(\n text: string,\n rPrNode?: FXPNode,\n): FXPNode {\n const children: FXPNode[] = [];\n if (rPrNode) {\n children.push(cloneNode(rPrNode));\n }\n\n if (text.includes(\"\\n\")) {\n // Multi-line: interleave w:t and w:br\n const segments = text.split(\"\\n\");\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n if (seg.length > 0 || i === 0) {\n children.push(createWt(seg));\n }\n if (i < segments.length - 1) {\n children.push({ \"w:br\": [] as FXPNode[] });\n }\n }\n } else {\n children.push(createWt(text));\n }\n\n return { \"w:r\": children };\n}\n","/**\n * Paragraph normalizer: walks a w:p FXP node and produces a\n * NormalizedParagraph with text, run mappings, and xmlRefs.\n *\n * This is the core of the mapping layer. Each RunMapping holds:\n * - The run's text content\n * - Character offset range in the concatenated paragraph text\n * - Direct reference to the w:r and w:t FXP nodes\n * - Captured formatting\n *\n * Patches use these mappings to find the right XML nodes to mutate.\n *\n * Hyperlink support: w:hyperlink elements wrap w:r runs. We collect\n * runs from both direct children and from inside w:hyperlink wrappers,\n * so hyperlink text participates in the paragraph's normalized text\n * and can be targeted by patches.\n */\n\nimport type {\n FXPNode,\n NormalizedParagraph,\n RunMapping,\n RunFormatting,\n ListInfo,\n RevisionMark,\n} from \"../types.js\";\nimport {\n findChild,\n getAttr,\n getChildren,\n getTagName,\n getRunText,\n findTextNode,\n isTag,\n unescapeXmlText,\n paragraphHasPageBreak,\n runHasPageBreak,\n getRunPageBreakOffset,\n} from \"../xml/helpers.js\";\n\n/**\n * Collect all w:r run nodes from a paragraph, including those nested\n * inside w:hyperlink and w:ins elements. Skips w:del entirely.\n * Preserves document order.\n *\n * This ensures:\n * - Inserted text (w:ins) is included in .text — subsequent patches see it\n * - Deleted text (w:del) is excluded from .text — subsequent patches don't see it\n */\nfunction collectRuns(paragraphNode: FXPNode): FXPNode[] {\n const children = getChildren(paragraphNode);\n const runs: FXPNode[] = [];\n\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n runs.push(child);\n } else if (tag === \"w:hyperlink\") {\n // Collect runs inside the hyperlink (recurse for nested w:ins within hyperlink)\n collectRunsFromContainer(child, runs);\n } else if (tag === \"w:ins\") {\n // Inserted runs are \"current content\" — recurse into them\n collectRunsFromContainer(child, runs);\n }\n // w:del is intentionally skipped — deleted text is excluded from normalized text\n }\n\n return runs;\n}\n\n/**\n * Recursively collect runs from a container element (w:hyperlink, w:ins).\n * Handles nested containers (e.g., w:ins inside w:hyperlink or vice versa).\n */\nfunction collectRunsFromContainer(containerNode: FXPNode, runs: FXPNode[]): void {\n const children = getChildren(containerNode);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n runs.push(child);\n } else if (tag === \"w:hyperlink\" || tag === \"w:ins\") {\n collectRunsFromContainer(child, runs);\n }\n // w:del inside containers is also skipped\n }\n}\n\n/**\n * Normalize a single w:p FXP node into a NormalizedParagraph.\n *\n * @param paragraphNode - The w:p FXP node (must have \"w:p\" key)\n * @param id - The stable ID to assign (e.g., \"p0\", \"tbl0.r1.c0.p0\")\n */\nexport function normalizeParagraph(\n paragraphNode: FXPNode,\n id: string,\n): NormalizedParagraph {\n const runs = collectRuns(paragraphNode);\n const runMappings: RunMapping[] = [];\n let offset = 0;\n let pageBreakOffset: number | undefined;\n\n for (const runNode of runs) {\n const text = getRunText(runNode);\n\n // Check for page break in this run before skipping empty runs.\n // A page-break-only run has text.length === 0 but still contains a break.\n if (pageBreakOffset === undefined && runHasPageBreak(runNode)) {\n const withinRun = getRunPageBreakOffset(runNode);\n if (withinRun >= 0) {\n pageBreakOffset = offset + withinRun;\n }\n }\n\n // Skip runs that contribute nothing to the text (empty w:t, page-break-only,\n // field chars, drawings). A run holding only a line break still yields \"\\n\",\n // so every w:br is one character in .text — the same text the projection\n // shows, so offsets and occurrences computed from it resolve correctly.\n if (text.length === 0) continue;\n\n const textNode = findTextNode(runNode);\n const formatting = extractFormatting(runNode);\n\n const mapping: RunMapping = {\n text,\n startOffset: offset,\n endOffset: offset + text.length,\n xmlRef: runNode,\n formatting,\n };\n if (textNode) mapping.textXmlRef = textNode;\n runMappings.push(mapping);\n\n offset += text.length;\n }\n\n const fullText = runMappings.map((r) => r.text).join(\"\");\n\n // Capture revision marks from w:ins / w:del elements\n const revisions = collectRevisionMarks(paragraphNode, runMappings);\n\n const result: NormalizedParagraph = {\n kind: \"paragraph\",\n id,\n text: fullText,\n runs: runMappings,\n xmlRef: paragraphNode,\n style: extractStyle(paragraphNode),\n listInfo: extractListInfo(paragraphNode),\n };\n\n const paraId = getAttr(paragraphNode, \"w14:paraId\");\n if (paraId) {\n result.paraId = paraId.toUpperCase();\n }\n\n if (revisions.length > 0) {\n result.revisions = revisions;\n }\n\n // Detect page breaks — check w:pageBreakBefore (always offset 0) and run-level breaks\n if (paragraphHasPageBreak(paragraphNode)) {\n result.hasPageBreak = true;\n\n // w:pageBreakBefore is a paragraph-level property — always offset 0\n const pPr = findChild(paragraphNode, \"w:pPr\");\n if (pPr) {\n const pbb = findChild(pPr, \"w:pageBreakBefore\");\n if (pbb) {\n const val = getAttr(pbb, \"w:val\");\n if (val !== \"0\" && val !== \"false\") {\n result.pageBreakOffset = 0;\n return result;\n }\n }\n }\n\n // Run-level break — use the offset computed during the run loop\n if (pageBreakOffset !== undefined) {\n result.pageBreakOffset = pageBreakOffset;\n }\n }\n\n return result;\n}\n\n/**\n * Extract formatting properties from a w:r run node's w:rPr element.\n */\nfunction extractFormatting(runNode: FXPNode): RunFormatting {\n const rPr = findChild(runNode, \"w:rPr\");\n if (!rPr) return {};\n\n const formatting: RunFormatting = {};\n const children = getChildren(rPr);\n\n for (const child of children) {\n if (isTag(child, \"w:b\")) {\n // <w:b/> means bold on; <w:b w:val=\"0\"/> means bold off\n const val = getAttr(child, \"w:val\");\n formatting.bold = val !== \"0\" && val !== \"false\";\n } else if (isTag(child, \"w:i\")) {\n const val = getAttr(child, \"w:val\");\n formatting.italic = val !== \"0\" && val !== \"false\";\n } else if (isTag(child, \"w:u\")) {\n formatting.underline = getAttr(child, \"w:val\") ?? \"single\";\n } else if (isTag(child, \"w:strike\")) {\n const val = getAttr(child, \"w:val\");\n formatting.strikethrough = val !== \"0\" && val !== \"false\";\n } else if (isTag(child, \"w:sz\")) {\n const val = getAttr(child, \"w:val\");\n if (val) formatting.fontSize = parseInt(val, 10);\n } else if (isTag(child, \"w:rFonts\")) {\n formatting.fontFamily = getAttr(child, \"w:ascii\") ?? getAttr(child, \"w:hAnsi\");\n } else if (isTag(child, \"w:color\")) {\n formatting.color = getAttr(child, \"w:val\");\n const themeColor = getAttr(child, \"w:themeColor\");\n if (themeColor) formatting.themeColor = themeColor;\n } else if (isTag(child, \"w:highlight\")) {\n formatting.highlight = getAttr(child, \"w:val\");\n }\n }\n\n return formatting;\n}\n\n/**\n * Extract the paragraph style name from w:pPr/w:pStyle.\n */\nfunction extractStyle(paragraphNode: FXPNode): string | undefined {\n const pPr = findChild(paragraphNode, \"w:pPr\");\n if (!pPr) return undefined;\n\n const pStyle = findChild(pPr, \"w:pStyle\");\n if (!pStyle) return undefined;\n\n return getAttr(pStyle, \"w:val\");\n}\n\n/**\n * Extract list/numbering info from w:pPr/w:numPr.\n */\nfunction extractListInfo(paragraphNode: FXPNode): ListInfo | undefined {\n const pPr = findChild(paragraphNode, \"w:pPr\");\n if (!pPr) return undefined;\n\n const numPr = findChild(pPr, \"w:numPr\");\n if (!numPr) return undefined;\n\n const numIdNode = findChild(numPr, \"w:numId\");\n const ilvlNode = findChild(numPr, \"w:ilvl\");\n\n const numId = numIdNode ? getAttr(numIdNode, \"w:val\") : undefined;\n const level = ilvlNode ? getAttr(ilvlNode, \"w:val\") : undefined;\n\n if (!numId) return undefined;\n\n // numId \"0\" means \"no numbering\" in OOXML (removes inherited list formatting)\n if (numId === \"0\") return undefined;\n\n return {\n numId,\n level: level ? parseInt(level, 10) : 0,\n };\n}\n\n// =============================================================================\n// Revision mark extraction\n// =============================================================================\n\n/**\n * Collect revision marks (w:ins, w:del) from a paragraph's XML.\n *\n * - For w:ins runs: startOffset/endOffset point into the .text (runs are included in normalized text)\n * - For w:del runs: startOffset === endOffset (zero-width position) since deleted text is excluded\n * from .text; the deleted content is stored in the RevisionMark.text field\n */\nfunction collectRevisionMarks(\n paragraphNode: FXPNode,\n runMappings: RunMapping[],\n): RevisionMark[] {\n const revisions: RevisionMark[] = [];\n\n // Build a set of xmlRefs for quick lookup of which runs are \"ins\" runs\n // We need to track which runs came from w:ins to annotate their offsets\n const insRunRefs = new Set<FXPNode>();\n collectInsRunRefs(paragraphNode, insRunRefs);\n\n // Track revision marks for w:ins runs\n for (const mapping of runMappings) {\n if (insRunRefs.has(mapping.xmlRef)) {\n // Find the parent w:ins element to get author/date\n const insInfo = findParentInsInfo(paragraphNode, mapping.xmlRef);\n revisions.push({\n type: \"insert\",\n text: mapping.text,\n startOffset: mapping.startOffset,\n endOffset: mapping.endOffset,\n author: insInfo?.author ?? \"\",\n date: insInfo?.date ?? \"\",\n });\n }\n }\n\n // Track revision marks for w:del (zero-width in normalized text)\n collectDelRevisionMarks(paragraphNode, runMappings, revisions);\n\n return revisions;\n}\n\n/**\n * Recursively collect xmlRef pointers for runs inside w:ins elements.\n */\nfunction collectInsRunRefs(node: FXPNode, refs: Set<FXPNode>): void {\n const children = getChildren(node);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:ins\") {\n // All runs inside this w:ins are \"inserted\"\n markAllRunsInContainer(child, refs);\n } else if (tag === \"w:hyperlink\") {\n collectInsRunRefs(child, refs);\n }\n // Don't recurse into w:del — those are excluded\n }\n}\n\nfunction markAllRunsInContainer(node: FXPNode, refs: Set<FXPNode>): void {\n const children = getChildren(node);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n refs.add(child);\n } else if (tag === \"w:hyperlink\" || tag === \"w:ins\") {\n markAllRunsInContainer(child, refs);\n }\n }\n}\n\n/**\n * Find the w:ins parent element's author and date for a given run node.\n */\nfunction findParentInsInfo(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n): { author: string; date: string } | undefined {\n return findInsInfoInChildren(getChildren(paragraphNode), runNode);\n}\n\nfunction findInsInfoInChildren(\n children: FXPNode[],\n runNode: FXPNode,\n): { author: string; date: string } | undefined {\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:ins\") {\n // Check if this w:ins contains the run\n if (containsRun(child, runNode)) {\n return {\n author: getAttr(child, \"w:author\") ?? \"\",\n date: getAttr(child, \"w:date\") ?? \"\",\n };\n }\n } else if (tag === \"w:hyperlink\") {\n const result = findInsInfoInChildren(getChildren(child), runNode);\n if (result) return result;\n }\n }\n return undefined;\n}\n\nfunction containsRun(node: FXPNode, runNode: FXPNode): boolean {\n const children = getChildren(node);\n for (const child of children) {\n if (child === runNode) return true;\n const tag = getTagName(child);\n if (tag && tag !== \"w:del\") {\n if (containsRun(child, runNode)) return true;\n }\n }\n return false;\n}\n\n/**\n * Collect deletion revision marks from w:del elements.\n *\n * For each w:del, we need to figure out its position in the normalized text.\n * Since deleted runs are excluded from .text, the position is zero-width\n * and falls at the boundary where the deletion occurred.\n *\n * This walker recurses into containers (w:hyperlink, w:ins) to find\n * w:del elements nested inside them — e.g., when a deletion occurs inside\n * a hyperlink or inside a previous insertion.\n */\nfunction collectDelRevisionMarks(\n paragraphNode: FXPNode,\n runMappings: RunMapping[],\n revisions: RevisionMark[],\n): void {\n // Build a constant-time lookup map: xmlRef → RunMapping\n // Avoids O(n²) linear scans when walking many runs\n const runMap = new Map<FXPNode, RunMapping>();\n for (const mapping of runMappings) {\n runMap.set(mapping.xmlRef, mapping);\n }\n\n walkForDeletions(getChildren(paragraphNode), runMap, revisions, 0);\n}\n\n/**\n * Recursively walk children, advancing the text offset through runs,\n * recording w:del revision marks, and recursing into containers.\n *\n * Returns the text offset after processing all children.\n */\nfunction walkForDeletions(\n children: FXPNode[],\n runMap: Map<FXPNode, RunMapping>,\n revisions: RevisionMark[],\n offset: number,\n): number {\n for (const child of children) {\n const tag = getTagName(child);\n\n if (tag === \"w:r\") {\n const mapping = runMap.get(child);\n if (mapping) {\n offset = mapping.endOffset;\n }\n } else if (tag === \"w:del\") {\n // Collect deleted text from this w:del\n const delText = getDeletedText(child);\n if (delText.length > 0) {\n revisions.push({\n type: \"delete\",\n text: delText,\n startOffset: offset,\n endOffset: offset,\n author: getAttr(child, \"w:author\") ?? \"\",\n date: getAttr(child, \"w:date\") ?? \"\",\n });\n }\n } else if (tag === \"w:hyperlink\" || tag === \"w:ins\") {\n // Recurse into containers — they may contain w:r, w:del, or further nesting\n offset = walkForDeletions(getChildren(child), runMap, revisions, offset);\n }\n }\n return offset;\n}\n\n/**\n * Extract text content from runs inside a w:del element.\n * Reads from both w:delText and w:t elements (some docs use w:t inside w:del);\n * a deleted soft line break (w:br) reads as \"\\n\", as in live text.\n */\nfunction getDeletedText(delNode: FXPNode): string {\n const children = getChildren(delNode);\n let text = \"\";\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n const runChildren = getChildren(child);\n for (const rc of runChildren) {\n if (\"w:br\" in rc) {\n const brType = getAttr(rc, \"w:type\");\n if (!brType || brType === \"textWrapping\") text += \"\\n\";\n } else if (\"w:delText\" in rc) {\n const dtChildren = rc[\"w:delText\"];\n if (Array.isArray(dtChildren)) {\n for (const dtChild of dtChildren as FXPNode[]) {\n if (\"#text\" in dtChild) {\n text += unescapeXmlText(String(dtChild[\"#text\"]));\n }\n }\n }\n } else if (\"w:t\" in rc) {\n const tChildren = rc[\"w:t\"];\n if (Array.isArray(tChildren)) {\n for (const tChild of tChildren as FXPNode[]) {\n if (\"#text\" in tChild) {\n text += unescapeXmlText(String(tChild[\"#text\"]));\n }\n }\n }\n }\n }\n }\n }\n return text;\n}\n","/**\n * Table normalizer: walks a w:tbl FXP node and produces a\n * NormalizedTable with rows, cells, and inner paragraphs.\n *\n * Tables are containers — their text lives in paragraphs inside cells.\n * We normalize the structure and delegate paragraph normalization to\n * the paragraph normalizer.\n *\n * Supports nested tables: if a table cell contains another w:tbl,\n * it is recursively normalized and included in the cell's nestedTables.\n */\n\nimport type {\n FXPNode,\n NormalizedTable,\n NormalizedTableRow,\n NormalizedTableCell,\n} from \"../types.js\";\nimport {\n findChild,\n findChildren,\n getAttr,\n} from \"../xml/helpers.js\";\nimport { normalizeParagraph } from \"./paragraph.js\";\n\n/**\n * Normalize a w:tbl FXP node into a NormalizedTable.\n *\n * @param tableNode - The w:tbl FXP node\n * @param id - The stable ID (e.g., \"tbl0\")\n */\nexport function normalizeTable(\n tableNode: FXPNode,\n id: string,\n): NormalizedTable {\n const rows: NormalizedTableRow[] = [];\n const trNodes = findChildren(tableNode, \"w:tr\");\n\n for (let rowIdx = 0; rowIdx < trNodes.length; rowIdx++) {\n const trNode = trNodes[rowIdx]!;\n const cells: NormalizedTableCell[] = [];\n const tcNodes = findChildren(trNode, \"w:tc\");\n\n for (let colIdx = 0; colIdx < tcNodes.length; colIdx++) {\n const tcNode = tcNodes[colIdx]!;\n const paragraphs = findChildren(tcNode, \"w:p\");\n const normalizedParas = paragraphs.map((pNode, pIdx) =>\n normalizeParagraph(\n pNode,\n `${id}.r${rowIdx}.c${colIdx}.p${pIdx}`,\n ),\n );\n\n // Normalize nested tables within this cell\n const nestedTblNodes = findChildren(tcNode, \"w:tbl\");\n const nestedTables = nestedTblNodes.map((tblNode, tblIdx) =>\n normalizeTable(\n tblNode,\n `${id}.r${rowIdx}.c${colIdx}.tbl${tblIdx}`,\n ),\n );\n\n cells.push({\n rowIndex: rowIdx,\n colIndex: colIdx,\n paragraphs: normalizedParas,\n nestedTables,\n xmlRef: tcNode,\n });\n }\n\n rows.push({\n index: rowIdx,\n cells,\n xmlRef: trNode,\n });\n }\n\n // Extract table style\n const tblPr = findChild(tableNode, \"w:tblPr\");\n const tblStyle = tblPr ? findChild(tblPr, \"w:tblStyle\") : undefined;\n const style = tblStyle ? getAttr(tblStyle, \"w:val\") : undefined;\n\n return {\n kind: \"table\",\n id,\n rows,\n xmlRef: tableNode,\n style,\n };\n}\n","/**\n * Header/footer normalizer: reads header/footer XML parts and\n * normalizes their paragraphs.\n *\n * Headers and footers are separate XML files (word/header1.xml, etc.)\n * with w:hdr or w:ftr as the root element. Inside, the structure is\n * the same as document.xml: paragraphs and tables.\n */\n\nimport type {\n FXPNode,\n HeaderFooter,\n NormalizedParagraph,\n NormalizedTable,\n} from \"../types.js\";\nimport { getChildren, getTagName } from \"../xml/helpers.js\";\nimport { normalizeParagraph } from \"./paragraph.js\";\nimport { normalizeTable } from \"./table.js\";\n\n/**\n * Normalize a header or footer FXP structure.\n *\n * @param fxpNodes - The parsed FXP nodes from the header/footer XML\n * @param type - \"header\" or \"footer\"\n * @param variant - \"default\", \"first\", or \"even\"\n * @param rId - The relationship ID\n */\nexport function normalizeHeaderFooter(\n fxpNodes: FXPNode[],\n type: \"header\" | \"footer\",\n variant: string,\n rId: string,\n): HeaderFooter {\n const prefix = type === \"header\" ? \"hdr\" : \"ftr\";\n\n // Find the root element (w:hdr or w:ftr)\n const rootTag = type === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const rootNode = fxpNodes.find((node) => getTagName(node) === rootTag);\n\n const paragraphs: NormalizedParagraph[] = [];\n const tables: NormalizedTable[] = [];\n\n if (rootNode) {\n const children = getChildren(rootNode);\n let paraIdx = 0;\n let tableIdx = 0;\n\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:p\") {\n paragraphs.push(\n normalizeParagraph(\n child,\n `${prefix}.${variant}.p${paraIdx}`,\n ),\n );\n paraIdx++;\n } else if (tag === \"w:tbl\") {\n tables.push(\n normalizeTable(\n child,\n `${prefix}.${variant}.tbl${tableIdx}`,\n ),\n );\n tableIdx++;\n }\n }\n }\n\n return {\n type,\n variant,\n rId,\n paragraphs,\n tables,\n xmlRef: fxpNodes,\n };\n}\n","/**\n * Numbering definition management for list support.\n *\n * Manages word/numbering.xml — creating numbering definitions for\n * bullet, ordered, and multilevel lists, and resolving list types\n * from existing definitions.\n */\n\nimport type { FXPNode, NormalizedDocument } from \"../types.js\";\nimport {\n createElement,\n findChild,\n findChildren,\n getChildren,\n getAttr,\n getTagName,\n} from \"../xml/helpers.js\";\n\n// =============================================================================\n// Ensure numbering part exists\n// =============================================================================\n\n/**\n * Ensure numbering.xml part exists in the document. Creates it if missing.\n * Also ensures content types and relationships are set up.\n * Returns the numbering FXP array.\n */\nexport function ensureNumberingPart(doc: NormalizedDocument): FXPNode[] {\n if (doc.xmlParts.numbering) {\n return doc.xmlParts.numbering;\n }\n\n // Create a minimal numbering.xml structure\n const numberingRoot = createElement(\n \"w:numbering\",\n [],\n {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n \"xmlns:r\": \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n },\n );\n\n doc.xmlParts.numbering = [numberingRoot];\n\n // Add content type override\n if (doc.xmlParts.contentTypes) {\n const typesNode = doc.xmlParts.contentTypes.find(\n (n) => getTagName(n) === \"Types\",\n );\n if (typesNode) {\n const children = getChildren(typesNode);\n // Check if override already exists\n const exists = children.some(\n (c) =>\n getTagName(c) === \"Override\" &&\n getAttr(c, \"PartName\") === \"/word/numbering.xml\",\n );\n if (!exists) {\n children.push(\n createElement(\"Override\", [], {\n PartName: \"/word/numbering.xml\",\n ContentType:\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml\",\n }),\n );\n }\n }\n }\n\n // Add relationship\n if (doc.xmlParts.documentRels) {\n const relsNode = doc.xmlParts.documentRels.find(\n (n) => getTagName(n) === \"Relationships\",\n );\n if (relsNode) {\n const rels = getChildren(relsNode);\n // Check if relationship already exists\n const exists = rels.some(\n (r) =>\n getAttr(r, \"Type\") ===\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering\",\n );\n if (!exists) {\n // Find next rId\n let maxId = 0;\n for (const r of rels) {\n const id = getAttr(r, \"Id\");\n if (id) {\n const num = parseInt(id.replace(\"rId\", \"\"), 10);\n if (!isNaN(num) && num > maxId) maxId = num;\n }\n }\n rels.push(\n createElement(\"Relationship\", [], {\n Id: `rId${maxId + 1}`,\n Type: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering\",\n Target: \"numbering.xml\",\n }),\n );\n }\n }\n }\n\n return doc.xmlParts.numbering;\n}\n\n// =============================================================================\n// ID generation\n// =============================================================================\n\n/** Scan existing w:num entries and return the next available numId. */\nexport function getNextNumId(numberingFxp: FXPNode[]): number {\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\");\n if (!root) return 1;\n\n let max = 0;\n for (const child of findChildren(root, \"w:num\")) {\n const id = getAttr(child, \"w:numId\");\n if (id) {\n const num = parseInt(id, 10);\n if (!isNaN(num) && num > max) max = num;\n }\n }\n return max + 1;\n}\n\n/** Scan existing w:abstractNum entries and return the next available abstractNumId. */\nexport function getNextAbstractNumId(numberingFxp: FXPNode[]): number {\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\");\n if (!root) return 0;\n\n let max = -1;\n for (const child of findChildren(root, \"w:abstractNum\")) {\n const id = getAttr(child, \"w:abstractNumId\");\n if (id) {\n const num = parseInt(id, 10);\n if (!isNaN(num) && num > max) max = num;\n }\n }\n return max + 1;\n}\n\n// =============================================================================\n// Create numbering definitions\n// =============================================================================\n\n/** Bullet symbols per level (cycling) with their required fonts. */\nconst BULLET_LEVELS: Array<{ char: string; font: string }> = [\n { char: \"\\uF0B7\", font: \"Symbol\" }, // Level 0: filled bullet (Symbol)\n { char: \"o\", font: \"Courier New\" }, // Level 1: open circle\n { char: \"\\uF0A7\", font: \"Wingdings\" }, // Level 2: filled square (Wingdings)\n { char: \"\\uF0B7\", font: \"Symbol\" },\n { char: \"o\", font: \"Courier New\" },\n { char: \"\\uF0A7\", font: \"Wingdings\" },\n { char: \"\\uF0B7\", font: \"Symbol\" },\n { char: \"o\", font: \"Courier New\" },\n { char: \"\\uF0A7\", font: \"Wingdings\" },\n];\n\n/** Ordered formats per level (cycling). */\nconst ORDERED_FORMATS: Array<{ fmt: string; textFn: (lvl: number) => string }> = [\n { fmt: \"decimal\", textFn: (l) => `%${l + 1}.` },\n { fmt: \"lowerLetter\", textFn: (l) => `%${l + 1}.` },\n { fmt: \"lowerRoman\", textFn: (l) => `%${l + 1}.` },\n];\n\n/**\n * Create a full numbering definition (w:abstractNum + w:num) for a list type.\n * Returns the numId string to use in w:numPr.\n */\nexport function createNumberingDefinition(\n doc: NormalizedDocument,\n listType: \"bullet\" | \"ordered\" | \"multilevel\",\n levels: number = 9,\n): string {\n const numberingFxp = ensureNumberingPart(doc);\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\")!;\n const rootChildren = getChildren(root);\n\n const abstractNumId = getNextAbstractNumId(numberingFxp);\n const numId = getNextNumId(numberingFxp);\n\n // Build level definitions\n const lvlNodes: FXPNode[] = [];\n for (let i = 0; i < levels; i++) {\n let fmt: string;\n let lvlText: string;\n\n if (listType === \"bullet\") {\n fmt = \"bullet\";\n const bulletInfo = BULLET_LEVELS[i % BULLET_LEVELS.length]!;\n lvlText = bulletInfo.char;\n\n // Add w:rPr with w:rFonts so the bullet glyph renders correctly\n const rPr = createElement(\"w:rPr\", [\n createElement(\"w:rFonts\", [], {\n \"w:ascii\": bulletInfo.font,\n \"w:hAnsi\": bulletInfo.font,\n \"w:hint\": \"default\",\n }),\n ]);\n\n lvlNodes.push(\n createElement(\"w:lvl\", [\n createElement(\"w:start\", [], { \"w:val\": \"1\" }),\n createElement(\"w:numFmt\", [], { \"w:val\": fmt }),\n createElement(\"w:lvlText\", [], { \"w:val\": lvlText }),\n createElement(\"w:lvlJc\", [], { \"w:val\": \"left\" }),\n rPr,\n ], { \"w:ilvl\": String(i) }),\n );\n continue;\n } else if (listType === \"multilevel\") {\n fmt = \"decimal\";\n // Compound: \"%1.%2.%3.\" etc.\n lvlText = Array.from({ length: i + 1 }, (_, k) => `%${k + 1}`).join(\".\") + \".\";\n } else {\n // ordered — independent counters\n const style = ORDERED_FORMATS[i % ORDERED_FORMATS.length]!;\n fmt = style.fmt;\n lvlText = style.textFn(i);\n }\n\n lvlNodes.push(\n createElement(\"w:lvl\", [\n createElement(\"w:start\", [], { \"w:val\": \"1\" }),\n createElement(\"w:numFmt\", [], { \"w:val\": fmt }),\n createElement(\"w:lvlText\", [], { \"w:val\": lvlText }),\n createElement(\"w:lvlJc\", [], { \"w:val\": \"left\" }),\n ], { \"w:ilvl\": String(i) }),\n );\n }\n\n const abstractNum = createElement(\n \"w:abstractNum\",\n lvlNodes,\n { \"w:abstractNumId\": String(abstractNumId) },\n );\n\n const num = createElement(\n \"w:num\",\n [createElement(\"w:abstractNumId\", [], { \"w:val\": String(abstractNumId) })],\n { \"w:numId\": String(numId) },\n );\n\n // Insert abstractNum before any w:num elements (OOXML ordering requirement)\n const firstNumIdx = rootChildren.findIndex((c) => getTagName(c) === \"w:num\");\n if (firstNumIdx >= 0) {\n rootChildren.splice(firstNumIdx, 0, abstractNum);\n } else {\n rootChildren.push(abstractNum);\n }\n // Now append the w:num\n rootChildren.push(num);\n\n return String(numId);\n}\n\n// =============================================================================\n// List type resolution\n// =============================================================================\n\n/**\n * Resolve list type from numId by inspecting numbering.xml definitions.\n */\nexport function resolveListType(\n numberingFxp: FXPNode[],\n numId: string,\n): \"bullet\" | \"ordered\" | \"multilevel\" | undefined {\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\");\n if (!root) return undefined;\n\n // Find the w:num with this numId\n const numNode = findChildren(root, \"w:num\").find(\n (n) => getAttr(n, \"w:numId\") === numId,\n );\n if (!numNode) return undefined;\n\n // Get the abstractNumId\n const abstractNumIdNode = findChild(numNode, \"w:abstractNumId\");\n if (!abstractNumIdNode) return undefined;\n const abstractNumId = getAttr(abstractNumIdNode, \"w:val\");\n if (!abstractNumId) return undefined;\n\n // Find the w:abstractNum\n const abstractNum = findChildren(root, \"w:abstractNum\").find(\n (n) => getAttr(n, \"w:abstractNumId\") === abstractNumId,\n );\n if (!abstractNum) return undefined;\n\n const lvls = findChildren(abstractNum, \"w:lvl\");\n\n // Check for bullet\n let hasBullet = false;\n let hasMultiPlaceholder = false;\n\n for (const lvl of lvls) {\n const numFmt = findChild(lvl, \"w:numFmt\");\n if (numFmt && getAttr(numFmt, \"w:val\") === \"bullet\") {\n hasBullet = true;\n }\n\n const lvlText = findChild(lvl, \"w:lvlText\");\n if (lvlText) {\n const pattern = getAttr(lvlText, \"w:val\") ?? \"\";\n // Count %N placeholders\n const matches = pattern.match(/%\\d+/g);\n if (matches && matches.length > 1) {\n hasMultiPlaceholder = true;\n }\n }\n }\n\n if (hasBullet) return \"bullet\";\n if (hasMultiPlaceholder) return \"multilevel\";\n return \"ordered\";\n}\n\n// =============================================================================\n// Level info for projection\n// =============================================================================\n\n/**\n * Get the w:lvlText pattern and w:numFmt for a given numId + level.\n */\nexport function getLevelInfo(\n numberingFxp: FXPNode[],\n numId: string,\n level: number,\n): {\n pattern: string;\n format: string;\n ancestorFormats: Map<number, string>;\n} | null {\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\");\n if (!root) return null;\n\n // Find w:num → abstractNumId\n const numNode = findChildren(root, \"w:num\").find(\n (n) => getAttr(n, \"w:numId\") === numId,\n );\n if (!numNode) return null;\n\n const abstractNumIdNode = findChild(numNode, \"w:abstractNumId\");\n if (!abstractNumIdNode) return null;\n const abstractNumId = getAttr(abstractNumIdNode, \"w:val\");\n if (!abstractNumId) return null;\n\n const abstractNum = findChildren(root, \"w:abstractNum\").find(\n (n) => getAttr(n, \"w:abstractNumId\") === abstractNumId,\n );\n if (!abstractNum) return null;\n\n const lvls = findChildren(abstractNum, \"w:lvl\");\n\n // Find the target level\n const targetLvl = lvls.find(\n (l) => getAttr(l, \"w:ilvl\") === String(level),\n );\n if (!targetLvl) return null;\n\n const fmtNode = findChild(targetLvl, \"w:numFmt\");\n const textNode = findChild(targetLvl, \"w:lvlText\");\n\n const format = fmtNode ? (getAttr(fmtNode, \"w:val\") ?? \"decimal\") : \"decimal\";\n const pattern = textNode ? (getAttr(textNode, \"w:val\") ?? \"\") : \"\";\n\n // Build ancestor formats map\n const ancestorFormats = new Map<number, string>();\n const placeholders = pattern.match(/%(\\d+)/g);\n if (placeholders) {\n for (const ph of placeholders) {\n const lvlIdx = parseInt(ph.slice(1), 10) - 1; // %1 → level 0\n const ancestorLvl = lvls.find(\n (l) => getAttr(l, \"w:ilvl\") === String(lvlIdx),\n );\n if (ancestorLvl) {\n const ancestorFmt = findChild(ancestorLvl, \"w:numFmt\");\n ancestorFormats.set(\n lvlIdx,\n ancestorFmt ? (getAttr(ancestorFmt, \"w:val\") ?? \"decimal\") : \"decimal\",\n );\n }\n }\n }\n\n return { pattern, format, ancestorFormats };\n}\n","/**\n * Native paragraph identity: `w14:paraId`.\n *\n * Word 2010+ stamps every `w:p` with a `w14:paraId` attribute — 8 uppercase\n * hex characters in the range (0, 0x80000000) — that survives round trips\n * through Word. Positional ids (`p12`) shift whenever a paragraph is inserted\n * or deleted; paraIds do not. This module:\n *\n * - generates fresh, document-unique paraIds\n * - stamps missing ids on load (`assignParaIds: true`)\n * - stamps new paragraphs created by patches, provided the part they live in\n * declares the `w14` namespace (otherwise Word would reject the attribute)\n */\n\nimport type { FXPNode, NormalizedDocument, DocxXmlParts } from \"../types.js\";\nimport { getAttr, setAttr, getChildren, getTagName } from \"../xml/helpers.js\";\n\nexport const W14_NAMESPACE = \"http://schemas.microsoft.com/office/word/2010/wordml\";\nconst MC_NAMESPACE = \"http://schemas.openxmlformats.org/markup-compatibility/2006\";\n\n/** Root element tags whose paragraphs may carry paraIds. */\nconst PART_ROOT_TAGS = new Set([\"w:document\", \"w:hdr\", \"w:ftr\", \"w:footnotes\", \"w:endnotes\", \"w:comments\"]);\n\n// =============================================================================\n// Registry of used ids (per xmlParts object, so a cloned document gets its own)\n// =============================================================================\n\nconst registry = new WeakMap<DocxXmlParts, Set<string>>();\n\n/**\n * Get (building lazily) the set of every paraId currently used in the document.\n */\nfunction getUsedParaIds(doc: NormalizedDocument): Set<string> {\n let used = registry.get(doc.xmlParts);\n if (!used) {\n used = new Set<string>();\n for (const nodes of iterateParts(doc.xmlParts)) {\n collectParaIds(nodes, used);\n }\n registry.set(doc.xmlParts, used);\n }\n return used;\n}\n\nfunction* iterateParts(xmlParts: DocxXmlParts): Generator<FXPNode[]> {\n for (const value of Object.values(xmlParts)) {\n if (Array.isArray(value)) {\n yield value;\n } else if (value instanceof Map) {\n for (const nodes of value.values()) yield nodes;\n }\n }\n}\n\nfunction collectParaIds(nodes: FXPNode[], into: Set<string>): void {\n for (const node of nodes) {\n const tag = getTagName(node);\n if (!tag) continue;\n if (tag === \"w:p\") {\n const id = getAttr(node, \"w14:paraId\");\n if (id) into.add(id.toUpperCase());\n }\n const children = getChildren(node);\n if (children.length > 0) collectParaIds(children, into);\n }\n}\n\n// =============================================================================\n// Generation\n// =============================================================================\n\n/**\n * Generate a fresh paraId not present in `used`, and record it.\n * Word accepts values in (0, 0x80000000); we emit 8 uppercase hex chars.\n */\nexport function generateParaId(used: Set<string>): string {\n for (;;) {\n const value = Math.floor(Math.random() * 0x7ffffffe) + 1;\n const id = value.toString(16).toUpperCase().padStart(8, \"0\");\n if (!used.has(id)) {\n used.add(id);\n return id;\n }\n }\n}\n\n/** Validate the shape of a paraId (8 hex chars, < 0x80000000, non-zero). */\nexport function isValidParaId(id: string): boolean {\n if (!/^[0-9A-Fa-f]{8}$/.test(id)) return false;\n const n = parseInt(id, 16);\n return n > 0 && n < 0x80000000;\n}\n\n// =============================================================================\n// Namespace handling\n// =============================================================================\n\n/** Whether a part root declares the `w14` namespace prefix. */\nexport function partDeclaresW14(rootNode: FXPNode | undefined): boolean {\n if (!rootNode) return false;\n return getAttr(rootNode, \"xmlns:w14\") !== undefined;\n}\n\n/**\n * Declare `xmlns:w14` and add `w14` to `mc:Ignorable` on a part root\n * (`w:document`, `w:hdr`, `w:ftr`). Idempotent.\n */\nexport function ensureW14Namespace(rootNode: FXPNode): void {\n if (getAttr(rootNode, \"xmlns:w14\") === undefined) {\n setAttr(rootNode, \"xmlns:w14\", W14_NAMESPACE);\n }\n if (getAttr(rootNode, \"xmlns:mc\") === undefined) {\n setAttr(rootNode, \"xmlns:mc\", MC_NAMESPACE);\n }\n const ignorable = getAttr(rootNode, \"mc:Ignorable\");\n if (ignorable === undefined) {\n setAttr(rootNode, \"mc:Ignorable\", \"w14\");\n } else {\n const prefixes = ignorable.split(/\\s+/).filter((p) => p.length > 0);\n if (!prefixes.includes(\"w14\")) {\n prefixes.push(\"w14\");\n setAttr(rootNode, \"mc:Ignorable\", prefixes.join(\" \"));\n }\n }\n}\n\n/** Find the root element node of a part (the first node with a known root tag). */\nexport function findPartRoot(nodes: FXPNode[]): FXPNode | undefined {\n return nodes.find((n) => {\n const tag = getTagName(n);\n return tag !== undefined && PART_ROOT_TAGS.has(tag);\n });\n}\n\n// =============================================================================\n// Stamping\n// =============================================================================\n\n/**\n * Stamp every `w:p` without a `w14:paraId` in the given part, declaring the\n * namespace on the root if needed. Used by `Docxtor.load(..., { assignParaIds: true })`.\n *\n * @returns number of paragraphs stamped\n */\nexport function assignMissingParaIds(\n partNodes: FXPNode[],\n used: Set<string>,\n): number {\n const root = findPartRoot(partNodes);\n if (!root) return 0;\n\n let stamped = 0;\n const visit = (nodes: FXPNode[]): void => {\n for (const node of nodes) {\n const tag = getTagName(node);\n if (!tag) continue;\n if (tag === \"w:p\") {\n const existing = getAttr(node, \"w14:paraId\");\n if (!existing || !isValidParaId(existing)) {\n const id = generateParaId(used);\n setAttr(node, \"w14:paraId\", id);\n stamped++;\n if (getAttr(node, \"w14:textId\") === undefined) {\n setAttr(node, \"w14:textId\", id);\n }\n } else if (getAttr(node, \"w14:textId\") === undefined) {\n setAttr(node, \"w14:textId\", existing.toUpperCase());\n }\n }\n const children = getChildren(node);\n if (children.length > 0) visit(children);\n }\n };\n\n // Count first so we only touch the namespace if there's something to stamp\n visit(getChildren(root));\n if (stamped > 0) ensureW14Namespace(root);\n return stamped;\n}\n\n/**\n * Assign paraIds across every part of a freshly parsed document.\n * Builds the registry so later patches generate collision-free ids.\n */\nexport function assignParaIdsToDocument(xmlParts: DocxXmlParts): void {\n const used = new Set<string>();\n for (const nodes of iterateParts(xmlParts)) collectParaIds(nodes, used);\n\n // Only the content parts get stamped (document, headers, footers).\n // Footnotes/endnotes/comments are left alone — Word stamps those itself.\n assignMissingParaIds(xmlParts.document, used);\n for (const nodes of xmlParts.headers.values()) assignMissingParaIds(nodes, used);\n for (const nodes of xmlParts.footers.values()) assignMissingParaIds(nodes, used);\n\n registry.set(xmlParts, used);\n}\n\n/**\n * Stamp fresh paraIds on new paragraph nodes created by a patch.\n *\n * Walks `nodes` recursively (so table cells inside a new `w:tbl` are covered).\n * Does nothing unless the part root declares `w14` — Word would otherwise\n * refuse to open the file. With `overwrite`, existing ids are replaced too\n * (used after cloning a table row, which would otherwise duplicate ids).\n */\nexport function stampNewParagraphs(\n doc: NormalizedDocument,\n nodes: FXPNode[],\n partRoot: FXPNode | undefined,\n overwrite: boolean = false,\n): void {\n if (!partDeclaresW14(partRoot)) return;\n const used = getUsedParaIds(doc);\n\n const visit = (list: FXPNode[]): void => {\n for (const node of list) {\n const tag = getTagName(node);\n if (!tag) continue;\n if (tag === \"w:p\" && (overwrite || getAttr(node, \"w14:paraId\") === undefined)) {\n const id = generateParaId(used);\n setAttr(node, \"w14:paraId\", id);\n setAttr(node, \"w14:textId\", id);\n }\n const children = getChildren(node);\n if (children.length > 0) visit(children);\n }\n };\n visit(nodes);\n}\n\n/**\n * Resolve the part root node that contains a normalized element, judged by\n * its positional id prefix (`hdr.` / `ftr.` → that header/footer, else body).\n */\nexport function findPartRootForId(doc: NormalizedDocument, id: string): FXPNode | undefined {\n const m = id.match(/^(hdr|ftr)\\.([^.]+)\\./);\n if (m) {\n const map = m[1] === \"hdr\" ? doc.headers : doc.footers;\n const rootTag = m[1] === \"hdr\" ? \"w:hdr\" : \"w:ftr\";\n for (const hf of map.values()) {\n if (hf.variant === m[2]) {\n return hf.xmlRef.find((n) => getTagName(n) === rootTag);\n }\n }\n return undefined;\n }\n return doc.xmlParts.document.find((n) => getTagName(n) === \"w:document\");\n}\n","/**\n * Document normalizer: orchestrates the full normalization pipeline.\n *\n * Takes the raw XML parts from the ZIP reader, parses them with\n * fast-xml-parser, and builds the full NormalizedDocument with\n * paragraphs, tables, headers, and footers — all holding xmlRef\n * pointers back to the mutable FXP objects.\n */\n\nimport type {\n FXPNode,\n NormalizedDocument,\n BodyElement,\n HeaderFooter,\n DocxXmlParts,\n LoadOptions,\n StyleInfo,\n NormalizedPageBreak,\n NormalizedParagraph,\n} from \"../types.js\";\nimport type { DocxZipEntries } from \"../zip/reader.js\";\nimport { parseXml } from \"../xml/parser.js\";\nimport {\n findChild,\n findChildren,\n getChildren,\n getTagName,\n getAttr,\n} from \"../xml/helpers.js\";\nimport { normalizeParagraph } from \"./paragraph.js\";\nimport { normalizeTable } from \"./table.js\";\nimport { normalizeHeaderFooter } from \"./header-footer.js\";\nimport { resolveListType } from \"../patches/numbering.js\";\nimport { assignParaIdsToDocument } from \"./para-id.js\";\n\n/**\n * Normalize a full DOCX document from zip entries.\n *\n * Returns a NormalizedDocument where every element holds xmlRef pointers\n * into the FXP objects. The FXP objects are the mutable XML — patches\n * mutate them, and serializing them produces the output document.\n */\nexport function normalizeDocument(\n zipEntries: DocxZipEntries,\n options?: LoadOptions,\n): NormalizedDocument {\n // Parse all XML parts\n const documentFxp = parseXml(zipEntries.documentXml);\n\n const headersFxp = new Map<string, FXPNode[]>();\n for (const [path, xml] of zipEntries.headers) {\n headersFxp.set(path, parseXml(xml));\n }\n\n const footersFxp = new Map<string, FXPNode[]>();\n for (const [path, xml] of zipEntries.footers) {\n footersFxp.set(path, parseXml(xml));\n }\n\n // Parse styles.xml if present\n const styles = zipEntries.stylesXml\n ? parseStyles(zipEntries.stylesXml)\n : [];\n\n const parseOptional = (xml: string | undefined): FXPNode[] | undefined =>\n xml ? parseXml(xml) : undefined;\n\n const xmlParts: DocxXmlParts = {\n document: documentFxp,\n headers: headersFxp,\n footers: footersFxp,\n documentRels: parseOptional(zipEntries.documentRels),\n contentTypes: parseOptional(zipEntries.contentTypes),\n settings: parseOptional(zipEntries.settingsXml),\n numbering: parseOptional(zipEntries.numberingXml),\n footnotes: parseOptional(zipEntries.footnotesXml),\n endnotes: parseOptional(zipEntries.endnotesXml),\n comments: parseOptional(zipEntries.commentsXml),\n core: parseOptional(zipEntries.coreXml),\n app: parseOptional(zipEntries.appXml),\n rootRels: parseOptional(zipEntries.rootRels),\n };\n\n // Stamp native paragraph ids before normalizing so they show up in the AST\n if (options?.assignParaIds) {\n assignParaIdsToDocument(xmlParts);\n }\n\n const doc: NormalizedDocument = {\n body: [],\n headers: new Map(),\n footers: new Map(),\n xmlParts,\n styles,\n loadOptions: options,\n };\n\n reNormalizeDocument(doc);\n\n return doc;\n}\n\n/**\n * Rebuild the normalized view (body, headers, footers, list types) of a\n * document from its current XML parts, in place.\n *\n * Call this after a structural change that invalidates positional ids\n * across the whole document — e.g., accepting/rejecting all revisions.\n * `paraId`s are read from the XML so they are unaffected.\n */\nexport function reNormalizeDocument(doc: NormalizedDocument): void {\n const includeHeadersFooters = doc.loadOptions?.includeHeadersFooters ?? true;\n\n doc.body = normalizeBody(doc.xmlParts.document);\n doc.headers = new Map<string, HeaderFooter>();\n doc.footers = new Map<string, HeaderFooter>();\n\n if (includeHeadersFooters) {\n const relMap = parseRelationshipsFxp(doc.xmlParts.documentRels);\n const sectPrRefs = findHeaderFooterRefs(doc.xmlParts.document);\n\n for (const ref of sectPrRefs) {\n const target = relMap.get(ref.rId);\n if (!target) continue;\n\n const fullPath = `word/${target}`;\n\n if (ref.type === \"header\") {\n const fxp = doc.xmlParts.headers.get(fullPath);\n if (fxp) {\n doc.headers.set(ref.rId, normalizeHeaderFooter(fxp, \"header\", ref.variant, ref.rId));\n }\n } else {\n const fxp = doc.xmlParts.footers.get(fullPath);\n if (fxp) {\n doc.footers.set(ref.rId, normalizeHeaderFooter(fxp, \"footer\", ref.variant, ref.rId));\n }\n }\n }\n }\n\n // Post-pass: enrich listType on all paragraphs with listInfo\n enrichAllListTypes(doc);\n}\n\n/**\n * Normalize the document body (w:document > w:body children).\n */\nfunction normalizeBody(documentFxp: FXPNode[]): BodyElement[] {\n const elements: BodyElement[] = [];\n\n // Find w:document > w:body\n const docNode = documentFxp.find((n) => getTagName(n) === \"w:document\");\n if (!docNode) return elements;\n\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) return elements;\n\n const bodyChildren = getChildren(bodyNode);\n let paraIdx = 0;\n let tableIdx = 0;\n let pageBreakIdx = 0;\n\n for (const child of bodyChildren) {\n const tag = getTagName(child);\n if (tag === \"w:p\") {\n // Normalize once and reuse the result for both the page-break-only\n // check and the final elements.push — avoids duplicating the full\n // run mapping + revision scanning work.\n const normalized = normalizeParagraph(child, `p${paraIdx}`);\n\n if (normalized.hasPageBreak && normalized.text.trim().length === 0) {\n // Page-break-only paragraph — emit as NormalizedPageBreak\n const pb: NormalizedPageBreak = {\n kind: \"page_break\",\n id: `pb${pageBreakIdx}`,\n xmlRef: child,\n };\n elements.push(pb);\n pageBreakIdx++;\n // Don't increment paraIdx — this isn't a paragraph in the projection\n continue;\n }\n\n elements.push(normalized);\n paraIdx++;\n } else if (tag === \"w:tbl\") {\n elements.push(normalizeTable(child, `tbl${tableIdx}`));\n tableIdx++;\n }\n // Other elements (w:sectPr, etc.) are ignored in the normalized view\n // but preserved in the FXP object\n }\n\n return elements;\n}\n\n/**\n * Build a relationship map (rId → target filename) from parsed\n * document.xml.rels nodes.\n */\nfunction parseRelationshipsFxp(\n parsed: FXPNode[] | undefined,\n): Map<string, string> {\n const map = new Map<string, string>();\n if (!parsed) return map;\n\n // Find the Relationships root element\n const relsNode = parsed.find(\n (n) => getTagName(n) === \"Relationships\",\n );\n if (!relsNode) return map;\n\n const rels = findChildren(relsNode, \"Relationship\");\n for (const rel of rels) {\n const id = getAttr(rel, \"Id\");\n const target = getAttr(rel, \"Target\");\n if (id && target) {\n map.set(id, target);\n }\n }\n\n return map;\n}\n\nexport interface HeaderFooterRef {\n type: \"header\" | \"footer\";\n variant: string; // \"default\", \"first\", \"even\"\n rId: string;\n}\n\n/**\n * Find header/footer references in w:sectPr elements.\n */\nexport function findHeaderFooterRefs(documentFxp: FXPNode[]): HeaderFooterRef[] {\n const refs: HeaderFooterRef[] = [];\n\n const docNode = documentFxp.find((n) => getTagName(n) === \"w:document\");\n if (!docNode) return refs;\n\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) return refs;\n\n // sectPr can be a child of w:body (last section) or w:pPr (section break)\n const sectPrs = findChildren(bodyNode, \"w:sectPr\");\n\n // Also check the last paragraph for embedded sectPr\n const bodyChildren = getChildren(bodyNode);\n for (const child of bodyChildren) {\n if (getTagName(child) === \"w:p\") {\n const pPr = findChild(child, \"w:pPr\");\n if (pPr) {\n const sectPr = findChild(pPr, \"w:sectPr\");\n if (sectPr) sectPrs.push(sectPr);\n }\n }\n }\n\n for (const sectPr of sectPrs) {\n const children = getChildren(sectPr);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:headerReference\") {\n const rId = getAttr(child, \"r:id\");\n const variant = getAttr(child, \"w:type\") ?? \"default\";\n if (rId) refs.push({ type: \"header\", variant, rId });\n } else if (tag === \"w:footerReference\") {\n const rId = getAttr(child, \"r:id\");\n const variant = getAttr(child, \"w:type\") ?? \"default\";\n if (rId) refs.push({ type: \"footer\", variant, rId });\n }\n }\n }\n\n return refs;\n}\n\n// =============================================================================\n// Styles\n// =============================================================================\n\n/**\n * Parse word/styles.xml and extract style definitions.\n *\n * We only extract the style ID, human-readable name, and type.\n * We never modify styles.xml — this is read-only metadata used for\n * validation and projection.\n */\nfunction parseStyles(stylesXml: string): StyleInfo[] {\n const parsed = parseXml(stylesXml);\n const styles: StyleInfo[] = [];\n\n // Find w:styles root\n const stylesNode = parsed.find((n) => getTagName(n) === \"w:styles\");\n if (!stylesNode) return styles;\n\n const styleNodes = findChildren(stylesNode, \"w:style\");\n for (const styleNode of styleNodes) {\n const id = getAttr(styleNode, \"w:styleId\");\n const type = getAttr(styleNode, \"w:type\") as StyleInfo[\"type\"] | undefined;\n\n if (!id || !type) continue;\n\n // Valid types: paragraph, character, table, numbering\n if (![\"paragraph\", \"character\", \"table\", \"numbering\"].includes(type)) continue;\n\n // Get the human-readable name from w:name\n const nameNode = findChild(styleNode, \"w:name\");\n const name = nameNode ? (getAttr(nameNode, \"w:val\") ?? id) : id;\n\n styles.push({ id, name, type });\n }\n\n return styles;\n}\n\n// =============================================================================\n// List type enrichment\n// =============================================================================\n\n/**\n * Enrich listType on all paragraphs with listInfo across the entire document.\n */\nfunction enrichAllListTypes(doc: NormalizedDocument): void {\n const numberingFxp = doc.xmlParts.numbering;\n if (!numberingFxp) return;\n\n for (const el of doc.body) {\n if (el.kind === \"paragraph\") {\n enrichListType(el, numberingFxp);\n } else if (el.kind === \"table\") {\n for (const row of el.rows) {\n for (const cell of row.cells) {\n for (const para of cell.paragraphs) {\n enrichListType(para, numberingFxp);\n }\n }\n }\n }\n }\n\n for (const hdr of doc.headers.values()) {\n for (const para of hdr.paragraphs) {\n enrichListType(para, numberingFxp);\n }\n }\n\n for (const ftr of doc.footers.values()) {\n for (const para of ftr.paragraphs) {\n enrichListType(para, numberingFxp);\n }\n }\n}\n\n/**\n * Enrich a single paragraph's listInfo with the resolved listType.\n * Exported for use by the patch engine after re-normalization.\n */\nexport function enrichListType(para: NormalizedParagraph, numberingFxp: FXPNode[]): void {\n if (!para.listInfo) return;\n const lt = resolveListType(numberingFxp, para.listInfo.numId);\n if (lt) {\n para.listInfo.listType = lt;\n }\n}\n","/**\n * Block-level markdown segmentation for multi-line patch text.\n *\n * Splits patch text (`append_text`, `insert_paragraph` validation) into\n * plain text lines, markdown tables, and markdown lists.\n */\n\n/** A contiguous block of text lines, a markdown table, or a markdown list. */\nexport type TextSegment =\n | { kind: \"text\"; lines: string[] }\n | { kind: \"table\"; headers: string[]; rows: string[][] }\n | { kind: \"list\"; listType: \"bullet\" | \"ordered\"; items: { text: string; level: number }[] };\n\n/**\n * Parse text into segments of plain lines, markdown tables, and markdown lists.\n *\n * A markdown table is detected as:\n * - A header row: `| col1 | col2 | ...`\n * - A separator row: `| --- | --- | ...` (dashes, colons, spaces)\n * - Zero or more data rows: `| val1 | val2 | ...`\n *\n * A markdown list is detected as consecutive lines starting with:\n * - Bullet: `- `, `* `, `+ ` (after optional whitespace)\n * - Ordered: `\\d+\\. ` (after optional whitespace)\n * Nesting uses 2-space increments. Blank lines within a list don't break it.\n */\nexport function parseTextSegments(text: string): TextSegment[] {\n const lines = text.split(\"\\n\");\n const segments: TextSegment[] = [];\n let i = 0;\n\n while (i < lines.length) {\n // Try to detect a markdown table starting at line i\n if (\n i + 1 < lines.length &&\n isMarkdownTableRow(lines[i]!) &&\n isMarkdownSeparator(lines[i + 1]!)\n ) {\n const headerCells = parseMarkdownRow(lines[i]!);\n // Skip header and separator\n i += 2;\n const dataRows: string[][] = [];\n while (i < lines.length && isMarkdownTableRow(lines[i]!)) {\n const cells = parseMarkdownRow(lines[i]!);\n // Pad or trim to match header count\n while (cells.length < headerCells.length) cells.push(\"\");\n if (cells.length > headerCells.length)\n cells.length = headerCells.length;\n dataRows.push(cells);\n i++;\n }\n segments.push({ kind: \"table\", headers: headerCells, rows: dataRows });\n continue;\n }\n\n // Try to detect a markdown list\n const listMatch = parseListLine(lines[i]!);\n if (listMatch) {\n const items: { text: string; level: number }[] = [];\n const listType = listMatch.listType;\n items.push({ text: listMatch.text, level: listMatch.level });\n i++;\n\n while (i < lines.length) {\n const line = lines[i]!;\n // Blank lines don't break the list\n if (line.trim().length === 0) {\n i++;\n continue;\n }\n const nextMatch = parseListLine(line);\n if (!nextMatch) break;\n // Type change breaks the list\n if (nextMatch.listType !== listType) break;\n\n items.push({ text: nextMatch.text, level: nextMatch.level });\n i++;\n }\n\n segments.push({ kind: \"list\", listType, items });\n continue;\n }\n\n // Plain text line\n const last = segments[segments.length - 1];\n if (last && last.kind === \"text\") {\n last.lines.push(lines[i]!);\n } else {\n segments.push({ kind: \"text\", lines: [lines[i]!] });\n }\n i++;\n }\n\n return segments;\n}\n\n/** True if the text contains a markdown table or list block. */\nexport function hasBlockMarkdown(text: string): boolean {\n return parseTextSegments(text).some((s) => s.kind !== \"text\");\n}\n\n/** Bullet pattern: optional whitespace + `- `, `* `, or `+ ` */\nconst BULLET_RE = /^(\\s*)[*+-]\\s(.*)$/;\n/** Ordered pattern: optional whitespace + `N. ` */\nconst ORDERED_RE = /^(\\s*)\\d+\\.\\s(.*)$/;\n\nfunction parseListLine(\n line: string,\n): { listType: \"bullet\" | \"ordered\"; text: string; level: number } | undefined {\n // Normalize tabs to 2 spaces\n const normalized = line.replace(/\\t/g, \" \");\n let m = BULLET_RE.exec(normalized);\n if (m) {\n const indent = m[1]!.length;\n return { listType: \"bullet\", text: m[2]!, level: Math.floor(indent / 2) };\n }\n m = ORDERED_RE.exec(normalized);\n if (m) {\n const indent = m[1]!.length;\n return { listType: \"ordered\", text: m[2]!, level: Math.floor(indent / 2) };\n }\n return undefined;\n}\n\nfunction isMarkdownTableRow(line: string): boolean {\n const trimmed = line.trim();\n return trimmed.startsWith(\"|\") && trimmed.endsWith(\"|\") && trimmed.length > 1;\n}\n\nfunction isMarkdownSeparator(line: string): boolean {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"|\") || !trimmed.endsWith(\"|\")) return false;\n // Each cell should be dashes (with optional colons for alignment)\n const cells = trimmed.slice(1, -1).split(\"|\");\n return cells.every((cell) => /^\\s*:?-+:?\\s*$/.test(cell));\n}\n\nfunction parseMarkdownRow(line: string): string[] {\n const trimmed = line.trim();\n // Remove leading/trailing pipes and split\n const inner = trimmed.startsWith(\"|\") ? trimmed.slice(1) : trimmed;\n const content = inner.endsWith(\"|\") ? inner.slice(0, -1) : inner;\n return content.split(\"|\").map((cell) => cell.trim());\n}\n","/**\n * Patch validator: validates patch structure before execution.\n */\n\nimport type {\n Patch,\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n BodyElement,\n} from \"../types.js\";\nimport { getChildren, getTagName, getAttr } from \"../xml/helpers.js\";\nimport { hasBlockMarkdown } from \"./text-segments.js\";\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/** Prefix for native paragraph identity targets: `para:4F2A1C7B`. */\nexport const PARA_ID_PREFIX = \"para:\";\n\n/**\n * Validate a single patch against the document.\n */\nexport function validatePatch(\n patch: Patch,\n doc: NormalizedDocument,\n): ValidationResult {\n switch (patch.type) {\n case \"insert_paragraph\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!patch.text || patch.text.length === 0) {\n return { valid: false, error: \"Empty paragraph text\" };\n }\n if (![\"before\", \"after\"].includes(patch.position)) {\n return { valid: false, error: `Invalid position: ${patch.position}` };\n }\n if (hasBlockMarkdown(patch.text)) {\n return {\n valid: false,\n error:\n \"Markdown tables and lists are not supported in insert_paragraph; use add_table or add_list with afterId\",\n };\n }\n return { valid: true };\n }\n case \"delete_paragraph\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n return { valid: true };\n }\n case \"add_comment\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!patch.text || patch.text.length === 0) {\n return { valid: false, error: \"Empty comment text\" };\n }\n if (patch.find !== undefined && patch.find.length === 0) {\n return { valid: false, error: \"Empty find string\" };\n }\n if (patch.find !== undefined && patch.occurrence !== undefined) {\n if (patch.occurrence < 1 || !Number.isInteger(patch.occurrence)) {\n return {\n valid: false,\n error: `Invalid occurrence: ${patch.occurrence} (must be a positive integer)`,\n };\n }\n }\n return { valid: true };\n }\n case \"replace_text\":\n case \"insert_text\":\n case \"delete_text\":\n case \"replace_cell_text\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return {\n valid: false,\n error: `Paragraph \"${patch.target}\" not found`,\n };\n }\n if (\"find\" in patch && patch.find.length === 0) {\n return { valid: false, error: \"Empty find string\" };\n }\n if (patch.type === \"insert_text\" && patch.text.length === 0) {\n return { valid: false, error: \"Empty insert text\" };\n }\n if (\"occurrence\" in patch && patch.occurrence !== undefined) {\n if (patch.occurrence < 1 || !Number.isInteger(patch.occurrence)) {\n return {\n valid: false,\n error: `Invalid occurrence: ${patch.occurrence} (must be a positive integer)`,\n };\n }\n }\n return { valid: true };\n }\n case \"add_table_row\": {\n const table = findTableById(patch.target, doc);\n if (!table) {\n return {\n valid: false,\n error: `Table \"${patch.target}\" not found`,\n };\n }\n if (patch.cells.length === 0) {\n return { valid: false, error: \"Empty cells array\" };\n }\n if (patch.afterRow < -1 || patch.afterRow >= table.rows.length) {\n return {\n valid: false,\n error: `Invalid afterRow ${patch.afterRow} for table \"${patch.target}\" with ${table.rows.length} rows`,\n };\n }\n return { valid: true };\n }\n case \"delete_table_row\": {\n const table = findTableById(patch.target, doc);\n if (!table) {\n return {\n valid: false,\n error: `Table \"${patch.target}\" not found`,\n };\n }\n if (patch.row < 0 || patch.row >= table.rows.length) {\n return {\n valid: false,\n error: `Invalid row index ${patch.row} for table \"${patch.target}\" with ${table.rows.length} rows`,\n };\n }\n return { valid: true };\n }\n case \"apply_formatting\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return {\n valid: false,\n error: `Paragraph \"${patch.target}\" not found`,\n };\n }\n if (patch.find !== undefined && patch.find.length === 0) {\n return { valid: false, error: \"Empty find string\" };\n }\n // Only validate occurrence when find is provided (it's ignored otherwise)\n if (patch.find !== undefined && patch.find.length > 0) {\n if (patch.occurrence !== undefined) {\n if (patch.occurrence < 1 || !Number.isInteger(patch.occurrence)) {\n return {\n valid: false,\n error: `Invalid occurrence: ${patch.occurrence} (must be a positive integer)`,\n };\n }\n }\n }\n // Must have at least one formatting property with a meaningful value\n // (undefined values are ignored, so { bold: undefined } is effectively empty)\n if (\n !patch.formatting ||\n !Object.values(patch.formatting).some((v) => v !== undefined)\n ) {\n return { valid: false, error: \"Empty formatting object\" };\n }\n return { valid: true };\n }\n case \"apply_style\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return {\n valid: false,\n error: `Paragraph \"${patch.target}\" not found`,\n };\n }\n if (patch.find !== undefined && patch.find.length === 0) {\n return { valid: false, error: \"Empty find string\" };\n }\n // Only validate occurrence when find is provided (it's ignored otherwise)\n if (patch.find !== undefined && patch.find.length > 0) {\n if (patch.occurrence !== undefined) {\n if (patch.occurrence < 1 || !Number.isInteger(patch.occurrence)) {\n return {\n valid: false,\n error: `Invalid occurrence: ${patch.occurrence} (must be a positive integer)`,\n };\n }\n }\n }\n if (!patch.style || patch.style.length === 0) {\n return { valid: false, error: \"Empty style ID\" };\n }\n // Validate style exists and is correct type\n const styleInfo = doc.styles.find((s) => s.id === patch.style);\n if (!styleInfo) {\n return {\n valid: false,\n error: `Style \"${patch.style}\" not found in document styles`,\n };\n }\n if (patch.find !== undefined && patch.find.length > 0) {\n // Character style mode\n if (styleInfo.type !== \"character\") {\n return {\n valid: false,\n error: `Style \"${patch.style}\" is type \"${styleInfo.type}\", expected \"character\" when \"find\" is specified`,\n };\n }\n } else {\n // Paragraph style mode\n if (styleInfo.type !== \"paragraph\") {\n return {\n valid: false,\n error: `Style \"${patch.style}\" is type \"${styleInfo.type}\", expected \"paragraph\" when \"find\" is omitted`,\n };\n }\n }\n return { valid: true };\n }\n case \"append_text\": {\n if (!patch.text || patch.text.length === 0) {\n return { valid: false, error: \"Empty append text\" };\n }\n if (patch.container) {\n const containerValidation = validateContainerTarget(patch.container, doc);\n if (!containerValidation.valid) {\n return containerValidation;\n }\n }\n return { valid: true };\n }\n case \"add_table\": {\n if (!patch.rows || patch.rows.length === 0) {\n if (!patch.headers || patch.headers.length === 0) {\n return { valid: false, error: \"Table must have at least one row or headers\" };\n }\n }\n // Determine column count from headers or first row\n const colCount = patch.headers?.length ?? (patch.rows.length > 0 ? patch.rows[0]!.length : 0);\n if (colCount === 0) {\n return { valid: false, error: \"Table must have at least one column\" };\n }\n // All rows must have the same column count\n for (let i = 0; i < patch.rows.length; i++) {\n if (patch.rows[i]!.length !== colCount) {\n return {\n valid: false,\n error: `Row ${i} has ${patch.rows[i]!.length} cells, expected ${colCount}`,\n };\n }\n }\n // If headers provided, must match column count\n if (patch.headers && patch.rows.length > 0 && patch.headers.length !== patch.rows[0]!.length) {\n return {\n valid: false,\n error: `Headers have ${patch.headers.length} cells, but rows have ${patch.rows[0]!.length}`,\n };\n }\n // Validate afterId target exists if provided\n if (patch.afterId) {\n if (patch.container) {\n return {\n valid: false,\n error: `\"afterId\" is not supported when \"container\" is set — container-targeted tables are appended to the end of the header/footer`,\n };\n }\n const found = findElementById(patch.afterId, doc);\n if (!found) {\n return {\n valid: false,\n error: `Element \"${patch.afterId}\" not found`,\n };\n }\n }\n // Validate container if provided\n if (patch.container) {\n const containerValidation = validateContainerTarget(patch.container, doc);\n if (!containerValidation.valid) {\n return containerValidation;\n }\n }\n return { valid: true };\n }\n case \"add_header\":\n case \"add_footer\": {\n if (![\"default\", \"first\", \"even\"].includes(patch.variant)) {\n return { valid: false, error: `Invalid variant: ${patch.variant}` };\n }\n if (!patch.paragraphs || patch.paragraphs.length === 0) {\n return { valid: false, error: \"At least one paragraph is required\" };\n }\n return { valid: true };\n }\n case \"remove_header\":\n case \"remove_footer\": {\n if (![\"default\", \"first\", \"even\"].includes(patch.variant)) {\n return { valid: false, error: `Invalid variant: ${patch.variant}` };\n }\n return { valid: true };\n }\n\n // =========================================================================\n // List operations\n // =========================================================================\n\n case \"add_list\": {\n const validTypes = [\"bullet\", \"ordered\", \"multilevel\"];\n if (!validTypes.includes(patch.listType)) {\n return { valid: false, error: `Invalid listType: ${patch.listType}` };\n }\n if (!patch.items || patch.items.length === 0) {\n return { valid: false, error: \"items must be non-empty\" };\n }\n if (patch.afterId && patch.container) {\n return {\n valid: false,\n error: `\"afterId\" is not supported when \"container\" is set`,\n };\n }\n if (patch.afterId) {\n const found = findElementById(patch.afterId, doc);\n if (!found) {\n return { valid: false, error: `Element \"${patch.afterId}\" not found` };\n }\n }\n if (patch.container) {\n const containerValidation = validateContainerTarget(patch.container, doc);\n if (!containerValidation.valid) return containerValidation;\n }\n if (patch.numId !== undefined) {\n // Validate that the numId exists in numbering.xml\n if (!doc.xmlParts.numbering) {\n return { valid: false, error: `numId \"${patch.numId}\" specified but document has no numbering.xml` };\n }\n const root = doc.xmlParts.numbering.find((n) => getTagName(n) === \"w:numbering\");\n const numExists = root\n ? getChildren(root).some(\n (c) => getTagName(c) === \"w:num\" && getAttr(c, \"w:numId\") === patch.numId,\n )\n : false;\n if (!numExists) {\n return { valid: false, error: `numId \"${patch.numId}\" not found in numbering.xml` };\n }\n }\n return { valid: true };\n }\n\n case \"add_list_item\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!para.listInfo) {\n return { valid: false, error: `Paragraph \"${patch.target}\" is not a list item` };\n }\n if (!patch.text && patch.text !== \"\") {\n return { valid: false, error: \"text is required\" };\n }\n if (![\"before\", \"after\"].includes(patch.position)) {\n return { valid: false, error: `Invalid position: ${patch.position}` };\n }\n if (patch.level !== undefined && patch.level < 0) {\n return { valid: false, error: \"level must be >= 0\" };\n }\n return { valid: true };\n }\n\n case \"remove_list_item\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!para.listInfo) {\n return { valid: false, error: `Paragraph \"${patch.target}\" is not a list item` };\n }\n return { valid: true };\n }\n\n case \"convert_to_list\": {\n const validTypes = [\"bullet\", \"ordered\", \"multilevel\"];\n if (!validTypes.includes(patch.listType)) {\n return { valid: false, error: `Invalid listType: ${patch.listType}` };\n }\n if (!patch.targets || patch.targets.length === 0) {\n return { valid: false, error: \"targets must be non-empty\" };\n }\n for (const target of patch.targets) {\n const para = findParagraphById(target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${target}\" not found` };\n }\n }\n return { valid: true };\n }\n\n case \"remove_list\": {\n if (!patch.targets || patch.targets.length === 0) {\n return { valid: false, error: \"targets must be non-empty\" };\n }\n for (const target of patch.targets) {\n const para = findParagraphById(target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${target}\" not found` };\n }\n if (!para.listInfo) {\n return { valid: false, error: `Paragraph \"${target}\" is not a list item` };\n }\n }\n return { valid: true };\n }\n\n case \"set_list_properties\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!para.listInfo) {\n return { valid: false, error: `Paragraph \"${patch.target}\" is not a list item` };\n }\n if (patch.listType === undefined && patch.level === undefined) {\n return { valid: false, error: \"At least one of listType or level must be provided\" };\n }\n if (patch.listType !== undefined && ![\"bullet\", \"ordered\", \"multilevel\"].includes(patch.listType)) {\n return { valid: false, error: `Invalid listType: ${patch.listType}` };\n }\n if (patch.level !== undefined && patch.level < 0) {\n return { valid: false, error: \"level must be >= 0\" };\n }\n return { valid: true };\n }\n\n default:\n return {\n valid: false,\n error: `Unknown patch type: ${(patch as Patch).type}`,\n };\n }\n}\n\nfunction validateContainerTarget(\n container: { type: string; variant: string },\n doc: NormalizedDocument,\n): ValidationResult {\n if (![\"header\", \"footer\"].includes(container.type)) {\n return { valid: false, error: `Invalid container type: ${container.type}` };\n }\n if (![\"default\", \"first\", \"even\"].includes(container.variant)) {\n return { valid: false, error: `Invalid container variant: ${container.variant}` };\n }\n\n const parts = container.type === \"header\" ? doc.headers : doc.footers;\n const exists = [...parts.values()].some((part) => part.variant === container.variant);\n if (!exists) {\n return {\n valid: false,\n error: `No ${container.type} with variant \"${container.variant}\" found`,\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Find a paragraph by its ID anywhere in the document\n * (body, tables, headers, footers).\n *\n * Accepts either a positional id (`p12`, `tbl0.r1.c0.p0`, `hdr.default.p0`)\n * or a native id in the form `para:4F2A1C7B`.\n */\nexport function findParagraphById(\n id: string,\n doc: NormalizedDocument,\n): NormalizedParagraph | undefined {\n if (id.startsWith(PARA_ID_PREFIX)) {\n return findParagraphByParaId(id.slice(PARA_ID_PREFIX.length), doc);\n }\n return findParagraph(doc, (p) => p.id === id);\n}\n\n/**\n * Find a paragraph by its native `w14:paraId` anywhere in the document.\n * Resolution order: body (paragraphs and table cells in document order),\n * headers, footers. Case-insensitive.\n */\nexport function findParagraphByParaId(\n paraId: string,\n doc: NormalizedDocument,\n): NormalizedParagraph | undefined {\n const wanted = paraId.toUpperCase();\n return findParagraph(doc, (p) => p.paraId === wanted);\n}\n\nfunction findParagraph(\n doc: NormalizedDocument,\n predicate: (p: NormalizedParagraph) => boolean,\n): NormalizedParagraph | undefined {\n // Search body elements\n for (const element of doc.body) {\n const found = searchParagraphInElement(predicate, element);\n if (found) return found;\n }\n\n // Search headers (paragraphs, then tables)\n for (const header of doc.headers.values()) {\n for (const para of header.paragraphs) {\n if (predicate(para)) return para;\n }\n for (const table of header.tables) {\n const found = searchParagraphInTable(predicate, table);\n if (found) return found;\n }\n }\n\n // Search footers\n for (const footer of doc.footers.values()) {\n for (const para of footer.paragraphs) {\n if (predicate(para)) return para;\n }\n for (const table of footer.tables) {\n const found = searchParagraphInTable(predicate, table);\n if (found) return found;\n }\n }\n\n return undefined;\n}\n\n/**\n * Find a table by its ID in the document body, headers, and footers (including nested tables).\n */\nexport function findTableById(\n id: string,\n doc: NormalizedDocument,\n): NormalizedTable | undefined {\n for (const element of doc.body) {\n if (element.kind === \"table\") {\n const found = searchTableInTable(id, element);\n if (found) return found;\n }\n }\n\n // Search header tables\n for (const header of doc.headers.values()) {\n for (const table of header.tables) {\n const found = searchTableInTable(id, table);\n if (found) return found;\n }\n }\n\n // Search footer tables\n for (const footer of doc.footers.values()) {\n for (const table of footer.tables) {\n const found = searchTableInTable(id, table);\n if (found) return found;\n }\n }\n\n return undefined;\n}\n\nfunction searchTableInTable(\n id: string,\n table: NormalizedTable,\n): NormalizedTable | undefined {\n if (table.id === id) return table;\n\n // Search nested tables\n for (const row of table.rows) {\n for (const cell of row.cells) {\n for (const nested of cell.nestedTables) {\n const found = searchTableInTable(id, nested);\n if (found) return found;\n }\n }\n }\n\n return undefined;\n}\n\nfunction searchParagraphInElement(\n predicate: (p: NormalizedParagraph) => boolean,\n element: BodyElement,\n): NormalizedParagraph | undefined {\n if (element.kind === \"paragraph\") {\n return predicate(element) ? element : undefined;\n }\n\n if (element.kind === \"table\") {\n return searchParagraphInTable(predicate, element);\n }\n\n return undefined;\n}\n\nfunction searchParagraphInTable(\n predicate: (p: NormalizedParagraph) => boolean,\n table: NormalizedTable,\n): NormalizedParagraph | undefined {\n for (const row of table.rows) {\n for (const cell of row.cells) {\n for (const para of cell.paragraphs) {\n if (predicate(para)) return para;\n }\n // Search nested tables\n for (const nested of cell.nestedTables) {\n const found = searchParagraphInTable(predicate, nested);\n if (found) return found;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Find any top-level body element (paragraph or table) by its ID.\n * Accepts `para:` ids for body paragraphs.\n */\nexport function findElementById(\n id: string,\n doc: NormalizedDocument,\n): BodyElement | undefined {\n if (id.startsWith(PARA_ID_PREFIX)) {\n const wanted = id.slice(PARA_ID_PREFIX.length).toUpperCase();\n return doc.body.find((el) => el.kind === \"paragraph\" && el.paraId === wanted);\n }\n for (const element of doc.body) {\n if (element.kind === \"paragraph\" && element.id === id) return element;\n if (element.kind === \"table\") {\n if (element.id === id) return element;\n }\n }\n return undefined;\n}\n","/**\n * Text-to-runs mapping: given a character range in the normalized\n * paragraph text, resolves which RunMapping(s) are affected and\n * computes sub-run offsets.\n *\n * This is the bridge between \"find text in paragraph\" and\n * \"mutate the right XML runs.\"\n */\n\nimport type { RunMapping } from \"../types.js\";\n\n/**\n * A resolved range within a specific run.\n * Tells you exactly which portion of a run is affected by a text match.\n */\nexport interface ResolvedRunRange {\n /** The RunMapping this range falls in. */\n run: RunMapping;\n /** Start offset within this run's text (0-based, inclusive). */\n localStart: number;\n /** End offset within this run's text (0-based, exclusive). */\n localEnd: number;\n /** Whether this range covers the entire run text. */\n isFullRun: boolean;\n /** Whether this range starts at the beginning of the run. */\n isStartAligned: boolean;\n /** Whether this range ends at the end of the run. */\n isEndAligned: boolean;\n}\n\n/**\n * Find the Nth occurrence of a substring in a paragraph's text.\n *\n * @param paragraphText - The full normalized paragraph text\n * @param find - The text to search for\n * @param occurrence - Which occurrence (1-based). Default: 1.\n * @returns The start index of the match, or -1 if not found.\n */\nexport function findOccurrence(\n paragraphText: string,\n find: string,\n occurrence: number = 1,\n): number {\n if (find.length === 0) return -1;\n\n let count = 0;\n let startIdx = 0;\n\n while (startIdx <= paragraphText.length - find.length) {\n const idx = paragraphText.indexOf(find, startIdx);\n if (idx === -1) return -1;\n\n count++;\n if (count === occurrence) return idx;\n\n startIdx = idx + 1;\n }\n\n return -1;\n}\n\n/**\n * Resolve a character range to the affected RunMapping(s).\n *\n * Given a start and end offset in the paragraph's normalized text,\n * returns the list of runs that overlap with this range, along with\n * the local offsets within each run.\n *\n * @param runs - The paragraph's RunMapping array\n * @param rangeStart - Start offset in paragraph text (inclusive)\n * @param rangeEnd - End offset in paragraph text (exclusive)\n */\nexport function resolveRunRanges(\n runs: RunMapping[],\n rangeStart: number,\n rangeEnd: number,\n): ResolvedRunRange[] {\n const results: ResolvedRunRange[] = [];\n\n for (const run of runs) {\n // Check if this run overlaps with the range\n if (run.endOffset <= rangeStart || run.startOffset >= rangeEnd) {\n continue; // No overlap\n }\n\n // Calculate local offsets within this run\n const localStart = Math.max(0, rangeStart - run.startOffset);\n const localEnd = Math.min(run.text.length, rangeEnd - run.startOffset);\n\n results.push({\n run,\n localStart,\n localEnd,\n isFullRun: localStart === 0 && localEnd === run.text.length,\n isStartAligned: localStart === 0,\n isEndAligned: localEnd === run.text.length,\n });\n }\n\n return results;\n}\n\n/**\n * Find all occurrences of a substring in a paragraph's text.\n *\n * @param paragraphText - The full normalized paragraph text\n * @param find - The text to search for\n * @returns Array of start indices\n */\nexport function findAllOccurrences(\n paragraphText: string,\n find: string,\n): number[] {\n if (find.length === 0) return [];\n\n const indices: number[] = [];\n let startIdx = 0;\n\n while (startIdx <= paragraphText.length - find.length) {\n const idx = paragraphText.indexOf(find, startIdx);\n if (idx === -1) break;\n indices.push(idx);\n startIdx = idx + 1;\n }\n\n return indices;\n}\n","/**\n * Run splitter: the critical piece of surgical editing.\n *\n * When a text match doesn't align with run boundaries, we need to split\n * runs so the edit can be applied precisely. For example:\n *\n * Run 1: \"This agreement is va\" (plain)\n * Run 2: \"lid and enforceable\" (bold)\n *\n * To replace \"valid\" → \"enforceable\", we need to:\n * 1. Split run 1 at offset 18: \"This agreement is \" | \"va\"\n * 2. Split run 2 at offset 3: \"lid\" | \" and enforceable\"\n * 3. Now \"va\" + \"lid\" are isolated runs that can be replaced\n *\n * The split preserves formatting (w:rPr) — new runs clone the original's\n * properties. This is what ensures minimal redlines and formatting preservation.\n */\n\nimport type { FXPNode } from \"../types.js\";\nimport type { ResolvedRunRange } from \"../mapping/text-to-runs.js\";\nimport {\n findChild,\n createRun,\n getChildren,\n getTagName,\n} from \"../xml/helpers.js\";\n\n/**\n * Result of splitting a run.\n */\nexport interface SplitResult {\n /** The part before the matched range (may be undefined if start-aligned). */\n before?: FXPNode;\n /** The matched part (the run covering the matched text). */\n matched: FXPNode;\n /** The part after the matched range (may be undefined if end-aligned). */\n after?: FXPNode;\n}\n\n/**\n * Find the parent children array that directly contains a run node.\n *\n * Runs can be direct children of w:p, or nested inside w:hyperlink,\n * w:ins, or w:del. We need the actual parent's children array to do\n * splice operations.\n */\nexport function findRunParentChildren(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n): FXPNode[] | undefined {\n // Check direct children first\n const children = getChildren(paragraphNode);\n if (children.includes(runNode)) {\n return children;\n }\n\n // Check inside container elements (w:hyperlink, w:ins, w:del)\n return findRunInContainers(children, runNode);\n}\n\n/**\n * Recursively search container elements for a run node.\n */\nfunction findRunInContainers(\n children: FXPNode[],\n runNode: FXPNode,\n): FXPNode[] | undefined {\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:hyperlink\" || tag === \"w:ins\" || tag === \"w:del\") {\n const containerChildren = getChildren(child);\n if (containerChildren.includes(runNode)) {\n return containerChildren;\n }\n // Recurse deeper (e.g., w:ins inside w:hyperlink)\n const found = findRunInContainers(containerChildren, runNode);\n if (found) return found;\n }\n }\n return undefined;\n}\n\n/**\n * Split a run at the boundaries defined by a ResolvedRunRange.\n *\n * This mutates the parent paragraph's children array:\n * - The original run is replaced with up to 3 runs (before, matched, after)\n * - All new runs clone the original's w:rPr formatting\n *\n * @param paragraphNode - The w:p FXP node containing the run\n * @param range - The resolved range within the run to isolate\n * @returns The SplitResult with references to the new nodes\n */\nexport function splitRun(\n paragraphNode: FXPNode,\n range: ResolvedRunRange,\n): SplitResult {\n // If the range covers the full run, no splitting needed\n if (range.isFullRun) {\n return { matched: range.run.xmlRef };\n }\n\n const runNode = range.run.xmlRef;\n const fullText = range.run.text;\n const parentChildren = findRunParentChildren(paragraphNode, runNode);\n\n if (!parentChildren) {\n throw new Error(\"Run not found in paragraph children or hyperlink children\");\n }\n\n const runIndex = parentChildren.indexOf(runNode);\n\n // Extract the w:rPr node (formatting) from the original run\n const rPrNode = findChild(runNode, \"w:rPr\");\n\n // Calculate the three text segments\n const beforeText = fullText.slice(0, range.localStart);\n const matchedText = fullText.slice(range.localStart, range.localEnd);\n const afterText = fullText.slice(range.localEnd);\n\n const result: SplitResult = {\n matched: undefined!,\n };\n\n // Build the replacement runs\n const newRuns: FXPNode[] = [];\n\n if (beforeText.length > 0) {\n const beforeRun = createRunWithText(beforeText, rPrNode);\n result.before = beforeRun;\n newRuns.push(beforeRun);\n }\n\n const matchedRun = createRunWithText(matchedText, rPrNode);\n result.matched = matchedRun;\n newRuns.push(matchedRun);\n\n if (afterText.length > 0) {\n const afterRun = createRunWithText(afterText, rPrNode);\n result.after = afterRun;\n newRuns.push(afterRun);\n }\n\n // Replace the original run with the new runs in the parent\n parentChildren.splice(runIndex, 1, ...newRuns);\n\n return result;\n}\n\n/**\n * Split runs across multiple ResolvedRunRanges to isolate a text match.\n *\n * For a match that spans multiple runs, this:\n * 1. Splits the first run at the match start (if not start-aligned)\n * 2. Keeps middle runs intact (they're fully covered)\n * 3. Splits the last run at the match end (if not end-aligned)\n *\n * @returns Array of FXP run nodes that together contain exactly the matched text\n */\nexport function splitRunsForMatch(\n paragraphNode: FXPNode,\n ranges: ResolvedRunRange[],\n): FXPNode[] {\n if (ranges.length === 0) return [];\n\n if (ranges.length === 1) {\n const result = splitRun(paragraphNode, ranges[0]!);\n return [result.matched];\n }\n\n // Multi-run match: split from last to first to preserve indices\n const matchedRuns: FXPNode[] = [];\n\n // Process in reverse order to avoid index invalidation\n // But collect results in forward order\n const results: { index: number; split: SplitResult }[] = [];\n\n // Split last run first (if needed)\n const lastRange = ranges[ranges.length - 1]!;\n if (!lastRange.isEndAligned) {\n const split = splitRun(paragraphNode, lastRange);\n results.push({ index: ranges.length - 1, split });\n }\n\n // Split first run (if needed) — must be done after last to avoid index issues\n // Actually, since we're working on different runs, order doesn't matter\n // as long as we split from right to left in the children array\n const firstRange = ranges[0]!;\n if (!firstRange.isStartAligned) {\n const split = splitRun(paragraphNode, firstRange);\n results.push({ index: 0, split });\n }\n\n // Now collect all matched runs in order\n // After splitting, we need to re-find the runs\n for (let i = 0; i < ranges.length; i++) {\n const range = ranges[i]!;\n const result = results.find((r) => r.index === i);\n\n if (result) {\n matchedRuns.push(result.split.matched);\n } else {\n // Middle run or already-aligned run: use original xmlRef\n // Check the run still exists in its parent (splits may have shifted)\n const parentChildren = findRunParentChildren(paragraphNode, range.run.xmlRef);\n if (parentChildren && parentChildren.includes(range.run.xmlRef)) {\n matchedRuns.push(range.run.xmlRef);\n }\n }\n }\n\n return matchedRuns;\n}\n\n/**\n * Create a new w:r run node with the given text and optional formatting.\n * `\\n` in the text becomes a `w:br`, mirroring how run text is read.\n */\nfunction createRunWithText(\n text: string,\n rPrNode: FXPNode | undefined,\n): FXPNode {\n return createRun(text, rPrNode);\n}\n","/**\n * Revision tracking helpers for OOXML tracked changes.\n *\n * Provides:\n * - RevisionContext: holds the auto-incrementing ID counter, author, date\n * - scanMaxRevisionId: finds the highest w:id in existing revisions\n * - createElement helpers for w:ins, w:del, w:rPrChange, w:pPrChange\n * - convertRunToDeleted: renames w:t → w:delText inside a run\n */\n\nimport type { FXPNode, NormalizedDocument, PatchOptions } from \"../types.js\";\nimport {\n getChildren,\n getTagName,\n getAttr,\n setAttr,\n createElement,\n cloneNode,\n escapeXmlText,\n needsSpacePreserve,\n} from \"../xml/helpers.js\";\n\n// =============================================================================\n// RevisionContext\n// =============================================================================\n\n/**\n * Context object threaded through patch execution when trackRevisions is true.\n * Holds the auto-incrementing revision ID counter, author, and date.\n */\nexport interface RevisionContext {\n /** Next revision ID to assign. */\n nextId: number;\n /** Author for revision marks. */\n author: string;\n /** ISO 8601 date string for revision marks. */\n date: string;\n}\n\n/**\n * Create a RevisionContext from PatchOptions.\n * Scans the document for existing revision IDs to avoid collisions.\n */\nexport function createRevisionContext(\n doc: NormalizedDocument,\n options: PatchOptions,\n): RevisionContext {\n const maxId = scanMaxRevisionId(doc);\n return {\n nextId: maxId + 1,\n author: options.revisionAuthor ?? \"docxtor\",\n date: options.revisionDate ?? new Date().toISOString(),\n };\n}\n\n/**\n * Get the next revision ID and increment the counter.\n */\nexport function nextRevisionId(ctx: RevisionContext): string {\n const id = String(ctx.nextId);\n ctx.nextId++;\n return id;\n}\n\n// =============================================================================\n// Scan for existing revision IDs\n// =============================================================================\n\n/** Tag names that carry w:id for revision tracking. */\nconst REVISION_TAGS = new Set([\n \"w:ins\",\n \"w:del\",\n \"w:rPrChange\",\n \"w:pPrChange\",\n \"w:trPrChange\",\n \"w:tblPrChange\",\n \"w:sectPrChange\",\n \"w:cellIns\",\n \"w:cellDel\",\n \"w:cellMerge\",\n]);\n\n/**\n * Scan all XML parts in the document for the highest w:id value\n * on revision-related elements.\n *\n * Returns 0 if no existing revisions are found.\n */\nexport function scanMaxRevisionId(doc: NormalizedDocument): number {\n let maxId = 0;\n\n // Scan document.xml\n maxId = Math.max(maxId, scanNodesForMaxId(doc.xmlParts.document));\n\n // Scan headers\n for (const fxp of doc.xmlParts.headers.values()) {\n maxId = Math.max(maxId, scanNodesForMaxId(fxp));\n }\n\n // Scan footers\n for (const fxp of doc.xmlParts.footers.values()) {\n maxId = Math.max(maxId, scanNodesForMaxId(fxp));\n }\n\n // Scan the other revision-bearing parts so new ids never collide\n for (const key of [\"footnotes\", \"endnotes\", \"comments\"] as const) {\n const fxp = doc.xmlParts[key];\n if (fxp) maxId = Math.max(maxId, scanNodesForMaxId(fxp));\n }\n\n return maxId;\n}\n\n/**\n * Recursively scan an FXP node array for revision w:id values.\n */\nfunction scanNodesForMaxId(nodes: FXPNode[]): number {\n let maxId = 0;\n\n for (const node of nodes) {\n const tag = getTagName(node);\n if (tag && REVISION_TAGS.has(tag)) {\n const idStr = getAttr(node, \"w:id\");\n if (idStr) {\n const id = parseInt(idStr, 10);\n if (!isNaN(id) && id > maxId) {\n maxId = id;\n }\n }\n }\n\n // Recurse into children\n if (tag) {\n const children = getChildren(node);\n if (children.length > 0) {\n maxId = Math.max(maxId, scanNodesForMaxId(children));\n }\n }\n }\n\n return maxId;\n}\n\n// =============================================================================\n// Revision element creation helpers\n// =============================================================================\n\n/**\n * Create a w:ins element wrapping child nodes.\n */\nexport function createInsElement(\n ctx: RevisionContext,\n children: FXPNode[],\n): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:ins\", children, {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:del element wrapping child nodes.\n */\nexport function createDelElement(\n ctx: RevisionContext,\n children: FXPNode[],\n): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:del\", children, {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:rPrChange element containing the old w:rPr snapshot.\n */\nexport function createRPrChange(\n ctx: RevisionContext,\n oldRPr: FXPNode,\n): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:rPrChange\", [cloneNode(oldRPr)], {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:pPrChange element containing the old w:pPr snapshot.\n */\nexport function createPPrChange(\n ctx: RevisionContext,\n oldPPr: FXPNode,\n): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:pPrChange\", [cloneNode(oldPPr)], {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:ins element for table row properties (goes inside w:trPr).\n * This is an empty self-closing element with revision attributes.\n */\nexport function createTrPrIns(ctx: RevisionContext): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:ins\", [], {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:del element for table row properties (goes inside w:trPr).\n */\nexport function createTrPrDel(ctx: RevisionContext): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:del\", [], {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n// =============================================================================\n// Run conversion helpers\n// =============================================================================\n\n/**\n * Convert a run's w:t elements to w:delText for use inside w:del.\n *\n * OOXML requires deleted text to use w:delText instead of w:t.\n * This mutates the run node in-place.\n */\nexport function convertRunToDeleted(runNode: FXPNode): void {\n const children = getChildren(runNode);\n for (let i = 0; i < children.length; i++) {\n const child = children[i]!;\n if (\"w:t\" in child) {\n // Replace the w:t with a w:delText that has the same children and attributes\n const tChildren = child[\"w:t\"] as FXPNode[];\n const attrs = child[\":@\"];\n const newNode: FXPNode = { \"w:delText\": tChildren };\n if (attrs) {\n newNode[\":@\"] = attrs;\n }\n children[i] = newNode;\n } else if (\"w:instrText\" in child) {\n // Field instructions inside a deleted run use w:delInstrText\n const newNode: FXPNode = { \"w:delInstrText\": child[\"w:instrText\"] as FXPNode[] };\n if (child[\":@\"]) newNode[\":@\"] = child[\":@\"];\n children[i] = newNode;\n }\n }\n}\n\n/**\n * Create a new w:r run node with the given text and optional formatting,\n * suitable for use inside w:ins. Handles newlines via w:br.\n */\nexport function createRevisionRun(\n text: string,\n rPrNode: FXPNode | undefined,\n): FXPNode {\n const children: FXPNode[] = [];\n\n // Clone formatting if present\n if (rPrNode) {\n children.push(cloneNode(rPrNode));\n }\n\n if (text.includes(\"\\n\")) {\n // Multi-line: interleave w:t and w:br\n const segments = text.split(\"\\n\");\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n if (seg.length > 0 || i === 0) {\n const wt: FXPNode = {\n \"w:t\": [{ \"#text\": escapeXmlText(seg) } as unknown as FXPNode],\n };\n if (needsSpacePreserve(seg)) {\n setAttr(wt, \"xml:space\", \"preserve\");\n }\n children.push(wt);\n }\n if (i < segments.length - 1) {\n children.push({ \"w:br\": [] as FXPNode[] });\n }\n }\n } else {\n // Single line\n const wt: FXPNode = {\n \"w:t\": [{ \"#text\": escapeXmlText(text) } as unknown as FXPNode],\n };\n if (needsSpacePreserve(text)) {\n setAttr(wt, \"xml:space\", \"preserve\");\n }\n children.push(wt);\n }\n\n return { \"w:r\": children };\n}\n","/**\n * Markdown formatting parser and stripper.\n *\n * Parses GFM inline formatting markers into structured segments\n * and provides a utility to strip markers for plain-text matching.\n *\n * Supported markers:\n * - **bold**\n * - _italic_\n * - ~~strikethrough~~\n * - <u>underline</u>\n * - Nesting: **_bold+italic_**, _**bold+italic**_\n *\n * Escapes: a backslash before `*`, `_`, `~`, `<` or `\\` writes that\n * character literally (`\\_` → `_`). Any other backslash is kept as-is.\n *\n * Underscore rules: a `_` is an italic marker only when it stands alone\n * (not next to another `_`) at a word boundary. Runs of underscores such as\n * signature blanks (`Name: ______`) and intra-word underscores\n * (`snake_case`) are literal text.\n *\n * Every entry point takes a `markers` flag; with `false` the text is\n * returned verbatim — no markers, no escapes.\n */\n\n/** Characters a backslash may escape when markers are enabled. */\nconst ESCAPABLE = new Set([\"*\", \"_\", \"~\", \"<\", \"\\\\\"]);\n\n/**\n * A segment of text with its inline formatting flags.\n */\nexport interface FormattedSegment {\n text: string;\n bold?: boolean;\n italic?: boolean;\n strikethrough?: boolean;\n underline?: boolean;\n}\n\n/**\n * Strip all GFM inline formatting markers from a string,\n * returning the plain text content.\n *\n * Used to normalize `find` / `anchor` fields in patches so they\n * match against the plain paragraph text.\n */\nexport function stripFormattingMarkers(text: string, markers: boolean = true): string {\n if (!markers) return text;\n // Process by walking the string and removing marker characters.\n // We use the same parser and concatenate the text segments.\n const segments = parseFormattedText(text);\n return segments.map((s) => s.text).join(\"\");\n}\n\n/**\n * Parse a GFM-formatted string into an array of segments, each\n * carrying the formatting flags that were active when the text\n * appeared.\n *\n * Handles:\n * - **bold**\n * - _italic_ (underscore only, to avoid conflicts with intra-word asterisks)\n * - ~~strikethrough~~\n * - Nesting in any order: **_text_**, _**text**_\n *\n * Plain text without markers returns a single segment with no flags.\n * With `markers: false` the input is returned as one verbatim segment.\n */\nexport function parseFormattedText(input: string, markers: boolean = true): FormattedSegment[] {\n if (!markers) return input.length > 0 ? [{ text: input }] : [];\n\n const segments: FormattedSegment[] = [];\n let pos = 0;\n let currentText = \"\";\n let bold = false;\n let italic = false;\n let strikethrough = false;\n let underline = false;\n\n function flush(): void {\n if (currentText.length > 0) {\n const seg: FormattedSegment = { text: currentText };\n if (bold) seg.bold = true;\n if (italic) seg.italic = true;\n if (strikethrough) seg.strikethrough = true;\n if (underline) seg.underline = true;\n segments.push(seg);\n currentText = \"\";\n }\n }\n\n while (pos < input.length) {\n // Backslash escape: the next character is literal\n if (input[pos] === \"\\\\\" && pos + 1 < input.length && ESCAPABLE.has(input[pos + 1]!)) {\n currentText += input[pos + 1];\n pos += 2;\n continue;\n }\n\n // Check for <u> (underline open)\n if (input.slice(pos, pos + 3) === \"<u>\") {\n flush();\n underline = true;\n pos += 3;\n continue;\n }\n\n // Check for </u> (underline close)\n if (input.slice(pos, pos + 4) === \"</u>\") {\n flush();\n underline = false;\n pos += 4;\n continue;\n }\n\n // Check for ~~ (strikethrough toggle)\n if (input[pos] === \"~\" && input[pos + 1] === \"~\") {\n flush();\n strikethrough = !strikethrough;\n pos += 2;\n continue;\n }\n\n // Check for ** (bold toggle)\n if (input[pos] === \"*\" && input[pos + 1] === \"*\") {\n flush();\n bold = !bold;\n pos += 2;\n continue;\n }\n\n // Check for _ (italic toggle)\n if (input[pos] === \"_\" && isItalicMarker(input, pos, italic)) {\n flush();\n italic = !italic;\n pos += 1;\n continue;\n }\n\n currentText += input[pos];\n pos++;\n }\n\n flush();\n\n // Merge adjacent segments with identical formatting\n return mergeSegments(segments);\n}\n\n/**\n * Decide whether the `_` at `pos` is an italic marker.\n *\n * - Never when it touches another `_` (underscore blanks, `__init__`).\n * - Opening: at the start of the string or after a non-word character, and\n * followed by something that is not whitespace.\n * - Closing (italic already open): preceded by something that is not\n * whitespace.\n * - Otherwise only at the end of the string or next to a non-word\n * character — the word-boundary rule that keeps `snake_case` literal.\n */\nfunction isItalicMarker(input: string, pos: number, italicOpen: boolean): boolean {\n const prevChar = pos > 0 ? input[pos - 1]! : \"\";\n const nextChar = pos + 1 < input.length ? input[pos + 1]! : \"\";\n if (prevChar === \"_\" || nextChar === \"_\") return false;\n\n if (italicOpen) {\n return prevChar !== \"\" && !/\\s/.test(prevChar);\n }\n const opensAtBoundary = pos === 0 || /\\W/.test(prevChar);\n if (opensAtBoundary) {\n return nextChar !== \"\" && !/\\s/.test(nextChar);\n }\n return pos === input.length - 1 || /\\W/.test(nextChar);\n}\n\n/**\n * Merge adjacent segments with identical formatting flags.\n */\nfunction mergeSegments(segments: FormattedSegment[]): FormattedSegment[] {\n if (segments.length <= 1) return segments;\n\n const merged: FormattedSegment[] = [segments[0]!];\n\n for (let i = 1; i < segments.length; i++) {\n const prev = merged[merged.length - 1]!;\n const curr = segments[i]!;\n\n if (\n !!prev.bold === !!curr.bold &&\n !!prev.italic === !!curr.italic &&\n !!prev.strikethrough === !!curr.strikethrough &&\n !!prev.underline === !!curr.underline\n ) {\n prev.text += curr.text;\n } else {\n merged.push(curr);\n }\n }\n\n return merged;\n}\n\n/**\n * Check whether a string contains any GFM formatting markers or escapes,\n * i.e. whether {@link parseFormattedText} would change it.\n */\nexport function hasFormattingMarkers(text: string): boolean {\n return stripFormattingMarkers(text) !== text;\n}\n","/**\n * Text operations: replace_text, insert_text, delete_text.\n *\n * These operate on a NormalizedParagraph by:\n * 1. Finding the target text in the normalized paragraph text\n * 2. Mapping to run(s) via the text-to-runs resolver\n * 3. Splitting runs if the match doesn't align with run boundaries\n * 4. Mutating the FXP w:t text nodes directly\n *\n * After execution, the FXP object reflects the edit, and the\n * paragraph should be re-normalized to update mappings.\n *\n * When a RevisionContext is provided, operations produce OOXML tracked\n * changes (w:ins, w:del) instead of direct mutations.\n */\n\nimport type {\n NormalizedParagraph,\n ReplaceTextPatch,\n InsertTextPatch,\n DeleteTextPatch,\n FXPNode,\n} from \"../types.js\";\nimport { findOccurrence, resolveRunRanges } from \"../mapping/text-to-runs.js\";\nimport { splitRun, splitRunsForMatch, findRunParentChildren } from \"./run-splitter.js\";\nimport {\n getChildren,\n setTextContent,\n removeChild,\n getRunText,\n findTextNode,\n getTagName,\n setAttr,\n escapeXmlText,\n needsSpacePreserve,\n findChild,\n cloneNode,\n createElement,\n getAttr,\n} from \"../xml/helpers.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport {\n createInsElement,\n createDelElement,\n convertRunToDeleted,\n createRevisionRun,\n nextRevisionId,\n} from \"./revision.js\";\nimport {\n stripFormattingMarkers,\n parseFormattedText,\n type FormattedSegment,\n} from \"./markdown-parser.js\";\n\n/**\n * Find the parent node that directly contains a run, and remove the run.\n * Handles runs that are direct children of w:p or nested inside\n * w:hyperlink, w:ins, or w:del containers.\n */\nfunction removeRunFromParagraph(paragraphNode: FXPNode, runNode: FXPNode): void {\n // Try direct removal first\n if (removeChild(paragraphNode, runNode)) return;\n\n // Try inside container elements (w:hyperlink, w:ins, w:del)\n const children = getChildren(paragraphNode);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:hyperlink\" || tag === \"w:ins\" || tag === \"w:del\") {\n if (removeChild(child, runNode)) return;\n // Check nested containers\n const containerChildren = getChildren(child);\n for (const nested of containerChildren) {\n const nestedTag = getTagName(nested);\n if (nestedTag === \"w:hyperlink\" || nestedTag === \"w:ins\" || nestedTag === \"w:del\") {\n if (removeChild(nested, runNode)) return;\n }\n }\n }\n }\n}\n\n/**\n * Find the container element (w:ins, w:del, w:hyperlink) that directly holds\n * a run, or undefined if the run is a direct child of the paragraph.\n */\nfunction findRunContainer(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n): FXPNode | undefined {\n const children = getChildren(paragraphNode);\n if (children.includes(runNode)) return undefined;\n\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:hyperlink\" || tag === \"w:ins\" || tag === \"w:del\") {\n const containerChildren = getChildren(child);\n if (containerChildren.includes(runNode)) return child;\n for (const nested of containerChildren) {\n const nestedTag = getTagName(nested);\n if (nestedTag === \"w:hyperlink\" || nestedTag === \"w:ins\" || nestedTag === \"w:del\") {\n if (getChildren(nested).includes(runNode)) return nested;\n }\n }\n }\n }\n return undefined;\n}\n\n// =============================================================================\n// Replace text\n// =============================================================================\n\n/**\n * Execute a replace_text patch on a paragraph.\n *\n * Finds the target text, maps to runs, splits if needed, and replaces.\n * When revCtx is provided, wraps old text in w:del and new text in w:ins.\n */\nexport function executeReplaceText(\n paragraph: NormalizedParagraph,\n patch: ReplaceTextPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n const occurrence = patch.occurrence ?? 1;\n const plainFind = stripFormattingMarkers(patch.find, markers);\n const matchStart = findOccurrence(paragraph.text, plainFind, occurrence);\n\n if (matchStart === -1) {\n throw new Error(\n `Text \"${plainFind}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n const matchEnd = matchStart + plainFind.length;\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchEnd);\n\n if (ranges.length === 0) {\n throw new Error(\n `No runs found for text range [${matchStart}, ${matchEnd}] in paragraph \"${paragraph.id}\"`,\n );\n }\n\n if (revCtx) {\n executeReplaceTextRevision(paragraph, patch, ranges, revCtx, markers);\n } else {\n executeReplaceTextDirect(paragraph, patch, ranges, markers);\n }\n}\n\n/**\n * Direct (non-revision) replacement.\n *\n * If the replacement text contains GFM formatting markers (**bold**, _italic_,\n * ~~strike~~), it is parsed into segments and each segment becomes a separate\n * run with the appropriate w:rPr formatting, inheriting any existing formatting\n * from the matched run.\n */\nfunction executeReplaceTextDirect(\n paragraph: NormalizedParagraph,\n patch: ReplaceTextPatch,\n ranges: ReturnType<typeof resolveRunRanges>,\n markers: boolean,\n): void {\n const segments = parseFormattedText(patch.replace, markers);\n const hasFormatting = segments.some((s) => s.bold || s.italic || s.strikethrough || s.underline);\n const plainText = stripFormattingMarkers(patch.replace, markers);\n\n if (ranges.length === 1) {\n const range = ranges[0]!;\n const splitResult = splitRun(paragraph.xmlRef, range);\n\n if (!hasFormatting) {\n // Simple plain text replacement\n setRunText(splitResult.matched, plainText);\n } else {\n // Replace the matched run with formatted runs\n replaceRunWithFormattedRuns(paragraph.xmlRef, splitResult.matched, segments);\n }\n } else {\n const matchedRuns = splitRunsForMatch(paragraph.xmlRef, ranges);\n\n if (matchedRuns.length === 0) {\n throw new Error(\n `Failed to isolate matched runs in paragraph \"${paragraph.id}\"`,\n );\n }\n\n if (!hasFormatting) {\n setRunText(matchedRuns[0]!, plainText);\n } else {\n replaceRunWithFormattedRuns(paragraph.xmlRef, matchedRuns[0]!, segments);\n }\n\n // Remove extra matched runs\n for (let i = 1; i < matchedRuns.length; i++) {\n removeRunFromParagraph(paragraph.xmlRef, matchedRuns[i]!);\n }\n }\n}\n\n/**\n * Revision-tracked replacement:\n * 1. Split runs to isolate matched text\n * 2. Wrap matched runs in w:del (converting w:t → w:delText)\n * 3. Create new run(s) with replacement text wrapped in w:ins\n * 4. Insert w:ins immediately after w:del\n */\nfunction executeReplaceTextRevision(\n paragraph: NormalizedParagraph,\n patch: ReplaceTextPatch,\n ranges: ReturnType<typeof resolveRunRanges>,\n revCtx: RevisionContext,\n markers: boolean,\n): void {\n // Split runs to isolate the matched text\n let matchedRuns: FXPNode[];\n if (ranges.length === 1) {\n const range = ranges[0]!;\n const splitResult = splitRun(paragraph.xmlRef, range);\n matchedRuns = [splitResult.matched];\n } else {\n matchedRuns = splitRunsForMatch(paragraph.xmlRef, ranges);\n }\n\n if (matchedRuns.length === 0) {\n throw new Error(\n `Failed to isolate matched runs in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Get formatting from the first matched run for the replacement\n const rPrNode = findChild(matchedRuns[0]!, \"w:rPr\");\n\n // Find the parent children array for the first matched run\n const parentChildren = findRunParentChildren(paragraph.xmlRef, matchedRuns[0]!);\n if (!parentChildren) {\n throw new Error(\"Matched run not found in paragraph\");\n }\n\n // Check if the matched runs live inside a w:ins (unaccepted insertion).\n // If so, we must NOT wrap in w:del — just remove the runs from the w:ins\n // (deleting unaccepted content = removing the insertion, not creating a deletion).\n const container = findRunContainer(paragraph.xmlRef, matchedRuns[0]!);\n const insideIns = container != null && getTagName(container) === \"w:ins\";\n\n // Create the replacement run(s); wrapped in w:ins below\n const segments = parseFormattedText(patch.replace, markers);\n const hasFormatting = segments.some((s) => s.bold || s.italic || s.strikethrough || s.underline);\n const plainText = stripFormattingMarkers(patch.replace, markers);\n\n let newRuns: FXPNode[] = [];\n if (segments.length > 0 && plainText.length > 0) {\n newRuns = hasFormatting\n ? createFormattedRuns(segments, rPrNode)\n : [createRevisionRun(plainText, rPrNode)];\n }\n const insElement = newRuns.length > 0 ? createInsElement(revCtx, newRuns) : undefined;\n\n // Find the position of the first matched run in the parent\n const firstRunIndex = parentChildren.indexOf(matchedRuns[0]!);\n\n // Remove all matched runs from the parent\n for (const run of matchedRuns) {\n const idx = parentChildren.indexOf(run);\n if (idx !== -1) {\n parentChildren.splice(idx, 1);\n }\n }\n\n if (insideIns) {\n // The matched runs were an unaccepted insertion, so there is nothing to\n // mark deleted — they are simply gone. The replacement must still land\n // exactly where they were, not after the surrounding w:ins.\n insertIntoInsertion(paragraph.xmlRef, container!, firstRunIndex, newRuns, revCtx);\n } else {\n // Normal case: insert w:del and w:ins at the position of the first matched run\n // Clone matched runs and convert to deleted runs\n const deletedRuns: FXPNode[] = [];\n for (const run of matchedRuns) {\n const cloned = cloneNode(run);\n convertRunToDeleted(cloned);\n deletedRuns.push(cloned);\n }\n const delElement = createDelElement(revCtx, deletedRuns);\n\n if (insElement) {\n parentChildren.splice(firstRunIndex, 0, delElement, insElement);\n } else {\n parentChildren.splice(firstRunIndex, 0, delElement);\n }\n }\n}\n\n/**\n * Put `newRuns` at `index` among the children of an existing `w:ins`.\n *\n * - Same author: the runs join that insertion in place.\n * - Another author: the insertion is split around `index`; the new runs go\n * between the halves in their own `w:ins`, so attribution and order are\n * both kept. An empty half is dropped (and the whole container, if it\n * ends up empty).\n */\nfunction insertIntoInsertion(\n paragraphNode: FXPNode,\n container: FXPNode,\n index: number,\n newRuns: FXPNode[],\n revCtx: RevisionContext,\n): void {\n const containerChildren = getChildren(container);\n const containerParent = findRunParentChildren(paragraphNode, container);\n if (!containerParent) {\n throw new Error(\"Insertion container not found in paragraph\");\n }\n const containerIdx = containerParent.indexOf(container);\n\n if (newRuns.length === 0) {\n if (containerChildren.length === 0) containerParent.splice(containerIdx, 1);\n return;\n }\n\n if (getAttr(container, \"w:author\") === revCtx.author) {\n containerChildren.splice(index, 0, ...newRuns);\n return;\n }\n\n const tail = containerChildren.splice(index);\n const replacement: FXPNode[] = [];\n if (containerChildren.length > 0) replacement.push(container);\n replacement.push(createInsElement(revCtx, newRuns));\n if (tail.length > 0) {\n const second = createElement(\"w:ins\", tail, {\n \"w:id\": nextRevisionId(revCtx),\n \"w:author\": getAttr(container, \"w:author\") ?? \"\",\n \"w:date\": getAttr(container, \"w:date\") ?? \"\",\n });\n replacement.push(second);\n }\n containerParent.splice(containerIdx, 1, ...replacement);\n}\n\n// =============================================================================\n// Insert text\n// =============================================================================\n\n/**\n * Execute an insert_text patch on a paragraph.\n *\n * Strategy: find the insertion offset in the normalized text, then find\n * which run contains that offset. Insert the text directly into that run's\n * w:t node at the correct position.\n *\n * With revision tracking, the inserted text goes into a new run wrapped\n * in w:ins, and the target run is split if needed.\n */\nexport function executeInsertText(\n paragraph: NormalizedParagraph,\n patch: InsertTextPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n const occurrence = patch.occurrence ?? 1;\n const plainAnchor = stripFormattingMarkers(patch.anchor, markers);\n const anchorStart = findOccurrence(paragraph.text, plainAnchor, occurrence);\n\n if (anchorStart === -1) {\n throw new Error(\n `Anchor text \"${plainAnchor}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n // Determine the character offset where we want to insert\n const insertOffset =\n patch.position === \"before\"\n ? anchorStart\n : anchorStart + plainAnchor.length;\n\n if (revCtx) {\n executeInsertTextRevision(paragraph, patch, insertOffset, revCtx, markers);\n } else {\n executeInsertTextDirect(paragraph, patch, insertOffset, markers);\n }\n}\n\n/**\n * Direct (non-revision) insertion — modifies the run's text in-place,\n * or creates multiple formatted runs if the text contains GFM markers.\n */\nfunction executeInsertTextDirect(\n paragraph: NormalizedParagraph,\n patch: InsertTextPatch,\n insertOffset: number,\n markers: boolean,\n): void {\n let targetRun: typeof paragraph.runs[number] | undefined;\n\n for (const run of paragraph.runs) {\n if (insertOffset >= run.startOffset && insertOffset <= run.endOffset) {\n targetRun = run;\n break;\n }\n }\n\n if (!targetRun) {\n const lastRun = paragraph.runs[paragraph.runs.length - 1];\n if (!lastRun) {\n throw new Error(\n `No runs found in paragraph \"${paragraph.id}\" for insertion`,\n );\n }\n targetRun = lastRun;\n }\n\n const segments = parseFormattedText(patch.text, markers);\n const hasFormatting = segments.some((s) => s.bold || s.italic || s.strikethrough || s.underline);\n\n if (!hasFormatting) {\n // Simple plain text insertion\n const plainText = stripFormattingMarkers(patch.text, markers);\n const localInsertOffset = insertOffset - targetRun.startOffset;\n const currentText = getRunText(targetRun.xmlRef);\n const newText =\n currentText.slice(0, localInsertOffset) +\n plainText +\n currentText.slice(localInsertOffset);\n setRunText(targetRun.xmlRef, newText);\n } else {\n // Formatted insertion: split the target run at the insert point and\n // insert formatted runs between the halves\n const localInsertOffset = insertOffset - targetRun.startOffset;\n const rPrNode = findChild(targetRun.xmlRef, \"w:rPr\");\n const parentChildren = findRunParentChildren(paragraph.xmlRef, targetRun.xmlRef);\n if (!parentChildren) {\n throw new Error(\"Target run not found in paragraph\");\n }\n\n const runIndex = parentChildren.indexOf(targetRun.xmlRef);\n const fullText = targetRun.text;\n const beforeText = fullText.slice(0, localInsertOffset);\n const afterText = fullText.slice(localInsertOffset);\n\n const newNodes: FXPNode[] = [];\n\n // Before part (if any)\n if (beforeText.length > 0) {\n newNodes.push(createRunWithTextAndFormatting(beforeText, rPrNode));\n }\n\n // Formatted inserted runs\n const formattedRuns = createFormattedRuns(segments, rPrNode);\n newNodes.push(...formattedRuns);\n\n // After part (if any)\n if (afterText.length > 0) {\n newNodes.push(createRunWithTextAndFormatting(afterText, rPrNode));\n }\n\n // Replace the original run\n parentChildren.splice(runIndex, 1, ...newNodes);\n }\n}\n\n/**\n * Revision-tracked insertion:\n * 1. Find the target run containing the insertion offset\n * 2. Split the run at the insertion point if needed\n * 3. Create a new w:r wrapped in w:ins\n * 4. Insert between the split halves (or at boundary)\n */\nfunction executeInsertTextRevision(\n paragraph: NormalizedParagraph,\n patch: InsertTextPatch,\n insertOffset: number,\n revCtx: RevisionContext,\n markers: boolean,\n): void {\n let targetRun: typeof paragraph.runs[number] | undefined;\n\n for (const run of paragraph.runs) {\n if (insertOffset >= run.startOffset && insertOffset <= run.endOffset) {\n targetRun = run;\n break;\n }\n }\n\n if (!targetRun) {\n const lastRun = paragraph.runs[paragraph.runs.length - 1];\n if (!lastRun) {\n throw new Error(\n `No runs found in paragraph \"${paragraph.id}\" for insertion`,\n );\n }\n targetRun = lastRun;\n }\n\n const localInsertOffset = insertOffset - targetRun.startOffset;\n\n // Get formatting from the target run to clone onto the new run\n const rPrNode = findChild(targetRun.xmlRef, \"w:rPr\");\n\n // Create the new run(s) with inserted text wrapped in w:ins\n const segments = parseFormattedText(patch.text, markers);\n const hasFormatting = segments.some((s) => s.bold || s.italic || s.strikethrough || s.underline);\n\n let insRuns: FXPNode[];\n if (hasFormatting) {\n insRuns = createFormattedRuns(segments, rPrNode);\n } else {\n const plainText = stripFormattingMarkers(patch.text, markers);\n insRuns = [createRevisionRun(plainText, rPrNode)];\n }\n const insElement = createInsElement(revCtx, insRuns);\n\n // Find the parent children array containing the target run\n const parentChildren = findRunParentChildren(paragraph.xmlRef, targetRun.xmlRef);\n if (!parentChildren) {\n throw new Error(\"Target run not found in paragraph\");\n }\n\n const runIndex = parentChildren.indexOf(targetRun.xmlRef);\n\n // Determine if we need to split the run\n const atStart = localInsertOffset === 0;\n const atEnd = localInsertOffset === targetRun.text.length;\n\n if (atStart) {\n // Insert before the target run (no splitting needed)\n parentChildren.splice(runIndex, 0, insElement);\n } else if (atEnd) {\n // Insert after the target run (no splitting needed)\n parentChildren.splice(runIndex + 1, 0, insElement);\n } else {\n // Split the run at the insertion point\n const fullText = targetRun.text;\n const beforeText = fullText.slice(0, localInsertOffset);\n const afterText = fullText.slice(localInsertOffset);\n\n // Create two halves preserving formatting\n const beforeRun = createRunWithTextAndFormatting(beforeText, rPrNode);\n const afterRun = createRunWithTextAndFormatting(afterText, rPrNode);\n\n // Replace the original run with: beforeRun, insElement, afterRun\n parentChildren.splice(runIndex, 1, beforeRun, insElement, afterRun);\n }\n}\n\n/**\n * Create a w:r run node with text and optional formatting (for splitting).\n *\n * Handles \\n in text by emitting w:br elements (matching the behavior\n * of getRunText which converts w:br → \\n). Without this, splitting a\n * run containing line breaks would produce literal \\n in w:t instead\n * of proper w:br elements.\n */\nfunction createRunWithTextAndFormatting(\n text: string,\n rPrNode: FXPNode | undefined,\n): FXPNode {\n const children: FXPNode[] = [];\n if (rPrNode) {\n children.push(cloneNode(rPrNode));\n }\n\n if (text.includes(\"\\n\")) {\n // Multi-line: interleave w:t and w:br\n const segments = text.split(\"\\n\");\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n if (seg.length > 0 || i === 0) {\n const textNode = { \"#text\": escapeXmlText(seg) } as unknown as FXPNode;\n const wtNode: FXPNode = { \"w:t\": [textNode] };\n if (needsSpacePreserve(seg)) {\n setAttr(wtNode, \"xml:space\", \"preserve\");\n }\n children.push(wtNode);\n }\n if (i < segments.length - 1) {\n children.push({ \"w:br\": [] as FXPNode[] });\n }\n }\n } else {\n // Single line\n const textNode = { \"#text\": escapeXmlText(text) } as unknown as FXPNode;\n const wtNode: FXPNode = { \"w:t\": [textNode] };\n if (needsSpacePreserve(text)) {\n setAttr(wtNode, \"xml:space\", \"preserve\");\n }\n children.push(wtNode);\n }\n\n return { \"w:r\": children };\n}\n\n// =============================================================================\n// Delete text\n// =============================================================================\n\n/**\n * Execute a delete_text patch on a paragraph.\n *\n * Without revisions: equivalent to replace_text with an empty replacement.\n * With revisions: wraps matched runs in w:del (text stays in XML but is marked as deleted).\n */\nexport function executeDeleteText(\n paragraph: NormalizedParagraph,\n patch: DeleteTextPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n if (revCtx) {\n executeDeleteTextRevision(paragraph, patch, revCtx, markers);\n } else {\n executeReplaceText(\n paragraph,\n {\n type: \"replace_text\",\n target: patch.target,\n find: patch.find,\n replace: \"\",\n occurrence: patch.occurrence,\n },\n undefined,\n markers,\n );\n }\n}\n\n/**\n * Revision-tracked deletion:\n * 1. Split runs to isolate the matched text\n * 2. Wrap matched runs in w:del, converting w:t → w:delText\n * 3. Text stays in XML but is marked as deleted\n */\nfunction executeDeleteTextRevision(\n paragraph: NormalizedParagraph,\n patch: DeleteTextPatch,\n revCtx: RevisionContext,\n markers: boolean,\n): void {\n const occurrence = patch.occurrence ?? 1;\n const plainFind = stripFormattingMarkers(patch.find, markers);\n const matchStart = findOccurrence(paragraph.text, plainFind, occurrence);\n\n if (matchStart === -1) {\n throw new Error(\n `Text \"${plainFind}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n const matchEnd = matchStart + plainFind.length;\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchEnd);\n\n if (ranges.length === 0) {\n throw new Error(\n `No runs found for text range [${matchStart}, ${matchEnd}] in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Split runs to isolate the matched text\n let matchedRuns: FXPNode[];\n if (ranges.length === 1) {\n const range = ranges[0]!;\n const splitResult = splitRun(paragraph.xmlRef, range);\n matchedRuns = [splitResult.matched];\n } else {\n matchedRuns = splitRunsForMatch(paragraph.xmlRef, ranges);\n }\n\n if (matchedRuns.length === 0) {\n throw new Error(\n `Failed to isolate matched runs in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Find the parent children array for the first matched run\n const parentChildren = findRunParentChildren(paragraph.xmlRef, matchedRuns[0]!);\n if (!parentChildren) {\n throw new Error(\"Matched run not found in paragraph\");\n }\n\n // Check if the matched runs live inside a w:ins (unaccepted insertion).\n const container = findRunContainer(paragraph.xmlRef, matchedRuns[0]!);\n const insideIns = container != null && getTagName(container) === \"w:ins\";\n\n // Find the position of the first matched run in the parent\n const firstRunIndex = parentChildren.indexOf(matchedRuns[0]!);\n\n // Remove all matched runs from the parent\n for (const run of matchedRuns) {\n const idx = parentChildren.indexOf(run);\n if (idx !== -1) {\n parentChildren.splice(idx, 1);\n }\n }\n\n if (insideIns) {\n // Runs were inside w:ins — just remove them (and the w:ins if now empty).\n // No w:del needed: the content was never part of the accepted document.\n if (parentChildren.length === 0 && container) {\n const paraChildren = getChildren(paragraph.xmlRef);\n const containerIdx = paraChildren.indexOf(container);\n if (containerIdx !== -1) {\n paraChildren.splice(containerIdx, 1);\n }\n }\n } else {\n // Normal case: clone matched runs, wrap in w:del\n const deletedRuns: FXPNode[] = [];\n for (const run of matchedRuns) {\n const cloned = cloneNode(run);\n convertRunToDeleted(cloned);\n deletedRuns.push(cloned);\n }\n const delElement = createDelElement(revCtx, deletedRuns);\n parentChildren.splice(firstRunIndex, 0, delElement);\n }\n}\n\n// =============================================================================\n// Formatted run creation from GFM markers\n// =============================================================================\n\n/**\n * Build an array of w:r FXP nodes from parsed formatted segments.\n *\n * Each segment becomes a run. Formatting flags from the segment (bold, italic,\n * strikethrough) are layered on top of any existing rPr inherited from the\n * base run.\n */\nexport function createFormattedRuns(\n segments: FormattedSegment[],\n baseRPr: FXPNode | undefined,\n): FXPNode[] {\n const runs: FXPNode[] = [];\n\n for (const seg of segments) {\n if (seg.text.length === 0) continue;\n\n const run = createRunWithTextAndFormatting(seg.text, baseRPr);\n\n // Layer GFM formatting on top\n if (seg.bold || seg.italic || seg.strikethrough || seg.underline) {\n let rPr = findChild(run, \"w:rPr\");\n if (!rPr) {\n rPr = createElement(\"w:rPr\", []);\n const children = getChildren(run);\n children.unshift(rPr);\n }\n const rPrChildren = getChildren(rPr);\n\n if (seg.bold && !findChild(rPr, \"w:b\")) {\n rPrChildren.push(createElement(\"w:b\", []));\n }\n if (seg.italic && !findChild(rPr, \"w:i\")) {\n rPrChildren.push(createElement(\"w:i\", []));\n }\n if (seg.strikethrough && !findChild(rPr, \"w:strike\")) {\n rPrChildren.push(createElement(\"w:strike\", []));\n }\n if (seg.underline && !findChild(rPr, \"w:u\")) {\n rPrChildren.push(createElement(\"w:u\", [], { \"w:val\": \"single\" }));\n }\n }\n\n runs.push(run);\n }\n\n return runs;\n}\n\n/**\n * Replace a single run node in a paragraph with multiple formatted runs.\n *\n * Finds the run in the paragraph's children (or nested containers) and\n * splices in the new runs.\n */\nfunction replaceRunWithFormattedRuns(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n segments: FormattedSegment[],\n): void {\n const rPrNode = findChild(runNode, \"w:rPr\");\n const newRuns = createFormattedRuns(segments, rPrNode);\n\n if (newRuns.length === 0) {\n // All segments were empty — just clear the run text\n setRunText(runNode, \"\");\n return;\n }\n\n // Find the run in parent's children and replace\n const parentChildren = findRunParentChildren(paragraphNode, runNode);\n if (parentChildren) {\n const idx = parentChildren.indexOf(runNode);\n if (idx !== -1) {\n parentChildren.splice(idx, 1, ...newRuns);\n return;\n }\n }\n\n // Fallback: just set text on the existing run (shouldn't happen)\n const plainText = segments.map((s) => s.text).join(\"\");\n setRunText(runNode, plainText);\n}\n\n// =============================================================================\n// Internal: run text manipulation\n// =============================================================================\n\n/**\n * Set the text content of a w:r run node.\n * Finds or creates the w:t element and sets its text.\n * Escapes XML special characters and manages xml:space=\"preserve\".\n *\n * If the text contains newlines (\\n), they are converted to w:br\n * (soft line break) elements within the run. This allows replacement\n * text to contain line breaks. A run that already holds a soft break\n * (or several w:t elements) is rebuilt as well, so the old break never\n * survives next to the new text.\n */\nfunction setRunText(runNode: FXPNode, text: string): void {\n if (text.includes(\"\\n\") || runHasMixedContent(runNode)) {\n // Rebuild the run's content children with w:t/w:br pairs\n setRunTextWithBreaks(runNode, text);\n return;\n }\n\n const wtNode = findTextNode(runNode);\n if (wtNode) {\n const tChildren = (wtNode as Record<string, unknown>)[\"w:t\"];\n if (Array.isArray(tChildren)) {\n // Find or create the text node\n const textNode = tChildren.find(\n (c: Record<string, unknown>) => \"#text\" in c,\n );\n if (textNode) {\n setTextContent(textNode as FXPNode, text);\n } else {\n tChildren.push({ \"#text\": escapeXmlText(text) });\n }\n }\n\n // Update xml:space=\"preserve\" if needed\n if (needsSpacePreserve(text)) {\n setAttr(wtNode, \"xml:space\", \"preserve\");\n }\n } else {\n // No w:t exists — create one\n const children = getChildren(runNode);\n const newWt: FXPNode = {\n \"w:t\": [{ \"#text\": escapeXmlText(text) } as unknown as FXPNode],\n };\n if (needsSpacePreserve(text)) {\n setAttr(newWt, \"xml:space\", \"preserve\");\n }\n children.push(newWt);\n }\n}\n\n/**\n * True if the run's text cannot be updated by editing a single w:t: it holds\n * a soft line break, or more than one w:t element.\n */\nfunction runHasMixedContent(runNode: FXPNode): boolean {\n let textNodes = 0;\n for (const child of getChildren(runNode)) {\n if (\"w:br\" in child) {\n const brType = getAttr(child, \"w:type\");\n if (!brType || brType === \"textWrapping\") return true;\n } else if (\"w:t\" in child) {\n textNodes++;\n }\n }\n return textNodes > 1;\n}\n\n/**\n * Replace a run's text content with text containing line breaks.\n *\n * Splits text on \\n and interleaves w:t and w:br elements. Preserves the\n * run's w:rPr and any other non-text children (page/column breaks, tabs,\n * rendering hints): those that came before the run's text stay before the\n * new text, those that came after it stay after, so a trailing page break\n * still follows the replacement.\n */\nfunction setRunTextWithBreaks(runNode: FXPNode, text: string): void {\n const children = getChildren(runNode);\n\n const isTextContent = (c: FXPNode): boolean => {\n if (\"w:t\" in c) return true;\n if (\"w:br\" in c) {\n const brType = getAttr(c, \"w:type\");\n return !brType || brType === \"textWrapping\";\n }\n return false;\n };\n\n const rPr: FXPNode[] = [];\n const leading: FXPNode[] = [];\n const trailing: FXPNode[] = [];\n let seenText = false;\n for (const c of children) {\n if (\"w:rPr\" in c) rPr.push(c);\n else if (isTextContent(c)) seenText = true;\n else (seenText ? trailing : leading).push(c);\n }\n\n children.length = 0;\n children.push(...rPr, ...leading);\n\n const segments = text.split(\"\\n\");\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n if (seg.length > 0 || i === 0) {\n // Add w:t element for this segment\n const wt: FXPNode = {\n \"w:t\": [{ \"#text\": escapeXmlText(seg) } as unknown as FXPNode],\n };\n if (needsSpacePreserve(seg)) {\n setAttr(wt, \"xml:space\", \"preserve\");\n }\n children.push(wt);\n }\n // Add w:br between segments (not after the last one)\n if (i < segments.length - 1) {\n children.push({ \"w:br\": [] as FXPNode[] });\n }\n }\n\n children.push(...trailing);\n}\n","/**\n * Table operations: add_row, delete_row, replace_cell_text.\n *\n * Table edits work by:\n * - add_row: clone an existing row (preserving cell styles), replace text\n * - delete_row: remove the w:tr node from the w:tbl\n * - replace_cell_text: delegate to text-ops on the paragraph inside the cell\n */\n\nimport type {\n NormalizedTable,\n NormalizedParagraph,\n AddTableRowPatch,\n DeleteTableRowPatch,\n ReplaceCellTextPatch,\n FXPNode,\n} from \"../types.js\";\nimport {\n cloneNode,\n findChildren,\n getChildren,\n insertAfter,\n removeChild,\n findChild,\n createElement,\n escapeXmlText,\n needsSpacePreserve,\n} from \"../xml/helpers.js\";\nimport { executeReplaceText } from \"./text-ops.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport { createTrPrIns, createTrPrDel } from \"./revision.js\";\n\n/**\n * Execute an add_table_row patch.\n *\n * Clones an existing row to preserve styling, then replaces cell text.\n *\n * @returns the new w:tr node (so the caller can re-stamp paragraph ids\n * that were duplicated by cloning)\n */\nexport function executeAddTableRow(\n table: NormalizedTable,\n patch: AddTableRowPatch,\n revCtx?: RevisionContext,\n): FXPNode {\n if (table.rows.length === 0) {\n throw new Error(`Table \"${table.id}\" has no rows to clone from`);\n }\n\n if (patch.afterRow < -1 || patch.afterRow >= table.rows.length) {\n throw new Error(\n `Invalid afterRow ${patch.afterRow} for table \"${table.id}\" with ${table.rows.length} rows`,\n );\n }\n\n // Pick a row to clone (the row we're inserting after, or the first row)\n const sourceRowIdx = patch.afterRow >= 0 ? patch.afterRow : 0;\n const sourceRow = table.rows[sourceRowIdx]!;\n\n // Verify cell count matches\n if (patch.cells.length !== sourceRow.cells.length) {\n throw new Error(\n `Cell count mismatch: patch has ${patch.cells.length} cells, ` +\n `but row has ${sourceRow.cells.length} cells`,\n );\n }\n\n // Clone the source row\n const newRowNode = cloneNode(sourceRow.xmlRef);\n\n // Replace text in each cell of the cloned row\n const tcNodes = findChildren(newRowNode, \"w:tc\");\n for (let i = 0; i < tcNodes.length; i++) {\n const tcNode = tcNodes[i]!;\n const cellText = patch.cells[i]!;\n\n // Find the first paragraph in the cell\n const pNode = findChild(tcNode, \"w:p\");\n if (pNode) {\n // Clear existing runs and add new text\n const pChildren = getChildren(pNode);\n\n // Remove all existing w:r runs (including those wrapped in w:ins from tracked changes)\n const toRemove = pChildren.filter(\n (c) => \"w:r\" in c || \"w:ins\" in c || \"w:del\" in c,\n );\n for (const node of toRemove) {\n const idx = pChildren.indexOf(node);\n if (idx !== -1) pChildren.splice(idx, 1);\n }\n\n // Add a new run with the cell text\n const wtNode: Record<string, unknown> = {\n \"w:t\": [{ \"#text\": escapeXmlText(cellText) } as any],\n };\n if (needsSpacePreserve(cellText)) {\n if (!wtNode[\":@\"]) wtNode[\":@\"] = {};\n (wtNode[\":@\"] as Record<string, string>)[\"@_xml:space\"] = \"preserve\";\n }\n const newRun = {\n \"w:r\": [wtNode as any],\n };\n pChildren.push(newRun);\n }\n }\n\n // Insert the new row into the table XML\n const tableChildren = getChildren(table.xmlRef);\n if (patch.afterRow === -1) {\n // Insert at the beginning, after tblPr and tblGrid\n const firstTr = tableChildren.find((c) => \"w:tr\" in c);\n if (firstTr) {\n const idx = tableChildren.indexOf(firstTr);\n tableChildren.splice(idx, 0, newRowNode);\n } else {\n tableChildren.push(newRowNode);\n }\n } else {\n insertAfter(table.xmlRef, sourceRow.xmlRef, newRowNode);\n }\n\n // If tracking revisions, add w:ins to the new row's w:trPr\n if (revCtx) {\n const rowChildren = getChildren(newRowNode);\n let trPr = rowChildren.find((c) => \"w:trPr\" in c);\n if (!trPr) {\n // Create w:trPr and insert at the beginning\n trPr = createElement(\"w:trPr\", []);\n rowChildren.unshift(trPr);\n }\n const trPrChildren = getChildren(trPr);\n trPrChildren.push(createTrPrIns(revCtx));\n }\n\n return newRowNode;\n}\n\n/**\n * Execute a delete_table_row patch.\n */\nexport function executeDeleteTableRow(\n table: NormalizedTable,\n patch: DeleteTableRowPatch,\n revCtx?: RevisionContext,\n): void {\n if (patch.row < 0 || patch.row >= table.rows.length) {\n throw new Error(\n `Invalid row index ${patch.row} for table \"${table.id}\" with ${table.rows.length} rows`,\n );\n }\n\n const row = table.rows[patch.row]!;\n\n if (revCtx) {\n // With revision tracking: keep the row, add w:del to w:trPr\n const rowChildren = getChildren(row.xmlRef);\n let trPr = rowChildren.find((c) => \"w:trPr\" in c);\n if (!trPr) {\n trPr = createElement(\"w:trPr\", []);\n rowChildren.unshift(trPr);\n }\n const trPrChildren = getChildren(trPr);\n trPrChildren.push(createTrPrDel(revCtx));\n } else {\n // Without revision tracking: remove the row from XML\n removeChild(table.xmlRef, row.xmlRef);\n }\n}\n\n/**\n * Execute a replace_cell_text patch.\n *\n * This delegates to the text replace operation on the targeted paragraph.\n */\nexport function executeReplaceCellText(\n paragraph: NormalizedParagraph,\n patch: ReplaceCellTextPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n executeReplaceText(paragraph, {\n type: \"replace_text\",\n target: patch.target,\n find: patch.find,\n replace: patch.replace,\n occurrence: patch.occurrence,\n }, revCtx, markers);\n}\n\n// =============================================================================\n// Build a w:tbl FXP node from scratch\n// =============================================================================\n\n/**\n * Build a complete w:tbl FXP node with TableGrid style and single borders.\n *\n * @param headers - Optional header row cells\n * @param rows - Data rows (each sub-array is one row of cell values)\n * @returns A w:tbl FXP node ready for insertion into w:body\n */\nexport function buildTableNode(\n headers: string[] | undefined,\n rows: string[][],\n): FXPNode {\n const colCount =\n headers?.length ?? (rows.length > 0 ? rows[0]!.length : 0);\n\n // w:tblPr — table properties with TableGrid style and borders\n const borderAttrs = { \"w:val\": \"single\", \"w:sz\": \"4\", \"w:space\": \"0\", \"w:color\": \"auto\" };\n const tblBorders = createElement(\"w:tblBorders\", [\n createElement(\"w:top\", [], borderAttrs),\n createElement(\"w:left\", [], borderAttrs),\n createElement(\"w:bottom\", [], borderAttrs),\n createElement(\"w:right\", [], borderAttrs),\n createElement(\"w:insideH\", [], borderAttrs),\n createElement(\"w:insideV\", [], borderAttrs),\n ]);\n\n const tblPr = createElement(\"w:tblPr\", [\n createElement(\"w:tblStyle\", [], { \"w:val\": \"TableGrid\" }),\n createElement(\"w:tblW\", [], { \"w:w\": \"0\", \"w:type\": \"auto\" }),\n tblBorders,\n createElement(\"w:tblLook\", [], {\n \"w:val\": \"04A0\",\n \"w:firstRow\": \"1\",\n \"w:lastRow\": \"0\",\n \"w:firstColumn\": \"1\",\n \"w:lastColumn\": \"0\",\n \"w:noHBand\": \"0\",\n \"w:noVBand\": \"1\",\n }),\n ]);\n\n // w:tblGrid — one gridCol per column\n const gridCols: FXPNode[] = [];\n for (let i = 0; i < colCount; i++) {\n gridCols.push(createElement(\"w:gridCol\", []));\n }\n const tblGrid = createElement(\"w:tblGrid\", gridCols);\n\n // Build rows\n const allRows: FXPNode[] = [];\n\n if (headers) {\n allRows.push(buildTableRowNode(headers));\n }\n\n for (const row of rows) {\n allRows.push(buildTableRowNode(row));\n }\n\n return createElement(\"w:tbl\", [tblPr, tblGrid, ...allRows]);\n}\n\n/**\n * Build a single w:tr node from cell text values.\n */\nfunction buildTableRowNode(cells: string[]): FXPNode {\n const tcNodes: FXPNode[] = [];\n for (const cellText of cells) {\n const wt: FXPNode = {\n \"w:t\": [{ \"#text\": escapeXmlText(cellText) } as any],\n };\n if (needsSpacePreserve(cellText)) {\n if (!wt[\":@\"]) wt[\":@\"] = {};\n (wt[\":@\"] as Record<string, string>)[\"@_xml:space\"] = \"preserve\";\n }\n const run: FXPNode = { \"w:r\": [wt] };\n const para: FXPNode = { \"w:p\": [run] };\n const tc = createElement(\"w:tc\", [\n createElement(\"w:tcPr\", [\n createElement(\"w:tcW\", [], { \"w:w\": \"0\", \"w:type\": \"auto\" }),\n ]),\n para,\n ]);\n tcNodes.push(tc);\n }\n return createElement(\"w:tr\", tcNodes);\n}\n","/**\n * Formatting operations: apply_formatting patch execution.\n *\n * Manipulates inline w:rPr (run properties) on targeted runs.\n * Never touches w:pPr, styles.xml, or any global definitions.\n *\n * The approach:\n * 1. If `find` is provided: locate the substring via findOccurrence + resolveRunRanges,\n * split runs to isolate the match, then apply formatting to isolated runs.\n * 2. If `find` is omitted: apply formatting to all runs in the paragraph.\n *\n * Formatting semantics:\n * - `true` / value → ensure the property is present (add/update w:rPr child)\n * - `false` / `null` → remove the property from w:rPr\n * - Property not mentioned → leave unchanged\n */\n\nimport type {\n NormalizedParagraph,\n ApplyFormattingPatch,\n FormattingUpdate,\n FXPNode,\n} from \"../types.js\";\nimport { findOccurrence, resolveRunRanges } from \"../mapping/text-to-runs.js\";\nimport { splitRunsForMatch } from \"./run-splitter.js\";\nimport {\n findChild,\n getChildren,\n createElement,\n setAttr,\n removeAttr,\n getAttrs,\n removeChild,\n cloneNode,\n} from \"../xml/helpers.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport { createRPrChange } from \"./revision.js\";\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Execute an apply_formatting patch on a paragraph.\n */\nexport function executeApplyFormatting(\n paragraph: NormalizedParagraph,\n patch: ApplyFormattingPatch,\n revCtx?: RevisionContext,\n): void {\n if (patch.find !== undefined && patch.find.length > 0) {\n applyFormattingToSubstring(paragraph, patch.find, patch.occurrence ?? 1, patch.formatting, revCtx);\n } else {\n applyFormattingToAllRuns(paragraph, patch.formatting, revCtx);\n }\n}\n\n// =============================================================================\n// Internal: target selection\n// =============================================================================\n\n/**\n * Apply formatting to a substring within a paragraph.\n * Isolates the matched runs via splitting, then applies formatting.\n */\nfunction applyFormattingToSubstring(\n paragraph: NormalizedParagraph,\n find: string,\n occurrence: number,\n formatting: FormattingUpdate,\n revCtx?: RevisionContext,\n): void {\n const matchStart = findOccurrence(paragraph.text, find, occurrence);\n if (matchStart === -1) {\n throw new Error(\n `Text \"${find}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n const matchEnd = matchStart + find.length;\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchEnd);\n\n if (ranges.length === 0) {\n throw new Error(\n `No runs found for text range [${matchStart}, ${matchEnd}] in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Split runs to isolate the matched text, then apply formatting to each\n const matchedRunNodes = splitRunsForMatch(paragraph.xmlRef, ranges);\n for (const runNode of matchedRunNodes) {\n applyFormattingToRunNode(runNode, formatting, revCtx);\n }\n}\n\n/**\n * Apply formatting to all runs in a paragraph.\n */\nfunction applyFormattingToAllRuns(\n paragraph: NormalizedParagraph,\n formatting: FormattingUpdate,\n revCtx?: RevisionContext,\n): void {\n for (const run of paragraph.runs) {\n applyFormattingToRunNode(run.xmlRef, formatting, revCtx);\n }\n}\n\n// =============================================================================\n// Internal: w:rPr manipulation\n// =============================================================================\n\n/**\n * Apply a FormattingUpdate to a single w:r run node's w:rPr.\n *\n * When revCtx is provided, clones the original w:rPr before modifying,\n * then adds w:rPrChange with the old snapshot.\n */\nfunction applyFormattingToRunNode(\n runNode: FXPNode,\n formatting: FormattingUpdate,\n revCtx?: RevisionContext,\n): void {\n // Only create w:rPr if we're actually adding properties\n const hasAdds = Object.entries(formatting).some(\n ([, v]) => v !== null && v !== false && v !== undefined,\n );\n\n let rPr = findChild(runNode, \"w:rPr\");\n\n // If we only have removes and there's no rPr, nothing to do\n if (!rPr && !hasAdds) return;\n\n // Snapshot the old rPr before modifications (for revision tracking)\n const oldRPr = revCtx && rPr ? cloneNode(rPr) : undefined;\n\n // Create rPr if needed (must be first child of w:r)\n if (!rPr) {\n rPr = createElement(\"w:rPr\");\n const children = getChildren(runNode);\n children.unshift(rPr);\n }\n\n // Apply each formatting property\n if (formatting.bold !== undefined) {\n applyBooleanProp(rPr, \"w:b\", formatting.bold);\n }\n if (formatting.italic !== undefined) {\n applyBooleanProp(rPr, \"w:i\", formatting.italic);\n }\n if (formatting.strikethrough !== undefined) {\n applyBooleanProp(rPr, \"w:strike\", formatting.strikethrough);\n }\n if (formatting.underline !== undefined) {\n applyAttrProp(rPr, \"w:u\", \"w:val\", formatting.underline);\n }\n if (formatting.fontSize !== undefined) {\n applyAttrProp(rPr, \"w:sz\", \"w:val\", formatting.fontSize !== null ? String(formatting.fontSize) : null);\n }\n if (formatting.fontFamily !== undefined) {\n applyFontFamily(rPr, formatting.fontFamily);\n }\n if (formatting.color !== undefined) {\n applyAttrProp(rPr, \"w:color\", \"w:val\", formatting.color);\n }\n if (formatting.highlight !== undefined) {\n applyAttrProp(rPr, \"w:highlight\", \"w:val\", formatting.highlight);\n }\n\n // Add w:rPrChange with the old snapshot (revision tracking)\n if (revCtx) {\n // Remove any existing w:rPrChange first\n const existingChange = findChild(rPr, \"w:rPrChange\");\n if (existingChange) {\n removeChild(rPr, existingChange);\n }\n\n const oldRPrForChange = oldRPr ?? createElement(\"w:rPr\");\n const rPrChange = createRPrChange(revCtx, oldRPrForChange);\n getChildren(rPr).push(rPrChange);\n }\n}\n\n/**\n * Set or remove a boolean property element (e.g., w:b, w:i, w:strike).\n * - `true` → ensure the element exists (no attributes needed)\n * - `false` / `null` → remove the element if present\n */\nfunction applyBooleanProp(\n rPr: FXPNode,\n tagName: string,\n value: boolean | null,\n): void {\n const existing = findChild(rPr, tagName);\n\n if (value === true) {\n if (!existing) {\n getChildren(rPr).push(createElement(tagName));\n }\n } else {\n // false or null → remove\n if (existing) {\n removeChild(rPr, existing);\n }\n }\n}\n\n/**\n * Set or remove a property element that carries its value in an attribute.\n * e.g., <w:u w:val=\"single\"/>, <w:sz w:val=\"24\"/>, <w:color w:val=\"FF0000\"/>\n *\n * - non-null string → create/update element with the attribute\n * - `null` → remove the element\n */\nfunction applyAttrProp(\n rPr: FXPNode,\n tagName: string,\n attrName: string,\n value: string | null,\n): void {\n const existing = findChild(rPr, tagName);\n\n if (value !== null) {\n if (existing) {\n setAttr(existing, attrName, value);\n } else {\n const el = createElement(tagName, [], { [attrName]: value });\n getChildren(rPr).push(el);\n }\n } else {\n // null → remove\n if (existing) {\n removeChild(rPr, existing);\n }\n }\n}\n\n/**\n * Set or remove font family.\n * In OOXML, font family is set via w:rFonts with multiple attributes:\n * <w:rFonts w:ascii=\"Arial\" w:hAnsi=\"Arial\"/>\n *\n * - non-null string → create/update w:rFonts with w:ascii and w:hAnsi\n * - `null` → remove only w:ascii and w:hAnsi attributes (preserving w:eastAsia,\n * w:cs, theme attributes, etc.). If no attributes remain, remove the element.\n */\nfunction applyFontFamily(rPr: FXPNode, value: string | null): void {\n const existing = findChild(rPr, \"w:rFonts\");\n\n if (value !== null) {\n if (existing) {\n setAttr(existing, \"w:ascii\", value);\n setAttr(existing, \"w:hAnsi\", value);\n } else {\n const el = createElement(\"w:rFonts\", [], {\n \"w:ascii\": value,\n \"w:hAnsi\": value,\n });\n getChildren(rPr).push(el);\n }\n } else {\n if (existing) {\n removeAttr(existing, \"w:ascii\");\n removeAttr(existing, \"w:hAnsi\");\n // Only remove the element entirely if no attributes remain\n if (Object.keys(getAttrs(existing)).length === 0) {\n removeChild(rPr, existing);\n }\n }\n }\n}\n","/**\n * Style operations: apply_style patch execution.\n *\n * Re-points paragraphs or runs to pre-existing styles in styles.xml.\n * Never creates, modifies, or deletes style definitions.\n *\n * Two modes:\n * 1. Paragraph style (no `find`): Sets w:pPr > w:pStyle on the paragraph.\n * The style must be type \"paragraph\" in styles.xml.\n *\n * 2. Character style (with `find`): Isolates matched runs via splitting,\n * then sets w:rPr > w:rStyle on each matched run.\n * The style must be type \"character\" in styles.xml.\n */\n\nimport type {\n NormalizedParagraph,\n NormalizedDocument,\n ApplyStylePatch,\n FXPNode,\n} from \"../types.js\";\nimport { findOccurrence, resolveRunRanges } from \"../mapping/text-to-runs.js\";\nimport { splitRunsForMatch } from \"./run-splitter.js\";\nimport {\n findChild,\n getChildren,\n createElement,\n setAttr,\n removeChild,\n cloneNode,\n} from \"../xml/helpers.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport { createPPrChange, createRPrChange } from \"./revision.js\";\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Execute an apply_style patch on a paragraph.\n *\n * Validates that the style exists and is the correct type before applying.\n */\nexport function executeApplyStyle(\n paragraph: NormalizedParagraph,\n patch: ApplyStylePatch,\n doc: NormalizedDocument,\n revCtx?: RevisionContext,\n): void {\n const styleInfo = doc.styles.find((s) => s.id === patch.style);\n if (!styleInfo) {\n throw new Error(\n `Style \"${patch.style}\" not found in document styles.xml`,\n );\n }\n\n if (patch.find !== undefined && patch.find.length > 0) {\n // Character style mode\n if (styleInfo.type !== \"character\") {\n throw new Error(\n `Style \"${patch.style}\" is type \"${styleInfo.type}\", but character style is required when \"find\" is specified`,\n );\n }\n applyCharacterStyle(paragraph, patch.find, patch.occurrence ?? 1, patch.style, revCtx);\n } else {\n // Paragraph style mode\n if (styleInfo.type !== \"paragraph\") {\n throw new Error(\n `Style \"${patch.style}\" is type \"${styleInfo.type}\", but paragraph style is required when \"find\" is omitted`,\n );\n }\n applyParagraphStyle(paragraph, patch.style, revCtx);\n }\n}\n\n// =============================================================================\n// Internal: paragraph style\n// =============================================================================\n\n/**\n * Apply a paragraph style by setting w:pPr > w:pStyle.\n *\n * If w:pPr doesn't exist, it's created as the first child of w:p.\n * If w:pStyle already exists, its w:val is updated.\n * If w:pStyle doesn't exist, it's created as the first child of w:pPr.\n *\n * When revCtx is provided, adds w:pPrChange with the old pPr snapshot.\n */\nfunction applyParagraphStyle(\n paragraph: NormalizedParagraph,\n styleId: string,\n revCtx?: RevisionContext,\n): void {\n const paraNode = paragraph.xmlRef;\n\n // Snapshot old pPr before modification (for revision tracking)\n let pPr = findChild(paraNode, \"w:pPr\");\n const oldPPr = revCtx && pPr ? cloneNode(pPr) : undefined;\n\n // Ensure w:pPr exists (must be first child of w:p)\n if (!pPr) {\n pPr = createElement(\"w:pPr\");\n const children = getChildren(paraNode);\n children.unshift(pPr);\n }\n\n // Set or update w:pStyle\n let pStyle = findChild(pPr, \"w:pStyle\");\n if (pStyle) {\n setAttr(pStyle, \"w:val\", styleId);\n } else {\n pStyle = createElement(\"w:pStyle\", [], { \"w:val\": styleId });\n // w:pStyle should be first child of w:pPr\n getChildren(pPr).unshift(pStyle);\n }\n\n // Add w:pPrChange with the old snapshot (revision tracking)\n if (revCtx) {\n // Remove any existing w:pPrChange first\n const existingChange = findChild(pPr, \"w:pPrChange\");\n if (existingChange) {\n removeChild(pPr, existingChange);\n }\n\n const oldPPrForChange = oldPPr ?? createElement(\"w:pPr\");\n const pPrChange = createPPrChange(revCtx, oldPPrForChange);\n getChildren(pPr).push(pPrChange);\n }\n}\n\n// =============================================================================\n// Internal: character style\n// =============================================================================\n\n/**\n * Apply a character style to a substring by setting w:rPr > w:rStyle\n * on each matched run.\n *\n * Uses the same find/split machinery as formatting patches.\n * When revCtx is provided, adds w:rPrChange with the old rPr snapshot.\n */\nfunction applyCharacterStyle(\n paragraph: NormalizedParagraph,\n find: string,\n occurrence: number,\n styleId: string,\n revCtx?: RevisionContext,\n): void {\n const matchStart = findOccurrence(paragraph.text, find, occurrence);\n if (matchStart === -1) {\n throw new Error(\n `Text \"${find}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n const matchEnd = matchStart + find.length;\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchEnd);\n\n if (ranges.length === 0) {\n throw new Error(\n `No runs found for text range [${matchStart}, ${matchEnd}] in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Split runs to isolate the matched text\n const matchedRunNodes = splitRunsForMatch(paragraph.xmlRef, ranges);\n\n // Apply w:rStyle to each matched run\n for (const runNode of matchedRunNodes) {\n applyRStyleToRunNode(runNode, styleId, revCtx);\n }\n}\n\n/**\n * Set w:rPr > w:rStyle on a single run node.\n *\n * Creates w:rPr if needed. If w:rStyle already exists, updates it.\n * Otherwise creates it as the first child of w:rPr.\n * When revCtx is provided, adds w:rPrChange with the old rPr snapshot.\n */\nfunction applyRStyleToRunNode(\n runNode: FXPNode,\n styleId: string,\n revCtx?: RevisionContext,\n): void {\n // Snapshot old rPr before modification (for revision tracking)\n let rPr = findChild(runNode, \"w:rPr\");\n const oldRPr = revCtx && rPr ? cloneNode(rPr) : undefined;\n\n // Ensure w:rPr exists (must be first child of w:r)\n if (!rPr) {\n rPr = createElement(\"w:rPr\");\n const children = getChildren(runNode);\n children.unshift(rPr);\n }\n\n // Remove any existing w:rStyle first\n const existingRStyle = findChild(rPr, \"w:rStyle\");\n if (existingRStyle) {\n removeChild(rPr, existingRStyle);\n }\n\n // Create new w:rStyle as first child of w:rPr\n const rStyle = createElement(\"w:rStyle\", [], { \"w:val\": styleId });\n getChildren(rPr).unshift(rStyle);\n\n // Add w:rPrChange with the old snapshot (revision tracking)\n if (revCtx) {\n // Remove any existing w:rPrChange first\n const existingChange = findChild(rPr, \"w:rPrChange\");\n if (existingChange) {\n removeChild(rPr, existingChange);\n }\n\n const oldRPrForChange = oldRPr ?? createElement(\"w:rPr\");\n const rPrChange = createRPrChange(revCtx, oldRPrForChange);\n getChildren(rPr).push(rPrChange);\n }\n}\n","/**\n * Field code placeholder support for OOXML.\n *\n * Supports {{FIELD}} syntax in text content. When encountered, emits\n * proper w:fldChar / w:instrText XML instead of plain text.\n *\n * Supported fields:\n * - {{PAGE}} → PAGE (current page number)\n * - {{NUMPAGES}} → NUMPAGES (total page count)\n * - {{DATE}} → DATE (current date)\n * - {{TIME}} → TIME (current time)\n * - {{FILENAME}} → FILENAME (document filename)\n */\n\nimport type { FXPNode } from \"../types.js\";\nimport {\n createElement,\n createRun,\n getChildren,\n setAttr,\n} from \"../xml/helpers.js\";\nimport type { FormattedSegment } from \"./markdown-parser.js\";\nimport { parseFormattedText } from \"./markdown-parser.js\";\nimport { createFormattedRuns } from \"./text-ops.js\";\n\n/** Supported OOXML field codes. */\nconst SUPPORTED_FIELDS = new Set([\n \"PAGE\",\n \"NUMPAGES\",\n \"DATE\",\n \"TIME\",\n \"FILENAME\",\n]);\n\n/** A parsed segment: either plain text or a field code. */\nexport type TextSegment =\n | { kind: \"text\"; text: string }\n | { kind: \"field\"; fieldCode: string };\n\n/**\n * Parse text containing {{FIELD}} placeholders into segments.\n *\n * Example: \"Page {{PAGE}} of {{NUMPAGES}}\" →\n * [{ kind: \"text\", text: \"Page \" },\n * { kind: \"field\", fieldCode: \"PAGE\" },\n * { kind: \"text\", text: \" of \" },\n * { kind: \"field\", fieldCode: \"NUMPAGES\" }]\n */\nexport function parseFieldPlaceholders(text: string): TextSegment[] {\n const segments: TextSegment[] = [];\n const regex = /\\{\\{(\\w+)\\}\\}/g;\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(text)) !== null) {\n const fieldName = match[1]!;\n\n // Only treat as field if it's a supported field code\n if (!SUPPORTED_FIELDS.has(fieldName)) {\n continue;\n }\n\n // Add preceding plain text\n if (match.index > lastIndex) {\n segments.push({ kind: \"text\", text: text.slice(lastIndex, match.index) });\n }\n\n segments.push({ kind: \"field\", fieldCode: fieldName });\n lastIndex = match.index + match[0].length;\n }\n\n // Add trailing text\n if (lastIndex < text.length) {\n segments.push({ kind: \"text\", text: text.slice(lastIndex) });\n }\n\n // If no fields were found, return a single text segment\n if (segments.length === 0) {\n segments.push({ kind: \"text\", text });\n }\n\n return segments;\n}\n\n/**\n * Check if text contains any field placeholders.\n */\nexport function hasFieldPlaceholders(text: string): boolean {\n const regex = /\\{\\{(\\w+)\\}\\}/g;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n if (SUPPORTED_FIELDS.has(match[1]!)) return true;\n }\n return false;\n}\n\n/**\n * Pick a character that does not occur in `text` to stand in for a field\n * while inline markers are parsed around it. U+FFFC (object replacement\n * character) is the first choice; if the text happens to contain it, a\n * private-use character that the text does not contain is used instead, so\n * literal text is never mistaken for a field.\n */\nfunction pickFieldPlaceholder(text: string): string {\n if (!text.includes(\"\\uFFFC\")) return \"\\uFFFC\";\n for (let cp = 0xe000; cp <= 0xf8ff; cp++) {\n const ch = String.fromCharCode(cp);\n if (!text.includes(ch)) return ch;\n }\n throw new Error(\"Cannot find a placeholder character for field codes\");\n}\n\n/**\n * Build FXP run nodes for a text string that may contain field placeholders\n * and/or inline markdown formatting.\n *\n * Formatting state is tracked across field boundaries so that markers like\n * `**{{PAGE}}**` correctly apply bold to the field runs rather than emitting\n * literal `**` characters.\n *\n * Algorithm:\n * 1. Parse field placeholders to get text/field segments.\n * 2. Replace every field with a placeholder character (one the text does not\n * contain) and run the GFM parser on the whole string, so markers open and\n * close across fields and the placeholder (a non-word character) keeps\n * `_{{PAGE}}_` a valid italic.\n * 3. Walk the formatted segments: text becomes runs with that formatting,\n * each placeholder becomes the field-code run sequence carrying the\n * formatting active at that point.\n *\n * @param text - Text possibly containing {{FIELD}} placeholders and/or formatting markers\n * @param rPrNode - Optional run properties to clone onto each run\n * @param markers - Parse inline formatting markers and escapes (default true);\n * with `false` the text is written verbatim\n * @returns Array of w:r FXP nodes\n */\nexport function buildFieldRuns(text: string, rPrNode?: FXPNode, markers: boolean = true): FXPNode[] {\n const segments = parseFieldPlaceholders(text);\n\n // Fast path: no field placeholders — just parse formatting directly\n if (segments.length === 1 && segments[0]!.kind === \"text\") {\n return emitTextRuns(parseFormattedText(segments[0]!.text, markers), rPrNode);\n }\n\n const fieldCodes = segments\n .filter((s): s is { kind: \"field\"; fieldCode: string } => s.kind === \"field\")\n .map((s) => s.fieldCode);\n const placeholder = pickFieldPlaceholder(text);\n const combined = segments\n .map((s) => (s.kind === \"text\" ? s.text : placeholder))\n .join(\"\");\n const formatted = parseFormattedText(combined, markers);\n\n const runs: FXPNode[] = [];\n let pending: FormattedSegment[] = [];\n let fieldIdx = 0;\n const flushPending = (): void => {\n if (pending.length > 0) {\n runs.push(...emitTextRuns(pending, rPrNode));\n pending = [];\n }\n };\n\n for (const seg of formatted) {\n const parts = seg.text.split(placeholder);\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!;\n if (part.length > 0) pending.push({ ...seg, text: part });\n if (i < parts.length - 1) {\n flushPending();\n const fieldRPr = buildFormattedRPr(rPrNode, {\n bold: !!seg.bold,\n italic: !!seg.italic,\n strikethrough: !!seg.strikethrough,\n underline: !!seg.underline,\n });\n runs.push(...buildFieldCodeRuns(fieldCodes[fieldIdx++]!, fieldRPr));\n }\n }\n }\n flushPending();\n\n return runs;\n}\n\n/**\n * Turn formatted segments into runs: one formatted run per segment when any\n * segment carries formatting, otherwise a single plain run.\n */\nfunction emitTextRuns(segments: FormattedSegment[], rPrNode?: FXPNode): FXPNode[] {\n const hasFormatting = segments.some(\n (s) => s.bold || s.italic || s.strikethrough || s.underline,\n );\n if (hasFormatting) {\n return createFormattedRuns(segments, rPrNode);\n }\n const plainText = segments.map((s) => s.text).join(\"\");\n return plainText.length > 0 ? [createRun(plainText, rPrNode)] : [];\n}\n\n/**\n * Build the w:fldChar / w:instrText run sequence for a field code.\n */\nfunction buildFieldCodeRuns(fieldCode: string, rPrNode?: FXPNode): FXPNode[] {\n const cloneRPr = (): FXPNode[] => {\n if (!rPrNode) return [];\n return [JSON.parse(JSON.stringify(rPrNode)) as FXPNode];\n };\n\n // Begin run\n const beginRun: FXPNode = {\n \"w:r\": [\n ...cloneRPr(),\n createElement(\"w:fldChar\", [], { \"w:fldCharType\": \"begin\" }),\n ],\n };\n\n // Instruction run\n const instrText = ` ${fieldCode} `;\n const instrTextNode: FXPNode = {\n \"w:instrText\": [{ \"#text\": instrText } as unknown as FXPNode],\n };\n setAttr(instrTextNode, \"xml:space\", \"preserve\");\n\n const instrRun: FXPNode = {\n \"w:r\": [...cloneRPr(), instrTextNode],\n };\n\n // Separate run\n const separateRun: FXPNode = {\n \"w:r\": [\n ...cloneRPr(),\n createElement(\"w:fldChar\", [], { \"w:fldCharType\": \"separate\" }),\n ],\n };\n\n // End run\n const endRun: FXPNode = {\n \"w:r\": [\n ...cloneRPr(),\n createElement(\"w:fldChar\", [], { \"w:fldCharType\": \"end\" }),\n ],\n };\n\n return [beginRun, instrRun, separateRun, endRun];\n}\n\n/**\n * Build a w:rPr node that merges the base rPrNode with GFM formatting flags.\n * Returns undefined if neither base nor flags produce any properties.\n */\nfunction buildFormattedRPr(\n baseRPr: FXPNode | undefined,\n fmt: { bold: boolean; italic: boolean; strikethrough: boolean; underline: boolean },\n): FXPNode | undefined {\n const needsFormatting = fmt.bold || fmt.italic || fmt.strikethrough || fmt.underline;\n if (!baseRPr && !needsFormatting) return undefined;\n\n // Clone the base rPr (or create a fresh one)\n const rPr: FXPNode = baseRPr\n ? (JSON.parse(JSON.stringify(baseRPr)) as FXPNode)\n : createElement(\"w:rPr\", []);\n\n if (!needsFormatting) return rPr;\n\n const children = getChildren(rPr);\n\n if (fmt.bold && !children.some((c) => \"w:b\" in c)) {\n children.push(createElement(\"w:b\", []));\n }\n if (fmt.italic && !children.some((c) => \"w:i\" in c)) {\n children.push(createElement(\"w:i\", []));\n }\n if (fmt.strikethrough && !children.some((c) => \"w:strike\" in c)) {\n children.push(createElement(\"w:strike\", []));\n }\n if (fmt.underline && !children.some((c) => \"w:u\" in c)) {\n children.push(createElement(\"w:u\", [], { \"w:val\": \"single\" }));\n }\n\n return rPr;\n}\n","/**\n * Header/footer structural operations: add and remove headers/footers.\n *\n * These ops create/remove XML parts, manage relationships in\n * document.xml.rels and [Content_Types].xml, and mutate sectPr\n * references in the document body.\n */\n\nimport type {\n NormalizedDocument,\n AddHeaderPatch,\n AddFooterPatch,\n RemoveHeaderPatch,\n RemoveFooterPatch,\n FXPNode,\n HeaderFooterVariant,\n} from \"../types.js\";\nimport {\n createElement,\n findChild,\n findChildren,\n getChildren,\n getTagName,\n getAttr,\n} from \"../xml/helpers.js\";\nimport { normalizeHeaderFooter } from \"../normalizer/header-footer.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport { createInsElement } from \"./revision.js\";\n\n// =============================================================================\n// OOXML variant → w:type mapping\n// =============================================================================\n\n/** Map variant names to OOXML w:type attribute values. */\nconst VARIANT_TYPE_MAP: Record<HeaderFooterVariant, string> = {\n default: \"default\",\n first: \"first\",\n even: \"even\",\n};\n\n// =============================================================================\n// Add Header\n// =============================================================================\n\n/**\n * Execute an add_header patch: creates a new header XML part and wires\n * it into the document's last section.\n */\nexport function executeAddHeader(\n doc: NormalizedDocument,\n patch: AddHeaderPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n executeAddHeaderFooter(doc, \"header\", patch.variant, patch.paragraphs, revCtx, markers);\n}\n\n// =============================================================================\n// Add Footer\n// =============================================================================\n\n/**\n * Execute an add_footer patch: creates a new footer XML part and wires\n * it into the document's last section.\n */\nexport function executeAddFooter(\n doc: NormalizedDocument,\n patch: AddFooterPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n executeAddHeaderFooter(doc, \"footer\", patch.variant, patch.paragraphs, revCtx, markers);\n}\n\n// =============================================================================\n// Remove Header\n// =============================================================================\n\n/**\n * Execute a remove_header patch: removes a header from the document's\n * last section.\n */\nexport function executeRemoveHeader(\n doc: NormalizedDocument,\n patch: RemoveHeaderPatch,\n): void {\n executeRemoveHeaderFooter(doc, \"header\", patch.variant);\n}\n\n// =============================================================================\n// Remove Footer\n// =============================================================================\n\n/**\n * Execute a remove_footer patch: removes a footer from the document's\n * last section.\n */\nexport function executeRemoveFooter(\n doc: NormalizedDocument,\n patch: RemoveFooterPatch,\n): void {\n executeRemoveHeaderFooter(doc, \"footer\", patch.variant);\n}\n\n// =============================================================================\n// Shared implementation\n// =============================================================================\n\n/**\n * Add a header or footer to the document.\n */\nfunction executeAddHeaderFooter(\n doc: NormalizedDocument,\n type: \"header\" | \"footer\",\n variant: HeaderFooterVariant,\n paragraphs: string[],\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n // 1. Determine next available filename number\n const existingParts = type === \"header\" ? doc.xmlParts.headers : doc.xmlParts.footers;\n const nextNum = getNextPartNumber(existingParts, type);\n const filename = `${type}${nextNum}.xml`;\n const fullPath = `word/${filename}`;\n\n // 2. Build the header/footer XML FXP\n const rootTag = type === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const pNodes: FXPNode[] = [];\n\n for (const text of paragraphs) {\n const runs = buildFieldRuns(text, undefined, markers);\n\n if (revCtx) {\n // Wrap runs in w:ins for revision tracking\n const revRuns = runs.map((run) => {\n // For field code runs, wrap as-is; for plain runs, use as-is\n return run;\n });\n const insElement = createInsElement(revCtx, revRuns);\n pNodes.push({ \"w:p\": [insElement] });\n } else {\n pNodes.push({ \"w:p\": runs });\n }\n }\n\n const rootNode = createElement(rootTag, pNodes, {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n \"xmlns:r\": \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n });\n // Wrap attributes onto the tag using FXP convention (attrs on root via :@)\n const fxpPart: FXPNode[] = [rootNode];\n\n // 3. Generate a new rId\n const rId = getNextRelationshipId(doc);\n\n // 4. Add relationship to document.xml.rels\n addRelationship(doc, rId, filename, type);\n\n // 5. Add content type override\n addContentTypeOverride(doc, fullPath, type);\n\n // 6. Add header/footer reference to last sectPr\n addSectPrReference(doc, type, variant, rId);\n\n // 7. Store the new FXP part\n existingParts.set(fullPath, fxpPart);\n\n // 8. Normalize and add to doc.headers/doc.footers\n const hf = normalizeHeaderFooter(fxpPart, type, variant, rId);\n if (type === \"header\") {\n doc.headers.set(rId, hf);\n } else {\n doc.footers.set(rId, hf);\n }\n}\n\n/**\n * Remove a header or footer from the document.\n */\nfunction executeRemoveHeaderFooter(\n doc: NormalizedDocument,\n type: \"header\" | \"footer\",\n variant: HeaderFooterVariant,\n): void {\n // 1. Find the reference in sectPr\n const refTag = type === \"header\" ? \"w:headerReference\" : \"w:footerReference\";\n const sectPr = getLastSectPr(doc);\n if (!sectPr) throw new Error(\"No sectPr found in document\");\n\n const sectPrChildren = getChildren(sectPr);\n const refNode = sectPrChildren.find((child) => {\n const tag = getTagName(child);\n if (tag !== refTag) return false;\n const refType = getAttr(child, \"w:type\") ?? \"default\";\n return refType === VARIANT_TYPE_MAP[variant];\n });\n\n if (!refNode) {\n throw new Error(`No ${type} with variant \"${variant}\" found in sectPr`);\n }\n\n const rId = getAttr(refNode, \"r:id\");\n if (!rId) throw new Error(`${type} reference has no r:id`);\n\n // 2. Remove from sectPr\n const idx = sectPrChildren.indexOf(refNode);\n if (idx !== -1) sectPrChildren.splice(idx, 1);\n\n // 3. Find the target filename from rels\n const target = findRelationshipTarget(doc, rId);\n const fullPath = target ? `word/${target}` : undefined;\n\n // 4. Remove the relationship\n removeRelationship(doc, rId);\n\n // 5. Remove content type override\n if (fullPath) {\n removeContentTypeOverride(doc, fullPath);\n }\n\n // 6. Remove from xmlParts\n const parts = type === \"header\" ? doc.xmlParts.headers : doc.xmlParts.footers;\n if (fullPath) {\n parts.delete(fullPath);\n }\n\n // 7. Remove from normalized maps\n if (type === \"header\") {\n doc.headers.delete(rId);\n } else {\n doc.footers.delete(rId);\n }\n}\n\n// =============================================================================\n// Helpers: file numbering\n// =============================================================================\n\n/**\n * Find the next available number for a header/footer filename.\n */\nfunction getNextPartNumber(\n parts: Map<string, FXPNode[]>,\n type: \"header\" | \"footer\",\n): number {\n let maxNum = 0;\n const regex = new RegExp(`${type}(\\\\d+)\\\\.xml$`, \"i\");\n for (const path of parts.keys()) {\n const match = path.match(regex);\n if (match) {\n const num = parseInt(match[1]!, 10);\n if (num > maxNum) maxNum = num;\n }\n }\n return maxNum + 1;\n}\n\n// =============================================================================\n// Helpers: relationship management\n// =============================================================================\n\n/**\n * Get the next available relationship ID.\n */\nfunction getNextRelationshipId(doc: NormalizedDocument): string {\n const rels = doc.xmlParts.documentRels;\n if (!rels) return \"rId1\";\n\n let maxId = 0;\n const relsNode = rels.find((n) => getTagName(n) === \"Relationships\");\n if (relsNode) {\n const relNodes = findChildren(relsNode, \"Relationship\");\n for (const rel of relNodes) {\n const id = getAttr(rel, \"Id\");\n if (id) {\n const num = parseInt(id.replace(/^rId/, \"\"), 10);\n if (!isNaN(num) && num > maxId) maxId = num;\n }\n }\n }\n return `rId${maxId + 1}`;\n}\n\n/**\n * Add a Relationship node to document.xml.rels.\n */\nfunction addRelationship(\n doc: NormalizedDocument,\n rId: string,\n target: string,\n type: \"header\" | \"footer\",\n): void {\n if (!doc.xmlParts.documentRels) {\n // Initialize a minimal Relationships root on-demand so that\n // add_header / add_footer works on documents created via Docxtor.create()\n // (which doesn't include word/_rels/document.xml.rels).\n doc.xmlParts.documentRels = [\n createElement(\"Relationships\", [], {\n \"xmlns\": \"http://schemas.openxmlformats.org/package/2006/relationships\",\n }),\n ];\n }\n\n const relType = type === \"header\"\n ? \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header\"\n : \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer\";\n\n const relNode = createElement(\"Relationship\", [], {\n \"Id\": rId,\n \"Type\": relType,\n \"Target\": target,\n });\n\n const relsNode = doc.xmlParts.documentRels.find(\n (n) => getTagName(n) === \"Relationships\",\n );\n if (relsNode) {\n getChildren(relsNode).push(relNode);\n }\n}\n\n/**\n * Find the target filename for a relationship by rId.\n */\nfunction findRelationshipTarget(\n doc: NormalizedDocument,\n rId: string,\n): string | undefined {\n const rels = doc.xmlParts.documentRels;\n if (!rels) return undefined;\n\n const relsNode = rels.find((n) => getTagName(n) === \"Relationships\");\n if (!relsNode) return undefined;\n\n const relNodes = findChildren(relsNode, \"Relationship\");\n for (const rel of relNodes) {\n if (getAttr(rel, \"Id\") === rId) {\n return getAttr(rel, \"Target\");\n }\n }\n return undefined;\n}\n\n/**\n * Remove a Relationship node from document.xml.rels by rId.\n */\nfunction removeRelationship(doc: NormalizedDocument, rId: string): void {\n const rels = doc.xmlParts.documentRels;\n if (!rels) return;\n\n const relsNode = rels.find((n) => getTagName(n) === \"Relationships\");\n if (!relsNode) return;\n\n const children = getChildren(relsNode);\n const idx = children.findIndex(\n (child) => getTagName(child) === \"Relationship\" && getAttr(child, \"Id\") === rId,\n );\n if (idx !== -1) children.splice(idx, 1);\n}\n\n// =============================================================================\n// Helpers: content types management\n// =============================================================================\n\n/**\n * Add an Override entry to [Content_Types].xml.\n */\nfunction addContentTypeOverride(\n doc: NormalizedDocument,\n partName: string,\n type: \"header\" | \"footer\",\n): void {\n if (!doc.xmlParts.contentTypes) return;\n\n const contentType = type === \"header\"\n ? \"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml\"\n : \"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml\";\n\n const overrideNode = createElement(\"Override\", [], {\n \"PartName\": `/${partName}`,\n \"ContentType\": contentType,\n });\n\n const typesNode = doc.xmlParts.contentTypes.find(\n (n) => getTagName(n) === \"Types\",\n );\n if (typesNode) {\n getChildren(typesNode).push(overrideNode);\n }\n}\n\n/**\n * Remove an Override entry from [Content_Types].xml by part name.\n */\nfunction removeContentTypeOverride(\n doc: NormalizedDocument,\n partName: string,\n): void {\n if (!doc.xmlParts.contentTypes) return;\n\n const typesNode = doc.xmlParts.contentTypes.find(\n (n) => getTagName(n) === \"Types\",\n );\n if (!typesNode) return;\n\n const children = getChildren(typesNode);\n // PartName may be stored with or without leading /\n const idx = children.findIndex((child) => {\n if (getTagName(child) !== \"Override\") return false;\n const pn = getAttr(child, \"PartName\");\n return pn === `/${partName}` || pn === partName;\n });\n if (idx !== -1) children.splice(idx, 1);\n}\n\n// =============================================================================\n// Helpers: sectPr management\n// =============================================================================\n\n/**\n * Get the last w:sectPr in the document body.\n */\nfunction getLastSectPr(doc: NormalizedDocument): FXPNode | undefined {\n const docNode = doc.xmlParts.document.find(\n (n) => getTagName(n) === \"w:document\",\n );\n if (!docNode) return undefined;\n\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) return undefined;\n\n const bodyChildren = getChildren(bodyNode);\n // sectPr is typically the last child of w:body\n for (let i = bodyChildren.length - 1; i >= 0; i--) {\n if (getTagName(bodyChildren[i]!) === \"w:sectPr\") {\n return bodyChildren[i]!;\n }\n }\n return undefined;\n}\n\n/**\n * Add a header/footer reference to the last sectPr.\n * Creates sectPr if it doesn't exist.\n */\nfunction addSectPrReference(\n doc: NormalizedDocument,\n type: \"header\" | \"footer\",\n variant: HeaderFooterVariant,\n rId: string,\n): void {\n let sectPr = getLastSectPr(doc);\n\n if (!sectPr) {\n // Create a sectPr and append to body\n sectPr = createElement(\"w:sectPr\", []);\n const docNode = doc.xmlParts.document.find(\n (n) => getTagName(n) === \"w:document\",\n );\n if (!docNode) throw new Error(\"Cannot find w:document\");\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) throw new Error(\"Cannot find w:body\");\n getChildren(bodyNode).push(sectPr);\n }\n\n const refTag = type === \"header\" ? \"w:headerReference\" : \"w:footerReference\";\n const refNode = createElement(refTag, [], {\n \"w:type\": VARIANT_TYPE_MAP[variant],\n \"r:id\": rId,\n });\n\n // Insert header/footer references at the beginning of sectPr children\n // (before pgSz, pgMar, etc.)\n getChildren(sectPr).unshift(refNode);\n\n // If variant is \"first\", ensure w:titlePg is present in sectPr\n if (variant === \"first\") {\n const sectPrChildren = getChildren(sectPr);\n const hasTitlePg = sectPrChildren.some(\n (c) => getTagName(c) === \"w:titlePg\",\n );\n if (!hasTitlePg) {\n sectPrChildren.push(createElement(\"w:titlePg\", []));\n }\n }\n\n // If variant is \"even\", ensure w:evenAndOddHeaders is present in\n // word/settings.xml so Word honours the even-page header/footer reference.\n if (variant === \"even\") {\n ensureEvenAndOddHeaders(doc);\n }\n}\n\n// =============================================================================\n// Helpers: settings.xml management\n// =============================================================================\n\n/**\n * Ensure `<w:evenAndOddHeaders/>` is present in word/settings.xml.\n *\n * Without this element, Word ignores even-page header/footer references.\n * If settings.xml was not loaded (e.g., created via Docxtor.create()),\n * a minimal settings part is initialized on-demand.\n */\nfunction ensureEvenAndOddHeaders(doc: NormalizedDocument): void {\n if (!doc.xmlParts.settings) {\n // Create a minimal settings.xml FXP structure\n doc.xmlParts.settings = [\n createElement(\"w:settings\", [\n createElement(\"w:evenAndOddHeaders\", []),\n ], {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n }),\n ];\n }\n\n const settingsNode = doc.xmlParts.settings.find(\n (n) => getTagName(n) === \"w:settings\",\n );\n if (!settingsNode) return;\n\n const children = getChildren(settingsNode);\n const hasFlag = children.some((c) => getTagName(c) === \"w:evenAndOddHeaders\");\n if (!hasFlag) {\n children.push(createElement(\"w:evenAndOddHeaders\", []));\n }\n\n ensureSettingsRelationship(doc);\n ensureSettingsContentTypeOverride(doc);\n}\n\nfunction ensureSettingsRelationship(doc: NormalizedDocument): void {\n if (!doc.xmlParts.documentRels) {\n doc.xmlParts.documentRels = [\n createElement(\"Relationships\", [], {\n \"xmlns\": \"http://schemas.openxmlformats.org/package/2006/relationships\",\n }),\n ];\n }\n\n const relsNode = doc.xmlParts.documentRels.find(\n (n) => getTagName(n) === \"Relationships\",\n );\n if (!relsNode) return;\n\n const relType =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings\";\n const children = getChildren(relsNode);\n const hasSettingsRel = children.some((child) => {\n if (getTagName(child) !== \"Relationship\") return false;\n return getAttr(child, \"Type\") === relType && getAttr(child, \"Target\") === \"settings.xml\";\n });\n if (hasSettingsRel) return;\n\n children.push(createElement(\"Relationship\", [], {\n \"Id\": getNextRelationshipId(doc),\n \"Type\": relType,\n \"Target\": \"settings.xml\",\n }));\n}\n\nfunction ensureSettingsContentTypeOverride(doc: NormalizedDocument): void {\n if (!doc.xmlParts.contentTypes) {\n doc.xmlParts.contentTypes = [\n createElement(\"Types\", [], {\n \"xmlns\": \"http://schemas.openxmlformats.org/package/2006/content-types\",\n }),\n ];\n }\n\n const typesNode = doc.xmlParts.contentTypes.find(\n (n) => getTagName(n) === \"Types\",\n );\n if (!typesNode) return;\n\n const partName = \"/word/settings.xml\";\n const contentType =\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml\";\n const children = getChildren(typesNode);\n const hasSettingsOverride = children.some((child) => {\n if (getTagName(child) !== \"Override\") return false;\n return getAttr(child, \"PartName\") === partName;\n });\n if (hasSettingsOverride) return;\n\n children.push(createElement(\"Override\", [], {\n \"PartName\": partName,\n \"ContentType\": contentType,\n }));\n}\n","/**\n * Shared helpers for locating a paragraph's containers — both in the FXP\n * tree (the parent children array holding its `w:p`) and in the normalized\n * view (the `doc.body` / `hf.paragraphs` / `cell.paragraphs` array holding\n * its NormalizedParagraph).\n *\n * Used by list ops, paragraph ops, and the engine for insertions and\n * removals that must keep both views in sync.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n NormalizedPageBreak,\n FXPNode,\n} from \"../types.js\";\nimport { findChild, getChildren, getTagName } from \"../xml/helpers.js\";\nimport { normalizeParagraph } from \"../normalizer/paragraph.js\";\nimport { enrichListType } from \"../normalizer/document.js\";\n\n/** Any array that holds normalized paragraphs (body, hf.paragraphs, cell.paragraphs). */\nexport type NormalizedContainerArray = Array<\n NormalizedParagraph | NormalizedTable | NormalizedPageBreak\n>;\n\n/**\n * Get the w:body children array from the document FXP tree.\n */\nexport function getBodyChildren(doc: NormalizedDocument): FXPNode[] {\n const docNode = doc.xmlParts.document.find(\n (n) => getTagName(n) === \"w:document\",\n );\n if (!docNode) throw new Error(\"Cannot find w:document in FXP tree\");\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) throw new Error(\"Cannot find w:body in FXP tree\");\n return getChildren(bodyNode);\n}\n\n/**\n * Insert FXP nodes into the body before w:sectPr, or after a specific element.\n */\nexport function insertIntoBody(\n bodyChildren: FXPNode[],\n nodes: FXPNode[],\n afterXmlRef?: FXPNode,\n): void {\n if (afterXmlRef) {\n const idx = bodyChildren.indexOf(afterXmlRef);\n if (idx !== -1) {\n bodyChildren.splice(idx + 1, 0, ...nodes);\n return;\n }\n }\n const sectPrIndex = bodyChildren.findIndex(\n (n) => getTagName(n) === \"w:sectPr\",\n );\n if (sectPrIndex !== -1) {\n bodyChildren.splice(sectPrIndex, 0, ...nodes);\n } else {\n bodyChildren.push(...nodes);\n }\n}\n\n/**\n * Compute the next available body paragraph ID index.\n */\nexport function getNextPIdx(doc: NormalizedDocument): number {\n let max = -1;\n for (const el of doc.body) {\n if (el.kind === \"paragraph\") {\n const num = parseInt(el.id.replace(\"p\", \"\"), 10);\n if (!isNaN(num) && num > max) max = num;\n }\n }\n return max + 1;\n}\n\n/**\n * Find the FXP parent children array that directly contains a paragraph's xmlRef.\n * Searches w:body, w:tc (table cells, recursively), and header/footer roots.\n */\nexport function findParentChildren(\n doc: NormalizedDocument,\n xmlRef: FXPNode,\n): FXPNode[] | undefined {\n // Check body\n const bodyChildren = getBodyChildren(doc);\n if (bodyChildren.includes(xmlRef)) return bodyChildren;\n\n // Check table cells in body\n for (const child of bodyChildren) {\n if (getTagName(child) === \"w:tbl\") {\n const result = findInTable(child, xmlRef);\n if (result) return result;\n }\n }\n\n // Check headers/footers\n for (const hf of [...doc.headers.values(), ...doc.footers.values()]) {\n const rootTag = hf.type === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const rootNode = hf.xmlRef.find((n) => getTagName(n) === rootTag);\n if (rootNode) {\n const rootChildren = getChildren(rootNode);\n if (rootChildren.includes(xmlRef)) return rootChildren;\n for (const child of rootChildren) {\n if (getTagName(child) === \"w:tbl\") {\n const result = findInTable(child, xmlRef);\n if (result) return result;\n }\n }\n }\n }\n\n return undefined;\n}\n\nfunction findInTable(tblNode: FXPNode, target: FXPNode): FXPNode[] | undefined {\n for (const tr of getChildren(tblNode).filter((n) => getTagName(n) === \"w:tr\")) {\n for (const tc of getChildren(tr).filter((n) => getTagName(n) === \"w:tc\")) {\n const tcChildren = getChildren(tc);\n if (tcChildren.includes(target)) return tcChildren;\n // Check nested tables\n for (const child of tcChildren) {\n if (getTagName(child) === \"w:tbl\") {\n const result = findInTable(child, target);\n if (result) return result;\n }\n }\n }\n }\n return undefined;\n}\n\n/**\n * Find the normalized paragraph array that contains a given paragraph ID.\n * Returns { array, index } for doc.body, hf.paragraphs, or cell.paragraphs\n * (searching nested tables recursively).\n */\nexport function findNormalizedContainer(\n doc: NormalizedDocument,\n paraId: string,\n): { array: NormalizedContainerArray; index: number } | undefined {\n // Check doc.body\n const bodyIdx = doc.body.findIndex((el) => el.id === paraId);\n if (bodyIdx !== -1) return { array: doc.body, index: bodyIdx };\n\n // Check header/footer paragraphs\n for (const hf of [...doc.headers.values(), ...doc.footers.values()]) {\n const hfIdx = hf.paragraphs.findIndex((p) => p.id === paraId);\n if (hfIdx !== -1) return { array: hf.paragraphs, index: hfIdx };\n }\n\n // Check table cell paragraphs (body tables + hf tables), recursing into nested tables\n const allTables: NormalizedTable[] = [];\n for (const el of doc.body) {\n if (el.kind === \"table\") allTables.push(el);\n }\n for (const hf of [...doc.headers.values(), ...doc.footers.values()]) {\n allTables.push(...hf.tables);\n }\n\n for (const table of allTables) {\n const found = findInNormalizedTable(table, paraId);\n if (found) return found;\n }\n\n return undefined;\n}\n\nfunction findInNormalizedTable(\n table: NormalizedTable,\n paraId: string,\n): { array: NormalizedContainerArray; index: number } | undefined {\n for (const row of table.rows) {\n for (const cell of row.cells) {\n const cellIdx = cell.paragraphs.findIndex((p) => p.id === paraId);\n if (cellIdx !== -1) return { array: cell.paragraphs, index: cellIdx };\n for (const nested of cell.nestedTables) {\n const found = findInNormalizedTable(nested, paraId);\n if (found) return found;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Derive a new paragraph ID in the same namespace as the target paragraph.\n * E.g., target \"hdr.default.p2\" → \"hdr.default.p3\",\n * target \"tbl0.r1.c0.p0\" → \"tbl0.r1.c0.p1\",\n * target \"p5\" → \"p6\" (body namespace).\n *\n * Scans sibling paragraphs in the container to find the next available index.\n */\nexport function deriveNewParagraphId(\n targetId: string,\n containerArray: readonly { id: string }[],\n): string {\n // Extract prefix: everything up to and including the last \"p\" before the trailing number\n const match = targetId.match(/^(.*p)(\\d+)$/);\n if (!match) {\n // Fallback — shouldn't happen for valid paragraph IDs\n return `p${Date.now()}`;\n }\n const prefix = match[1]!; // e.g. \"hdr.default.p\", \"tbl0.r1.c0.p\", \"p\"\n\n // Find the max index among siblings with the same prefix\n let maxIdx = -1;\n for (const el of containerArray) {\n if (el.id.startsWith(prefix)) {\n const suffix = el.id.slice(prefix.length);\n const num = parseInt(suffix, 10);\n if (!isNaN(num) && num > maxIdx) maxIdx = num;\n }\n }\n\n return `${prefix}${maxIdx + 1}`;\n}\n\n/**\n * Re-normalize a paragraph in place, enriching listType.\n */\nexport function reNormalizeParagraph(doc: NormalizedDocument, para: NormalizedParagraph): void {\n const fresh = normalizeParagraph(para.xmlRef, para.id);\n para.text = fresh.text;\n para.runs = fresh.runs;\n para.style = fresh.style;\n para.listInfo = fresh.listInfo;\n para.revisions = fresh.revisions;\n para.paraId = fresh.paraId;\n para.hasPageBreak = fresh.hasPageBreak;\n para.pageBreakOffset = fresh.pageBreakOffset;\n if (doc.xmlParts.numbering) {\n enrichListType(para, doc.xmlParts.numbering);\n }\n}\n","/**\n * List operation executors for the 6 list patch types.\n *\n * Each executor mutates the FXP tree directly and updates the\n * NormalizedDocument accordingly.\n */\n\nimport type {\n NormalizedDocument,\n FXPNode,\n AddListPatch,\n AddListItemPatch,\n RemoveListItemPatch,\n ConvertToListPatch,\n RemoveListPatch,\n SetListPropertiesPatch,\n} from \"../types.js\";\nimport { createNumberingDefinition } from \"./numbering.js\";\nimport { enrichListType } from \"../normalizer/document.js\";\nimport { normalizeParagraph } from \"../normalizer/paragraph.js\";\nimport {\n createElement,\n findChild,\n getChildren,\n getTagName,\n setAttr,\n cloneNode,\n} from \"../xml/helpers.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport { findParagraphById, findElementById } from \"./validator.js\";\nimport { createInsElement, createPPrChange, convertRunToDeleted, createDelElement, type RevisionContext } from \"./revision.js\";\nimport {\n getBodyChildren,\n insertIntoBody,\n getNextPIdx,\n findParentChildren,\n findNormalizedContainer,\n deriveNewParagraphId,\n reNormalizeParagraph as reNormalize,\n} from \"./container-utils.js\";\nimport { stampNewParagraphs, findPartRootForId } from \"../normalizer/para-id.js\";\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/**\n * Build a w:p node with w:numPr for a list item.\n */\nfunction buildListParagraphNode(\n text: string,\n numId: string,\n level: number,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): FXPNode {\n const numPr = createElement(\"w:numPr\", [\n createElement(\"w:ilvl\", [], { \"w:val\": String(level) }),\n createElement(\"w:numId\", [], { \"w:val\": numId }),\n ]);\n const pPr = createElement(\"w:pPr\", [numPr]);\n\n const runs = buildFieldRuns(text, undefined, markers);\n let children: FXPNode[];\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n children = [pPr, insElement];\n } else {\n children = [pPr, ...runs];\n }\n\n return { \"w:p\": children };\n}\n\n// =============================================================================\n// Executors\n// =============================================================================\n\nexport function executeAddList(\n doc: NormalizedDocument,\n patch: AddListPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n const numId = patch.numId ?? createNumberingDefinition(doc, patch.listType);\n const defaultLevel = patch.level ?? 0;\n const nodes: FXPNode[] = [];\n\n for (const item of patch.items) {\n const text = typeof item === \"string\" ? item : item.text;\n const level = typeof item === \"string\" ? defaultLevel : (item.level ?? defaultLevel);\n nodes.push(buildListParagraphNode(text, numId, level, revCtx, markers));\n }\n\n if (patch.container) {\n // Insert into header/footer\n const map = patch.container.type === \"header\" ? doc.headers : doc.footers;\n for (const hf of map.values()) {\n if (hf.variant === patch.container.variant) {\n const rootTag = hf.type === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const rootNode = hf.xmlRef.find((n) => getTagName(n) === rootTag);\n if (rootNode) {\n const rootChildren = getChildren(rootNode);\n stampNewParagraphs(doc, nodes, rootNode);\n rootChildren.push(...nodes);\n const prefix = patch.container.type === \"header\" ? \"hdr\" : \"ftr\";\n let nextPIdx = hf.paragraphs.length;\n for (const node of nodes) {\n const normalized = normalizeParagraph(\n node,\n `${prefix}.${patch.container.variant}.p${nextPIdx++}`,\n );\n if (doc.xmlParts.numbering) enrichListType(normalized, doc.xmlParts.numbering);\n hf.paragraphs.push(normalized);\n }\n }\n return;\n }\n }\n return;\n }\n\n const bodyChildren = getBodyChildren(doc);\n let afterXmlRef: FXPNode | undefined;\n let afterElementId: string | undefined;\n if (patch.afterId) {\n const target = findElementById(patch.afterId, doc);\n if (target) {\n afterXmlRef = target.xmlRef;\n afterElementId = target.id;\n }\n }\n\n stampNewParagraphs(doc, nodes, findPartRootForId(doc, \"body\"));\n insertIntoBody(bodyChildren, nodes, afterXmlRef);\n\n // Normalize and add to doc.body\n let nextPIdx = getNextPIdx(doc);\n const insertIdx = afterElementId !== undefined\n ? doc.body.findIndex((el) => el.id === afterElementId)\n : -1;\n\n for (let i = 0; i < nodes.length; i++) {\n const id = `p${nextPIdx++}`;\n const normalized = normalizeParagraph(nodes[i]!, id);\n if (doc.xmlParts.numbering) enrichListType(normalized, doc.xmlParts.numbering);\n if (insertIdx !== -1) {\n doc.body.splice(insertIdx + 1 + i, 0, normalized);\n } else {\n doc.body.push(normalized);\n }\n }\n}\n\nexport function executeAddListItem(\n doc: NormalizedDocument,\n patch: AddListItemPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n const targetPara = findParagraphById(patch.target, doc)!;\n const numId = targetPara.listInfo!.numId;\n const level = patch.level ?? targetPara.listInfo!.level;\n\n const newNode = buildListParagraphNode(patch.text, numId, level, revCtx, markers);\n stampNewParagraphs(doc, [newNode], findPartRootForId(doc, targetPara.id));\n\n // Find the parent FXP container\n const parentChildren = findParentChildren(doc, targetPara.xmlRef);\n if (!parentChildren) throw new Error(\"Cannot find parent container for target paragraph\");\n\n const targetIdx = parentChildren.indexOf(targetPara.xmlRef);\n if (targetIdx === -1) throw new Error(\"Target xmlRef not found in parent\");\n\n if (patch.position === \"after\") {\n parentChildren.splice(targetIdx + 1, 0, newNode);\n } else {\n parentChildren.splice(targetIdx, 0, newNode);\n }\n\n // Normalize and insert into the correct normalized container\n const container = findNormalizedContainer(doc, targetPara.id);\n const containerArray = container ? container.array : doc.body;\n const id = deriveNewParagraphId(targetPara.id, containerArray);\n const normalized = normalizeParagraph(newNode, id);\n if (doc.xmlParts.numbering) enrichListType(normalized, doc.xmlParts.numbering);\n\n if (container) {\n if (patch.position === \"after\") {\n container.array.splice(container.index + 1, 0, normalized);\n } else {\n container.array.splice(container.index, 0, normalized);\n }\n } else {\n doc.body.push(normalized);\n }\n}\n\nexport function executeRemoveListItem(\n doc: NormalizedDocument,\n patch: RemoveListItemPatch,\n revCtx?: RevisionContext,\n): void {\n const targetPara = findParagraphById(patch.target, doc)!;\n\n if (revCtx) {\n // Mark runs as deleted instead of removing\n const children = getChildren(targetPara.xmlRef);\n // Store old pPr for pPrChange\n const oldPPr = findChild(targetPara.xmlRef, \"w:pPr\");\n if (oldPPr) {\n const pPrChange = createPPrChange(revCtx, oldPPr);\n const pPrChildren = getChildren(oldPPr);\n pPrChildren.push(pPrChange);\n }\n // Wrap runs in w:del — handle both direct w:r and w:r nested inside w:ins\n for (let i = children.length - 1; i >= 0; i--) {\n const child = children[i]!;\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n // Clone the run and convert w:t → w:delText for valid OOXML\n const cloned = cloneNode(child);\n convertRunToDeleted(cloned);\n const delElement = createDelElement(revCtx, [cloned]);\n children[i] = delElement;\n } else if (tag === \"w:ins\") {\n // Inserted but not accepted text: removing the w:ins entirely is the\n // correct OOXML semantics (the content was never part of the accepted\n // document, so wrapping it in w:del would corrupt the file).\n children.splice(i, 1);\n }\n }\n reNormalize(doc, targetPara);\n return;\n }\n\n // Physical removal\n const parentChildren = findParentChildren(doc, targetPara.xmlRef);\n if (parentChildren) {\n const idx = parentChildren.indexOf(targetPara.xmlRef);\n if (idx !== -1) parentChildren.splice(idx, 1);\n }\n\n // Remove from the correct normalized container (body, hf.paragraphs, or cell.paragraphs)\n const container = findNormalizedContainer(doc, targetPara.id);\n if (container) {\n container.array.splice(container.index, 1);\n }\n}\n\nexport function executeConvertToList(\n doc: NormalizedDocument,\n patch: ConvertToListPatch,\n revCtx?: RevisionContext,\n): void {\n const numId = createNumberingDefinition(doc, patch.listType);\n const level = patch.level ?? 0;\n\n for (const target of patch.targets) {\n const para = findParagraphById(target, doc)!;\n const pNode = para.xmlRef;\n\n // Find or create w:pPr\n let pPr = findChild(pNode, \"w:pPr\");\n const pChildren = getChildren(pNode);\n\n if (revCtx && pPr) {\n const pPrChange = createPPrChange(revCtx, pPr);\n getChildren(pPr).push(pPrChange);\n }\n\n if (!pPr) {\n pPr = createElement(\"w:pPr\", []);\n pChildren.unshift(pPr);\n }\n\n const pPrChildren = getChildren(pPr);\n\n // Remove existing w:numPr if present\n const existingIdx = pPrChildren.findIndex((c) => getTagName(c) === \"w:numPr\");\n if (existingIdx !== -1) pPrChildren.splice(existingIdx, 1);\n\n // Add new w:numPr\n const numPr = createElement(\"w:numPr\", [\n createElement(\"w:ilvl\", [], { \"w:val\": String(level) }),\n createElement(\"w:numId\", [], { \"w:val\": numId }),\n ]);\n pPrChildren.unshift(numPr);\n\n reNormalize(doc, para);\n }\n}\n\nexport function executeRemoveList(\n doc: NormalizedDocument,\n patch: RemoveListPatch,\n revCtx?: RevisionContext,\n): void {\n for (const target of patch.targets) {\n const para = findParagraphById(target, doc)!;\n const pPr = findChild(para.xmlRef, \"w:pPr\");\n if (!pPr) continue;\n\n if (revCtx) {\n const pPrChange = createPPrChange(revCtx, pPr);\n getChildren(pPr).push(pPrChange);\n }\n\n const pPrChildren = getChildren(pPr);\n const numPrIdx = pPrChildren.findIndex((c) => getTagName(c) === \"w:numPr\");\n if (numPrIdx !== -1) pPrChildren.splice(numPrIdx, 1);\n\n reNormalize(doc, para);\n }\n}\n\nexport function executeSetListProperties(\n doc: NormalizedDocument,\n patch: SetListPropertiesPatch,\n revCtx?: RevisionContext,\n): void {\n const para = findParagraphById(patch.target, doc)!;\n const pPr = findChild(para.xmlRef, \"w:pPr\");\n if (!pPr) throw new Error(\"No w:pPr found on target paragraph\");\n\n if (revCtx) {\n const pPrChange = createPPrChange(revCtx, pPr);\n getChildren(pPr).push(pPrChange);\n }\n\n const numPr = findChild(pPr, \"w:numPr\");\n if (!numPr) throw new Error(\"No w:numPr found on target paragraph\");\n\n if (patch.level !== undefined) {\n const ilvl = findChild(numPr, \"w:ilvl\");\n if (ilvl) {\n setAttr(ilvl, \"w:val\", String(patch.level));\n } else {\n getChildren(numPr).unshift(\n createElement(\"w:ilvl\", [], { \"w:val\": String(patch.level) }),\n );\n }\n }\n\n if (patch.listType !== undefined) {\n const newNumId = createNumberingDefinition(doc, patch.listType);\n const numIdNode = findChild(numPr, \"w:numId\");\n if (numIdNode) {\n setAttr(numIdNode, \"w:val\", newNumId);\n } else {\n getChildren(numPr).push(\n createElement(\"w:numId\", [], { \"w:val\": newNumId }),\n );\n }\n }\n\n reNormalize(doc, para);\n}\n","/**\n * Whole-paragraph operations: insert_paragraph and delete_paragraph.\n *\n * insert_paragraph creates one or more sibling `w:p` nodes before or after a\n * target paragraph (in the body, a table cell, or a header/footer), optionally\n * inheriting the target's paragraph properties and run formatting. With\n * revision tracking, runs are wrapped in `w:ins` and the paragraph mark itself\n * carries `w:pPr/w:rPr/w:ins` so that accepting the change does not leave a\n * stray empty paragraph behind.\n *\n * delete_paragraph removes a paragraph outright, or with revision tracking\n * marks every run deleted and puts `w:del` on the paragraph mark — Word's\n * convention for a deleted paragraph.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n InsertParagraphPatch,\n DeleteParagraphPatch,\n FXPNode,\n} from \"../types.js\";\nimport {\n cloneNode,\n createElement,\n findChild,\n getAttr,\n getChildren,\n getTagName,\n} from \"../xml/helpers.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport { normalizeParagraph } from \"../normalizer/paragraph.js\";\nimport { enrichListType } from \"../normalizer/document.js\";\nimport {\n createInsElement,\n createDelElement,\n convertRunToDeleted,\n nextRevisionId,\n type RevisionContext,\n} from \"./revision.js\";\nimport {\n findParentChildren,\n findNormalizedContainer,\n deriveNewParagraphId,\n reNormalizeParagraph,\n} from \"./container-utils.js\";\nimport { stampNewParagraphs, findPartRootForId } from \"../normalizer/para-id.js\";\n\n// =============================================================================\n// Style inheritance\n// =============================================================================\n\n/** w:pPr children that must not be copied onto a new paragraph. */\nconst PPR_EXCLUDE = new Set([\"w:sectPr\", \"w:pPrChange\", \"w:rPr\"]);\n/** Paragraph-mark w:rPr children that are revision state, not formatting. */\nconst PARA_MARK_REVISION = new Set([\"w:ins\", \"w:del\", \"w:moveFrom\", \"w:moveTo\", \"w:rPrChange\"]);\n\n/**\n * Clone the target's w:pPr for a new sibling paragraph: paragraph style,\n * numbering, indentation, spacing, and the paragraph-mark run properties —\n * minus section breaks and revision bookkeeping.\n */\nfunction buildInheritedPPr(targetP: FXPNode): FXPNode | undefined {\n const pPr = findChild(targetP, \"w:pPr\");\n if (!pPr) return undefined;\n\n const children: FXPNode[] = [];\n let markRPr: FXPNode | undefined;\n\n for (const child of getChildren(pPr)) {\n const tag = getTagName(child);\n if (!tag) continue;\n if (tag === \"w:rPr\") {\n const kept = getChildren(child)\n .filter((c) => {\n const t = getTagName(c);\n return t !== undefined && !PARA_MARK_REVISION.has(t);\n })\n .map(cloneNode);\n if (kept.length > 0) markRPr = createElement(\"w:rPr\", kept);\n continue;\n }\n if (PPR_EXCLUDE.has(tag)) continue;\n children.push(cloneNode(child));\n }\n\n // w:rPr is the last element of CT_PPr apart from sectPr/pPrChange\n if (markRPr) children.push(markRPr);\n if (children.length === 0) return undefined;\n return createElement(\"w:pPr\", children);\n}\n\n/**\n * Run formatting for the new text: the target's last run's w:rPr (what Word\n * would give you if you pressed Enter at the end of the paragraph and typed).\n */\nfunction buildInheritedRunRPr(target: NormalizedParagraph): FXPNode | undefined {\n const lastRun = target.runs[target.runs.length - 1];\n if (!lastRun) return undefined;\n const rPr = findChild(lastRun.xmlRef, \"w:rPr\");\n if (!rPr) return undefined;\n const kept = getChildren(rPr)\n .filter((c) => getTagName(c) !== \"w:rPrChange\")\n .map(cloneNode);\n if (kept.length === 0) return undefined;\n return createElement(\"w:rPr\", kept);\n}\n\n// =============================================================================\n// Paragraph mark revisions\n// =============================================================================\n\n/**\n * Add a `w:ins` or `w:del` marker to a paragraph's mark (`w:pPr/w:rPr`).\n * Creates w:pPr / w:rPr as needed, keeping schema order.\n */\nexport function addParagraphMarkRevision(\n pNode: FXPNode,\n kind: \"ins\" | \"del\",\n revCtx: RevisionContext,\n): void {\n let pPr = findChild(pNode, \"w:pPr\");\n if (!pPr) {\n pPr = createElement(\"w:pPr\", []);\n getChildren(pNode).unshift(pPr);\n }\n const pPrChildren = getChildren(pPr);\n\n let rPr = findChild(pPr, \"w:rPr\");\n if (!rPr) {\n rPr = createElement(\"w:rPr\", []);\n const tailIdx = pPrChildren.findIndex((c) => {\n const t = getTagName(c);\n return t === \"w:sectPr\" || t === \"w:pPrChange\";\n });\n if (tailIdx === -1) pPrChildren.push(rPr);\n else pPrChildren.splice(tailIdx, 0, rPr);\n }\n\n const marker = createElement(`w:${kind}`, [], {\n \"w:id\": nextRevisionId(revCtx),\n \"w:author\": revCtx.author,\n \"w:date\": revCtx.date,\n });\n // w:ins / w:del are the first children of CT_ParaRPr\n getChildren(rPr).unshift(marker);\n}\n\n/** Find a paragraph-mark revision marker (`w:pPr/w:rPr/w:ins|w:del`). */\nfunction findParagraphMarkMarker(pNode: FXPNode, kind: \"ins\" | \"del\"): FXPNode | undefined {\n const pPr = findChild(pNode, \"w:pPr\");\n if (!pPr) return undefined;\n const rPr = findChild(pPr, \"w:rPr\");\n if (!rPr) return undefined;\n return findChild(rPr, `w:${kind}`);\n}\n\n// =============================================================================\n// insert_paragraph\n// =============================================================================\n\n/**\n * Build the new w:p nodes for an insert_paragraph patch.\n */\nfunction buildParagraphNodes(\n target: NormalizedParagraph,\n patch: InsertParagraphPatch,\n revCtx: RevisionContext | undefined,\n markers: boolean,\n): FXPNode[] {\n const inherit = patch.inheritStyle ?? true;\n const pPrTemplate = inherit ? buildInheritedPPr(target.xmlRef) : undefined;\n const runRPr = inherit ? buildInheritedRunRPr(target) : undefined;\n\n const lines = patch.text.split(\"\\n\");\n const nodes: FXPNode[] = [];\n\n for (const line of lines) {\n const children: FXPNode[] = [];\n if (pPrTemplate) children.push(cloneNode(pPrTemplate));\n\n const runs = buildFieldRuns(line, runRPr, markers);\n if (revCtx) {\n if (runs.length > 0) children.push(createInsElement(revCtx, runs));\n } else {\n children.push(...runs);\n }\n\n const pNode: FXPNode = { \"w:p\": children };\n if (revCtx) addParagraphMarkRevision(pNode, \"ins\", revCtx);\n nodes.push(pNode);\n }\n\n return nodes;\n}\n\n/**\n * Execute an insert_paragraph patch.\n *\n * The target is resolved by the caller (positional or `para:` id); the new\n * paragraphs are inserted as siblings in the same container — body, table\n * cell, or header/footer — and added to the normalized view with the next\n * free positional ids so they are addressable later in the same batch.\n *\n * @returns the normalized paragraphs that were created\n */\nexport function executeInsertParagraph(\n doc: NormalizedDocument,\n target: NormalizedParagraph,\n patch: InsertParagraphPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): NormalizedParagraph[] {\n const parentChildren = findParentChildren(doc, target.xmlRef);\n if (!parentChildren) {\n throw new Error(`Cannot find parent container for paragraph \"${target.id}\"`);\n }\n const targetIdx = parentChildren.indexOf(target.xmlRef);\n if (targetIdx === -1) {\n throw new Error(`Paragraph \"${target.id}\" not found in its parent`);\n }\n\n const nodes = buildParagraphNodes(target, patch, revCtx, markers);\n stampNewParagraphs(doc, nodes, findPartRootForId(doc, target.id));\n\n // XML insertion\n const xmlInsertAt = patch.position === \"after\" ? targetIdx + 1 : targetIdx;\n parentChildren.splice(xmlInsertAt, 0, ...nodes);\n\n // Normalized insertion\n const container = findNormalizedContainer(doc, target.id);\n if (!container) {\n throw new Error(`Paragraph \"${target.id}\" not found in normalized document`);\n }\n const normInsertAt = patch.position === \"after\" ? container.index + 1 : container.index;\n\n const created: NormalizedParagraph[] = [];\n for (let i = 0; i < nodes.length; i++) {\n const id = deriveNewParagraphId(target.id, container.array);\n const normalized = normalizeParagraph(nodes[i]!, id);\n if (doc.xmlParts.numbering) enrichListType(normalized, doc.xmlParts.numbering);\n container.array.splice(normInsertAt + i, 0, normalized);\n created.push(normalized);\n }\n\n return created;\n}\n\n// =============================================================================\n// delete_paragraph\n// =============================================================================\n\n/** Inline containers whose runs should be marked deleted recursively. */\nconst INLINE_CONTAINERS = new Set([\n \"w:hyperlink\",\n \"w:smartTag\",\n \"w:customXml\",\n \"w:dir\",\n \"w:bdo\",\n \"w:moveTo\",\n \"w:sdtContent\",\n \"w:fldSimple\",\n]);\n\n/**\n * Mark every run among `children` as deleted, in place.\n *\n * - Consecutive `w:r` siblings are grouped into one `w:del`.\n * - `w:ins` by the same author is removed outright (it was never part of the\n * accepted document); `w:ins` by another author gets `w:del` nested inside.\n * - Existing `w:del` / `w:moveFrom` content is left alone.\n * - Inline containers (hyperlinks, smart tags, content controls) are recursed.\n */\nexport function markRunsDeleted(children: FXPNode[], revCtx: RevisionContext): void {\n let i = 0;\n while (i < children.length) {\n const child = children[i]!;\n const tag = getTagName(child);\n\n if (tag === \"w:r\") {\n // Gather a group of consecutive runs\n let j = i;\n while (j < children.length && getTagName(children[j]!) === \"w:r\") j++;\n const group = children.slice(i, j);\n for (const run of group) convertRunToDeleted(run);\n children.splice(i, j - i, createDelElement(revCtx, group));\n i++;\n continue;\n }\n\n if (tag === \"w:ins\") {\n if (getAttr(child, \"w:author\") === revCtx.author) {\n children.splice(i, 1);\n continue;\n }\n markRunsDeleted(getChildren(child), revCtx);\n i++;\n continue;\n }\n\n if (tag === \"w:sdt\") {\n const content = findChild(child, \"w:sdtContent\");\n if (content) markRunsDeleted(getChildren(content), revCtx);\n i++;\n continue;\n }\n\n if (tag && INLINE_CONTAINERS.has(tag)) {\n markRunsDeleted(getChildren(child), revCtx);\n }\n // w:del, w:moveFrom, w:pPr, bookmarks, proofErr, comment ranges: untouched\n i++;\n }\n}\n\n/**\n * True if every run in the paragraph lives inside a `w:ins` by `author` and\n * the paragraph mark is also an insertion by `author` — i.e. the paragraph is\n * wholly our own unaccepted insertion and can simply be removed.\n */\nfunction isWhollyOwnInsertion(pNode: FXPNode, author: string): boolean {\n const mark = findParagraphMarkMarker(pNode, \"ins\");\n if (!mark || getAttr(mark, \"w:author\") !== author) return false;\n\n const check = (children: FXPNode[]): boolean => {\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") return false;\n if (tag === \"w:ins\") {\n if (getAttr(child, \"w:author\") !== author) return false;\n continue;\n }\n if (tag === \"w:del\" || tag === \"w:moveFrom\") return false;\n if (tag === \"w:sdt\") {\n const content = findChild(child, \"w:sdtContent\");\n if (content && !check(getChildren(content))) return false;\n continue;\n }\n if (tag && INLINE_CONTAINERS.has(tag)) {\n if (!check(getChildren(child))) return false;\n }\n }\n return true;\n };\n return check(getChildren(pNode));\n}\n\n/**\n * Physically remove a paragraph from the XML and the normalized view.\n *\n * Every story (table cell, header, footer, body) must keep at least one\n * block element, so the last paragraph of a container is emptied instead\n * of removed. For the body, a table counts as a block element too.\n */\nfunction removeParagraph(doc: NormalizedDocument, para: NormalizedParagraph): void {\n const parentChildren = findParentChildren(doc, para.xmlRef);\n if (!parentChildren) {\n throw new Error(`Cannot find parent container for paragraph \"${para.id}\"`);\n }\n const container = findNormalizedContainer(doc, para.id);\n if (!container) {\n throw new Error(`Paragraph \"${para.id}\" not found in normalized document`);\n }\n\n const isBody = container.array === doc.body;\n const blockCount = parentChildren.filter((c) => {\n const t = getTagName(c);\n return t === \"w:p\" || (isBody && t === \"w:tbl\");\n }).length;\n\n if (blockCount === 1) {\n // Keep the story valid: strip content, keep w:pPr\n const pChildren = getChildren(para.xmlRef);\n const pPr = findChild(para.xmlRef, \"w:pPr\");\n pChildren.length = 0;\n if (pPr) pChildren.push(pPr);\n reNormalizeParagraph(doc, para);\n return;\n }\n\n const idx = parentChildren.indexOf(para.xmlRef);\n if (idx !== -1) parentChildren.splice(idx, 1);\n container.array.splice(container.index, 1);\n}\n\n/**\n * Execute a delete_paragraph patch.\n *\n * With revision tracking: all runs are wrapped in `w:del` (w:t → w:delText),\n * and the paragraph mark gets `w:pPr/w:rPr/w:del`. Runs already inside a\n * `w:ins` by the same author are removed rather than double-marked; if the\n * whole paragraph is the author's own unaccepted insertion, it is removed.\n *\n * Without tracking: the `w:p` is removed.\n */\nexport function executeDeleteParagraph(\n doc: NormalizedDocument,\n target: NormalizedParagraph,\n _patch: DeleteParagraphPatch,\n revCtx?: RevisionContext,\n): void {\n if (!revCtx) {\n removeParagraph(doc, target);\n return;\n }\n\n if (isWhollyOwnInsertion(target.xmlRef, revCtx.author)) {\n removeParagraph(doc, target);\n return;\n }\n\n markRunsDeleted(getChildren(target.xmlRef), revCtx);\n\n // Don't double-mark a mark that is already deleted\n if (!findParagraphMarkMarker(target.xmlRef, \"del\")) {\n addParagraphMarkRevision(target.xmlRef, \"del\", revCtx);\n }\n\n reNormalizeParagraph(doc, target);\n}\n","/**\n * Helpers for OPC package plumbing: relationships and content types.\n *\n * Used when a part is created on demand (comments.xml, core.xml) or removed\n * (custom.xml). Works on either relationship part — the package-level\n * `_rels/.rels` or the main document's `word/_rels/document.xml.rels`.\n */\n\nimport type { FXPNode } from \"../types.js\";\nimport { createElement, findChildren, getAttr, getChildren, getTagName } from \"../xml/helpers.js\";\n\nconst RELS_NAMESPACE = \"http://schemas.openxmlformats.org/package/2006/relationships\";\nconst CONTENT_TYPES_NAMESPACE = \"http://schemas.openxmlformats.org/package/2006/content-types\";\n\n/** Create an empty Relationships part. */\nexport function createRelsPart(): FXPNode[] {\n return [\n { \"?xml\": [{ \"#text\": \"\" } as unknown as FXPNode], \":@\": { \"@_version\": \"1.0\", \"@_encoding\": \"UTF-8\", \"@_standalone\": \"yes\" } } as FXPNode,\n createElement(\"Relationships\", [], { xmlns: RELS_NAMESPACE }),\n ];\n}\n\n/** Create an empty [Content_Types] part with the standard defaults. */\nexport function createContentTypesPart(): FXPNode[] {\n return [\n createElement(\"Types\", [\n createElement(\"Default\", [], { Extension: \"rels\", ContentType: \"application/vnd.openxmlformats-package.relationships+xml\" }),\n createElement(\"Default\", [], { Extension: \"xml\", ContentType: \"application/xml\" }),\n ], { xmlns: CONTENT_TYPES_NAMESPACE }),\n ];\n}\n\nfunction getRelsRoot(rels: FXPNode[]): FXPNode | undefined {\n return rels.find((n) => getTagName(n) === \"Relationships\");\n}\n\nfunction getTypesRoot(contentTypes: FXPNode[]): FXPNode | undefined {\n return contentTypes.find((n) => getTagName(n) === \"Types\");\n}\n\n/** Next unused `rIdN` in a relationships part. */\nexport function getNextRelationshipId(rels: FXPNode[]): string {\n let maxId = 0;\n const root = getRelsRoot(rels);\n if (root) {\n for (const rel of findChildren(root, \"Relationship\")) {\n const id = getAttr(rel, \"Id\");\n if (id) {\n const num = parseInt(id.replace(/^rId/, \"\"), 10);\n if (!isNaN(num) && num > maxId) maxId = num;\n }\n }\n }\n return `rId${maxId + 1}`;\n}\n\n/** Find a relationship by type, returning its target. */\nexport function findRelationshipByType(\n rels: FXPNode[] | undefined,\n type: string,\n): { id: string; target: string } | undefined {\n if (!rels) return undefined;\n const root = getRelsRoot(rels);\n if (!root) return undefined;\n for (const rel of findChildren(root, \"Relationship\")) {\n if (getAttr(rel, \"Type\") === type) {\n return { id: getAttr(rel, \"Id\") ?? \"\", target: getAttr(rel, \"Target\") ?? \"\" };\n }\n }\n return undefined;\n}\n\n/**\n * Ensure a relationship of the given type and target exists. Returns its Id.\n */\nexport function ensureRelationship(\n rels: FXPNode[],\n type: string,\n target: string,\n): string {\n const root = getRelsRoot(rels);\n if (!root) throw new Error(\"Relationships root not found\");\n for (const rel of findChildren(root, \"Relationship\")) {\n if (getAttr(rel, \"Type\") === type && getAttr(rel, \"Target\") === target) {\n return getAttr(rel, \"Id\") ?? \"\";\n }\n }\n const id = getNextRelationshipId(rels);\n getChildren(root).push(createElement(\"Relationship\", [], { Id: id, Type: type, Target: target }));\n return id;\n}\n\n/** Remove every relationship of the given type. Returns the removed targets. */\nexport function removeRelationshipsByType(\n rels: FXPNode[] | undefined,\n type: string,\n): string[] {\n if (!rels) return [];\n const root = getRelsRoot(rels);\n if (!root) return [];\n const children = getChildren(root);\n const removed: string[] = [];\n for (let i = children.length - 1; i >= 0; i--) {\n const child = children[i]!;\n if (getTagName(child) === \"Relationship\" && getAttr(child, \"Type\") === type) {\n removed.push(getAttr(child, \"Target\") ?? \"\");\n children.splice(i, 1);\n }\n }\n return removed;\n}\n\n/** Ensure an Override for `partName` (leading slash) exists in [Content_Types].xml. */\nexport function ensureContentTypeOverride(\n contentTypes: FXPNode[],\n partName: string,\n contentType: string,\n): void {\n const root = getTypesRoot(contentTypes);\n if (!root) return;\n const normalized = partName.startsWith(\"/\") ? partName : `/${partName}`;\n const exists = findChildren(root, \"Override\").some(\n (o) => getAttr(o, \"PartName\") === normalized,\n );\n if (!exists) {\n getChildren(root).push(createElement(\"Override\", [], { PartName: normalized, ContentType: contentType }));\n }\n}\n\n/** Remove the Override for `partName` (with or without leading slash). */\nexport function removeContentTypeOverride(\n contentTypes: FXPNode[] | undefined,\n partName: string,\n): void {\n if (!contentTypes) return;\n const root = getTypesRoot(contentTypes);\n if (!root) return;\n const children = getChildren(root);\n const normalized = partName.startsWith(\"/\") ? partName : `/${partName}`;\n const idx = children.findIndex(\n (c) => getTagName(c) === \"Override\" && getAttr(c, \"PartName\") === normalized,\n );\n if (idx !== -1) children.splice(idx, 1);\n}\n","/**\n * add_comment: attach a Word comment to a paragraph or a substring of it.\n *\n * Creates `word/comments.xml` on demand (with its relationship and content\n * type), appends a `w:comment`, and anchors it in the paragraph with\n * `w:commentRangeStart` / `w:commentRangeEnd` plus a `w:commentReference` run.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n AddCommentPatch,\n FXPNode,\n} from \"../types.js\";\nimport {\n createElement,\n findChild,\n findChildren,\n getAttr,\n getChildren,\n getTagName,\n} from \"../xml/helpers.js\";\nimport { findOccurrence, resolveRunRanges } from \"../mapping/text-to-runs.js\";\nimport { splitRunsForMatch, findRunParentChildren } from \"./run-splitter.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport { stripFormattingMarkers } from \"./markdown-parser.js\";\nimport {\n createRelsPart,\n createContentTypesPart,\n ensureRelationship,\n ensureContentTypeOverride,\n} from \"./part-utils.js\";\n\nconst COMMENTS_REL_TYPE =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments\";\nconst COMMENTS_CONTENT_TYPE =\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml\";\n\nexport interface CommentOptions {\n /** Author to use when the patch does not name one. */\n defaultAuthor: string;\n /** ISO 8601 date for the comment. */\n date: string;\n /** Parse inline formatting markers in `find` and `text`. Default: true. */\n markers?: boolean;\n}\n\n/**\n * Ensure word/comments.xml exists (as a parsed part) and is wired into the\n * package. Returns the `w:comments` root node.\n */\nexport function ensureCommentsPart(doc: NormalizedDocument): FXPNode {\n if (!doc.xmlParts.comments) {\n doc.xmlParts.comments = [\n { \"?xml\": [{ \"#text\": \"\" } as unknown as FXPNode], \":@\": { \"@_version\": \"1.0\", \"@_encoding\": \"UTF-8\", \"@_standalone\": \"yes\" } } as FXPNode,\n createElement(\"w:comments\", [], {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n \"xmlns:r\": \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n }),\n ];\n\n if (!doc.xmlParts.documentRels) doc.xmlParts.documentRels = createRelsPart();\n ensureRelationship(doc.xmlParts.documentRels, COMMENTS_REL_TYPE, \"comments.xml\");\n\n if (!doc.xmlParts.contentTypes) doc.xmlParts.contentTypes = createContentTypesPart();\n ensureContentTypeOverride(doc.xmlParts.contentTypes, \"/word/comments.xml\", COMMENTS_CONTENT_TYPE);\n }\n\n const root = doc.xmlParts.comments.find((n) => getTagName(n) === \"w:comments\");\n if (!root) throw new Error(\"word/comments.xml has no w:comments root\");\n return root;\n}\n\n/** Next unused comment id (max existing + 1, starting at 0). */\nfunction nextCommentId(commentsRoot: FXPNode): string {\n let max = -1;\n for (const c of findChildren(commentsRoot, \"w:comment\")) {\n const id = parseInt(getAttr(c, \"w:id\") ?? \"\", 10);\n if (!isNaN(id) && id > max) max = id;\n }\n return String(max + 1);\n}\n\n/** Initials from an author name: first letter of each word, up to three. */\nexport function deriveInitials(author: string): string {\n const words = author.split(/\\s+/).filter((w) => w.length > 0);\n const initials = words.map((w) => w[0]!.toUpperCase()).join(\"\");\n return initials.slice(0, 3) || \"A\";\n}\n\n/**\n * Build the `w:comment` element with its body paragraphs.\n */\nfunction buildCommentElement(\n id: string,\n author: string,\n initials: string,\n date: string,\n text: string,\n doc: NormalizedDocument,\n markers: boolean,\n): FXPNode {\n const hasCommentTextStyle = doc.styles.some((s) => s.id === \"CommentText\");\n const hasCommentRefStyle = doc.styles.some((s) => s.id === \"CommentReference\");\n\n const paragraphs = text.split(\"\\n\").map((line, idx) => {\n const children: FXPNode[] = [];\n if (hasCommentTextStyle) {\n children.push(createElement(\"w:pPr\", [createElement(\"w:pStyle\", [], { \"w:val\": \"CommentText\" })]));\n }\n if (idx === 0) {\n const refRun: FXPNode[] = [];\n if (hasCommentRefStyle) {\n refRun.push(createElement(\"w:rPr\", [createElement(\"w:rStyle\", [], { \"w:val\": \"CommentReference\" })]));\n }\n refRun.push(createElement(\"w:annotationRef\", []));\n children.push({ \"w:r\": refRun });\n }\n children.push(...buildFieldRuns(line, undefined, markers));\n return { \"w:p\": children } as FXPNode;\n });\n\n return createElement(\"w:comment\", paragraphs, {\n \"w:id\": id,\n \"w:author\": author,\n \"w:date\": date,\n \"w:initials\": initials,\n });\n}\n\n/** The `w:r` holding the comment reference mark. */\nfunction buildReferenceRun(id: string, doc: NormalizedDocument): FXPNode {\n const children: FXPNode[] = [];\n if (doc.styles.some((s) => s.id === \"CommentReference\")) {\n children.push(createElement(\"w:rPr\", [createElement(\"w:rStyle\", [], { \"w:val\": \"CommentReference\" })]));\n }\n children.push(createElement(\"w:commentReference\", [], { \"w:id\": id }));\n return { \"w:r\": children };\n}\n\n/**\n * Execute an add_comment patch on a paragraph.\n */\nexport function executeAddComment(\n doc: NormalizedDocument,\n paragraph: NormalizedParagraph,\n patch: AddCommentPatch,\n options: CommentOptions,\n): void {\n const author = patch.author && patch.author.length > 0 ? patch.author : options.defaultAuthor;\n const initials = patch.initials ?? deriveInitials(author);\n\n // Resolve the anchor before touching the package, so a failed patch has\n // no side effects (no empty comments part, relationship or content type).\n let matchedRuns: FXPNode[] | undefined;\n if (patch.find !== undefined && patch.find.length > 0) {\n const occurrence = patch.occurrence ?? 1;\n const plainFind = stripFormattingMarkers(patch.find, options.markers ?? true);\n const matchStart = findOccurrence(paragraph.text, plainFind, occurrence);\n if (matchStart === -1) {\n throw new Error(\n `Text \"${plainFind}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchStart + plainFind.length);\n if (ranges.length === 0) {\n throw new Error(`No runs found for \"${plainFind}\" in paragraph \"${paragraph.id}\"`);\n }\n matchedRuns = splitRunsForMatch(paragraph.xmlRef, ranges);\n if (matchedRuns.length === 0) {\n throw new Error(`Failed to isolate matched runs in paragraph \"${paragraph.id}\"`);\n }\n }\n\n const commentsRoot = ensureCommentsPart(doc);\n const id = nextCommentId(commentsRoot);\n\n const rangeStart = createElement(\"w:commentRangeStart\", [], { \"w:id\": id });\n const rangeEnd = createElement(\"w:commentRangeEnd\", [], { \"w:id\": id });\n const refRun = buildReferenceRun(id, doc);\n\n if (matchedRuns) {\n const firstRun = matchedRuns[0]!;\n const lastRun = matchedRuns[matchedRuns.length - 1]!;\n\n const firstParent = findRunParentChildren(paragraph.xmlRef, firstRun);\n if (!firstParent) throw new Error(\"Matched run not found in paragraph\");\n firstParent.splice(firstParent.indexOf(firstRun), 0, rangeStart);\n\n const lastParent = findRunParentChildren(paragraph.xmlRef, lastRun);\n if (!lastParent) throw new Error(\"Matched run not found in paragraph\");\n lastParent.splice(lastParent.indexOf(lastRun) + 1, 0, rangeEnd, refRun);\n } else {\n const pChildren = getChildren(paragraph.xmlRef);\n const pPr = findChild(paragraph.xmlRef, \"w:pPr\");\n const startIdx = pPr ? pChildren.indexOf(pPr) + 1 : 0;\n pChildren.splice(startIdx, 0, rangeStart);\n pChildren.push(rangeEnd, refRun);\n }\n\n getChildren(commentsRoot).push(\n buildCommentElement(id, author, initials, options.date, patch.text, doc, options.markers ?? true),\n );\n}\n","/**\n * Patch engine: executes patches sequentially against a NormalizedDocument.\n *\n * After each patch:\n * 1. The FXP object is mutated directly (via xmlRef pointers)\n * 2. The affected paragraph is re-normalized to update text + run mappings\n *\n * This is the \"Option A: re-normalize after each patch\" approach — simple\n * and correct. Can be optimized to incremental updates later.\n *\n * With `atomic: true`, the whole batch runs against a deep clone of the\n * document; the clone replaces the document only if every patch succeeds.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedTable,\n Patch,\n PatchResult,\n PatchOptions,\n AppendTextPatch,\n AddTablePatch,\n DocxXmlParts,\n FXPNode,\n} from \"../types.js\";\nimport {\n validatePatch,\n findParagraphById,\n findTableById,\n findElementById,\n} from \"./validator.js\";\nimport {\n executeReplaceText,\n executeInsertText,\n executeDeleteText,\n} from \"./text-ops.js\";\nimport {\n executeAddTableRow,\n executeDeleteTableRow,\n executeReplaceCellText,\n buildTableNode,\n} from \"./table-ops.js\";\nimport { executeApplyFormatting } from \"./format-ops.js\";\nimport { executeApplyStyle } from \"./style-ops.js\";\nimport { normalizeParagraph } from \"../normalizer/paragraph.js\";\nimport { normalizeTable } from \"../normalizer/table.js\";\nimport { enrichListType, reNormalizeDocument } from \"../normalizer/document.js\";\nimport { createRevisionContext, type RevisionContext } from \"./revision.js\";\nimport {\n findChildren,\n getChildren,\n getTagName,\n createElement,\n} from \"../xml/helpers.js\";\nimport { createInsElement, createTrPrIns } from \"./revision.js\";\nimport {\n executeAddHeader,\n executeAddFooter,\n executeRemoveHeader,\n executeRemoveFooter,\n} from \"./header-footer-ops.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport {\n executeAddList,\n executeAddListItem,\n executeRemoveListItem,\n executeConvertToList,\n executeRemoveList,\n executeSetListProperties,\n} from \"./list-ops.js\";\nimport { createNumberingDefinition } from \"./numbering.js\";\nimport { parseTextSegments } from \"./text-segments.js\";\nimport {\n getBodyChildren,\n insertIntoBody,\n reNormalizeParagraph,\n} from \"./container-utils.js\";\nimport { executeInsertParagraph, executeDeleteParagraph } from \"./paragraph-ops.js\";\nimport { executeAddComment } from \"./comment-ops.js\";\nimport { stampNewParagraphs, findPartRootForId } from \"../normalizer/para-id.js\";\n\n/**\n * Apply a list of patches to a NormalizedDocument.\n *\n * Patches are applied sequentially in order. After each patch,\n * the affected elements are re-normalized so subsequent patches\n * operate on updated text/mappings.\n *\n * @param options - Optional patch options (e.g., revision tracking, atomic)\n * @returns Array of PatchResult for each patch\n */\nexport function applyPatches(\n doc: NormalizedDocument,\n patches: Patch[],\n options?: PatchOptions,\n): PatchResult[] {\n if (options?.atomic) {\n return applyPatchesAtomic(doc, patches, options);\n }\n return applyPatchesSequential(doc, patches, options);\n}\n\nfunction applyPatchesSequential(\n doc: NormalizedDocument,\n patches: Patch[],\n options?: PatchOptions,\n): PatchResult[] {\n const results: PatchResult[] = [];\n\n // Create revision context if tracking revisions\n const revCtx = options?.trackRevisions\n ? createRevisionContext(doc, options)\n : undefined;\n\n for (const patch of patches) {\n const result = applySinglePatch(doc, patch, revCtx, options);\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * All-or-nothing: apply every patch to a deep clone; swap the clone in only\n * if every patch succeeded. The results describe every patch either way.\n */\nfunction applyPatchesAtomic(\n doc: NormalizedDocument,\n patches: Patch[],\n options: PatchOptions,\n): PatchResult[] {\n const clone = cloneDocument(doc);\n const results = applyPatchesSequential(clone, patches, { ...options, atomic: false });\n\n if (results.every((r) => r.success)) {\n doc.body = clone.body;\n doc.headers = clone.headers;\n doc.footers = clone.footers;\n doc.xmlParts = clone.xmlParts;\n }\n\n return results;\n}\n\n/**\n * Deep-clone a document's XML parts and rebuild the normalized view over\n * the clone, so no xmlRef points back into the original.\n */\nexport function cloneDocument(doc: NormalizedDocument): NormalizedDocument {\n const xmlParts = {} as DocxXmlParts;\n for (const [key, value] of Object.entries(doc.xmlParts)) {\n if (value === undefined) continue;\n if (value instanceof Map) {\n const copy = new Map<string, FXPNode[]>();\n for (const [k, v] of value) copy.set(k, JSON.parse(JSON.stringify(v)) as FXPNode[]);\n xmlParts[key] = copy;\n } else {\n xmlParts[key] = JSON.parse(JSON.stringify(value)) as FXPNode[];\n }\n }\n\n const clone: NormalizedDocument = {\n body: [],\n headers: new Map(),\n footers: new Map(),\n xmlParts,\n styles: doc.styles,\n loadOptions: doc.loadOptions,\n };\n reNormalizeDocument(clone);\n return clone;\n}\n\n/**\n * Whether inline formatting markers apply to a patch: the patch's own\n * `markers` field wins, then `PatchOptions.inlineMarkers`, then `true`.\n */\nfunction markersFor(patch: Patch, options?: PatchOptions): boolean {\n const own = (patch as { markers?: boolean }).markers;\n return own ?? options?.inlineMarkers ?? true;\n}\n\n/**\n * Apply a single patch and re-normalize affected elements.\n */\nfunction applySinglePatch(\n doc: NormalizedDocument,\n patch: Patch,\n revCtx?: RevisionContext,\n options?: PatchOptions,\n): PatchResult {\n // Validate\n const validation = validatePatch(patch, doc);\n if (!validation.valid) {\n return { success: false, patch, error: validation.error };\n }\n\n const markers = markersFor(patch, options);\n\n try {\n switch (patch.type) {\n case \"replace_text\": {\n const para = findParagraphById(patch.target, doc)!;\n executeReplaceText(para, patch, revCtx, markers);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"insert_text\": {\n const para = findParagraphById(patch.target, doc)!;\n executeInsertText(para, patch, revCtx, markers);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"delete_text\": {\n const para = findParagraphById(patch.target, doc)!;\n executeDeleteText(para, patch, revCtx, markers);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"insert_paragraph\": {\n const para = findParagraphById(patch.target, doc)!;\n executeInsertParagraph(doc, para, patch, revCtx, markers);\n break;\n }\n case \"delete_paragraph\": {\n const para = findParagraphById(patch.target, doc)!;\n executeDeleteParagraph(doc, para, patch, revCtx);\n break;\n }\n case \"add_comment\": {\n const para = findParagraphById(patch.target, doc)!;\n executeAddComment(doc, para, patch, {\n defaultAuthor: options?.revisionAuthor ?? \"docxtor\",\n date: options?.revisionDate ?? new Date().toISOString(),\n markers,\n });\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"replace_cell_text\": {\n const para = findParagraphById(patch.target, doc)!;\n executeReplaceCellText(para, patch, revCtx, markers);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"add_table_row\": {\n const table = findTableById(patch.target, doc)!;\n const newRow = executeAddTableRow(table, patch, revCtx);\n // The cloned row carries the source row's paraIds — give it fresh ones\n stampNewParagraphs(doc, [newRow], findPartRootForId(doc, table.id), true);\n reNormalizeTable(doc, table);\n break;\n }\n case \"delete_table_row\": {\n const table = findTableById(patch.target, doc)!;\n executeDeleteTableRow(table, patch, revCtx);\n reNormalizeTable(doc, table);\n break;\n }\n case \"apply_formatting\": {\n const para = findParagraphById(patch.target, doc)!;\n executeApplyFormatting(para, patch, revCtx);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"apply_style\": {\n const para = findParagraphById(patch.target, doc)!;\n executeApplyStyle(para, patch, doc, revCtx);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"append_text\": {\n executeAppendText(doc, patch, revCtx, markers);\n break;\n }\n case \"add_table\": {\n executeAddTablePatch(doc, patch, revCtx);\n break;\n }\n case \"add_header\": {\n executeAddHeader(doc, patch, revCtx, markers);\n break;\n }\n case \"add_footer\": {\n executeAddFooter(doc, patch, revCtx, markers);\n break;\n }\n case \"remove_header\": {\n executeRemoveHeader(doc, patch);\n break;\n }\n case \"remove_footer\": {\n executeRemoveFooter(doc, patch);\n break;\n }\n case \"add_list\": {\n executeAddList(doc, patch, revCtx, markers);\n break;\n }\n case \"add_list_item\": {\n executeAddListItem(doc, patch, revCtx, markers);\n break;\n }\n case \"remove_list_item\": {\n executeRemoveListItem(doc, patch, revCtx);\n break;\n }\n case \"convert_to_list\": {\n executeConvertToList(doc, patch, revCtx);\n break;\n }\n case \"remove_list\": {\n executeRemoveList(doc, patch, revCtx);\n break;\n }\n case \"set_list_properties\": {\n executeSetListProperties(doc, patch, revCtx);\n break;\n }\n }\n\n return { success: true, patch };\n } catch (err) {\n return {\n success: false,\n patch,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Re-normalize a table after a structural change (row add/delete).\n * This replaces the table's rows/cells with fresh normalized data.\n */\nfunction reNormalizeTable(\n doc: NormalizedDocument,\n table: NormalizedTable,\n): void {\n const fresh = normalizeTable(table.xmlRef, table.id);\n\n // Update in-place\n table.rows = fresh.rows;\n table.style = fresh.style;\n\n if (doc.xmlParts.numbering) {\n for (const row of table.rows) {\n for (const cell of row.cells) {\n for (const para of cell.paragraphs) {\n enrichListType(para, doc.xmlParts.numbering);\n }\n }\n }\n }\n}\n\n// =============================================================================\n// Append text (document-level operation)\n// =============================================================================\n\n/**\n * Compute the next available paragraph and table IDs.\n */\nfunction getNextIds(doc: NormalizedDocument): {\n nextPIdx: number;\n nextTblIdx: number;\n} {\n let maxPIdx = -1;\n let maxTblIdx = -1;\n for (const el of doc.body) {\n if (el.kind === \"paragraph\") {\n const num = parseInt(el.id.replace(\"p\", \"\"), 10);\n if (!isNaN(num) && num > maxPIdx) maxPIdx = num;\n } else if (el.kind === \"table\") {\n const num = parseInt(el.id.replace(\"tbl\", \"\"), 10);\n if (!isNaN(num) && num > maxTblIdx) maxTblIdx = num;\n }\n }\n return { nextPIdx: maxPIdx + 1, nextTblIdx: maxTblIdx + 1 };\n}\n\n// =============================================================================\n// Revision tracking for new tables\n// =============================================================================\n\n/**\n * Mark all rows in a table node as inserted revisions.\n * Adds w:ins to w:trPr for each w:tr in the table.\n */\nfunction markTableRowsAsInserted(\n tableNode: FXPNode,\n revCtx: RevisionContext,\n): void {\n const trNodes = findChildren(tableNode, \"w:tr\");\n for (const trNode of trNodes) {\n const rowChildren = getChildren(trNode);\n let trPr = rowChildren.find((c) => \"w:trPr\" in c);\n if (!trPr) {\n trPr = createElement(\"w:trPr\", []);\n rowChildren.unshift(trPr);\n }\n const trPrChildren = getChildren(trPr);\n trPrChildren.push(createTrPrIns(revCtx));\n }\n}\n\n// =============================================================================\n// Execute append_text\n// =============================================================================\n\n/**\n * Execute an append_text patch: creates new w:p and/or w:tbl elements at the\n * end of the document body (or a header/footer if container is specified).\n *\n * Supports markdown table syntax — pipe-delimited rows with a separator line\n * are automatically converted to OOXML tables.\n * Supports {{FIELD}} placeholders (e.g., {{PAGE}}, {{NUMPAGES}}).\n *\n * Inserts before w:sectPr if present so section properties stay last.\n * With revision tracking, new paragraph runs are wrapped in w:ins.\n */\nfunction executeAppendText(\n doc: NormalizedDocument,\n patch: AppendTextPatch,\n revCtx: RevisionContext | undefined,\n markers: boolean,\n): void {\n if (patch.container) {\n // Target a header/footer container\n executeAppendTextToContainer(doc, patch, revCtx, markers);\n return;\n }\n\n const bodyChildren = getBodyChildren(doc);\n const segments = parseTextSegments(patch.text);\n const newNodes: FXPNode[] = [];\n\n // Track list context for numId reuse across table segments\n let lastListNumId: string | undefined;\n let lastListType: \"bullet\" | \"ordered\" | undefined;\n\n for (const segment of segments) {\n if (segment.kind === \"text\") {\n // A non-blank text segment resets list context\n if (segment.lines.some((l) => l.trim().length > 0)) {\n lastListNumId = undefined;\n lastListType = undefined;\n }\n for (const line of segment.lines) {\n const runs = buildFieldRuns(line, undefined, markers);\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n newNodes.push({ \"w:p\": [insElement] });\n } else {\n newNodes.push({ \"w:p\": runs });\n }\n }\n } else if (segment.kind === \"table\") {\n const tblNode = buildTableNode(segment.headers, segment.rows);\n if (revCtx) {\n markTableRowsAsInserted(tblNode, revCtx);\n }\n newNodes.push(tblNode);\n // Don't reset list context — lists continue across tables\n } else {\n // List segment\n let numId: string;\n if (lastListNumId && lastListType === segment.listType) {\n numId = lastListNumId;\n } else {\n numId = createNumberingDefinition(doc, segment.listType);\n }\n lastListNumId = numId;\n lastListType = segment.listType;\n\n for (const item of segment.items) {\n const numPr = createElement(\"w:numPr\", [\n createElement(\"w:ilvl\", [], { \"w:val\": String(item.level) }),\n createElement(\"w:numId\", [], { \"w:val\": numId }),\n ]);\n const pPr = createElement(\"w:pPr\", [numPr]);\n const runs = buildFieldRuns(item.text, undefined, markers);\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n newNodes.push({ \"w:p\": [pPr, insElement] });\n } else {\n newNodes.push({ \"w:p\": [pPr, ...runs] });\n }\n }\n }\n }\n\n stampNewParagraphs(doc, newNodes, findPartRootForId(doc, \"body\"));\n\n // Insert all new nodes before w:sectPr\n insertIntoBody(bodyChildren, newNodes);\n\n // Normalize and add to doc.body\n let { nextPIdx, nextTblIdx } = getNextIds(doc);\n for (const node of newNodes) {\n if (\"w:tbl\" in node) {\n const id = `tbl${nextTblIdx++}`;\n const normalized = normalizeTable(node, id);\n doc.body.push(normalized);\n } else {\n const id = `p${nextPIdx++}`;\n const normalized = normalizeParagraph(node, id);\n if (doc.xmlParts.numbering) {\n enrichListType(normalized, doc.xmlParts.numbering);\n }\n doc.body.push(normalized);\n }\n }\n}\n\n// =============================================================================\n// Execute add_table\n// =============================================================================\n\n/**\n * Execute an add_table patch: creates a new w:tbl element and inserts it\n * into the document body.\n */\nfunction executeAddTablePatch(\n doc: NormalizedDocument,\n patch: AddTablePatch,\n revCtx?: RevisionContext,\n): void {\n if (patch.container) {\n if (patch.afterId) {\n throw new Error(`\"afterId\" is not supported when \"container\" is set`);\n }\n executeAddTableToContainer(doc, patch, revCtx);\n return;\n }\n\n const bodyChildren = getBodyChildren(doc);\n const tableNode = buildTableNode(patch.headers, patch.rows);\n\n if (revCtx) {\n markTableRowsAsInserted(tableNode, revCtx);\n }\n\n stampNewParagraphs(doc, [tableNode], findPartRootForId(doc, \"body\"));\n\n // Find the afterId target's xmlRef if specified\n let afterXmlRef: FXPNode | undefined;\n let afterElementId: string | undefined;\n if (patch.afterId) {\n const target = findElementById(patch.afterId, doc);\n if (target) {\n afterXmlRef = target.xmlRef;\n afterElementId = target.id;\n }\n }\n\n insertIntoBody(bodyChildren, [tableNode], afterXmlRef);\n\n // Normalize and add to doc.body\n const { nextTblIdx } = getNextIds(doc);\n const id = `tbl${nextTblIdx}`;\n const normalized = normalizeTable(tableNode, id);\n\n // Insert into body array at correct position (after the target element)\n if (afterElementId !== undefined) {\n const targetIdx = doc.body.findIndex((el) => el.id === afterElementId);\n if (targetIdx !== -1) {\n doc.body.splice(targetIdx + 1, 0, normalized);\n return;\n }\n }\n doc.body.push(normalized);\n}\n\n// =============================================================================\n// Container helpers (header/footer targeting for append_text and add_table)\n// =============================================================================\n\n/**\n * Find a header/footer's root FXP node by type and variant.\n */\nfunction findContainerRoot(\n doc: NormalizedDocument,\n containerType: \"header\" | \"footer\",\n variant: string,\n): { rootNode: FXPNode; hf: import(\"../types.js\").HeaderFooter } | undefined {\n const map = containerType === \"header\" ? doc.headers : doc.footers;\n for (const hf of map.values()) {\n if (hf.variant === variant) {\n const rootTag = containerType === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const rootNode = hf.xmlRef.find((n) => getTagName(n) === rootTag);\n if (rootNode) return { rootNode, hf };\n }\n }\n return undefined;\n}\n\n/**\n * Append text to a header/footer container.\n */\nfunction executeAppendTextToContainer(\n doc: NormalizedDocument,\n patch: AppendTextPatch,\n revCtx: RevisionContext | undefined,\n markers: boolean,\n): void {\n const container = patch.container!;\n const result = findContainerRoot(doc, container.type, container.variant);\n if (!result) {\n throw new Error(\n `${container.type} with variant \"${container.variant}\" not found`,\n );\n }\n\n const { rootNode, hf } = result;\n const rootChildren = getChildren(rootNode);\n const prefix = container.type === \"header\" ? \"hdr\" : \"ftr\";\n\n const segments = parseTextSegments(patch.text);\n let nextPIdx = hf.paragraphs.length;\n\n // Track list context for numId reuse across segments (same as body path)\n let lastListNumId: string | undefined;\n let lastListType: \"bullet\" | \"ordered\" | undefined;\n\n for (const segment of segments) {\n if (segment.kind === \"text\") {\n // A non-blank text segment resets list context\n if (segment.lines.some((l) => l.trim().length > 0)) {\n lastListNumId = undefined;\n lastListType = undefined;\n }\n for (const line of segment.lines) {\n let pNode: FXPNode;\n const runs = buildFieldRuns(line, undefined, markers);\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n pNode = { \"w:p\": [insElement] };\n } else {\n pNode = { \"w:p\": runs };\n }\n stampNewParagraphs(doc, [pNode], rootNode);\n rootChildren.push(pNode);\n const normalized = normalizeParagraph(\n pNode,\n `${prefix}.${container.variant}.p${nextPIdx++}`,\n );\n hf.paragraphs.push(normalized);\n }\n } else if (segment.kind === \"table\") {\n // Table segment — add to container XML\n const tblNode = buildTableNode(segment.headers, segment.rows);\n if (revCtx) {\n markTableRowsAsInserted(tblNode, revCtx);\n }\n stampNewParagraphs(doc, [tblNode], rootNode);\n rootChildren.push(tblNode);\n\n // Normalize and track\n const nextTblIdx = hf.tables.length;\n const tblId = `${prefix}.${container.variant}.tbl${nextTblIdx}`;\n const normalizedTbl = normalizeTable(tblNode, tblId);\n hf.tables.push(normalizedTbl);\n } else {\n // List segment in container — reuse numId if same list type\n let numId: string;\n if (lastListNumId && lastListType === segment.listType) {\n numId = lastListNumId;\n } else {\n numId = createNumberingDefinition(doc, segment.listType);\n }\n lastListNumId = numId;\n lastListType = segment.listType;\n for (const item of segment.items) {\n const numPr = createElement(\"w:numPr\", [\n createElement(\"w:ilvl\", [], { \"w:val\": String(item.level) }),\n createElement(\"w:numId\", [], { \"w:val\": numId }),\n ]);\n const pPr = createElement(\"w:pPr\", [numPr]);\n const runs = buildFieldRuns(item.text, undefined, markers);\n let pNode: FXPNode;\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n pNode = { \"w:p\": [pPr, insElement] };\n } else {\n pNode = { \"w:p\": [pPr, ...runs] };\n }\n stampNewParagraphs(doc, [pNode], rootNode);\n rootChildren.push(pNode);\n const normalized = normalizeParagraph(\n pNode,\n `${prefix}.${container.variant}.p${nextPIdx++}`,\n );\n if (doc.xmlParts.numbering) {\n enrichListType(normalized, doc.xmlParts.numbering);\n }\n hf.paragraphs.push(normalized);\n }\n }\n }\n}\n\n/**\n * Execute add_table targeting a header/footer container.\n */\nfunction executeAddTableToContainer(\n doc: NormalizedDocument,\n patch: AddTablePatch,\n revCtx?: RevisionContext,\n): void {\n const container = patch.container!;\n const result = findContainerRoot(doc, container.type, container.variant);\n if (!result) {\n throw new Error(\n `${container.type} with variant \"${container.variant}\" not found`,\n );\n }\n\n const { rootNode, hf } = result;\n const rootChildren = getChildren(rootNode);\n const tableNode = buildTableNode(patch.headers, patch.rows);\n\n if (revCtx) {\n markTableRowsAsInserted(tableNode, revCtx);\n }\n\n stampNewParagraphs(doc, [tableNode], rootNode);\n rootChildren.push(tableNode);\n\n // Normalize and track\n const prefix = container.type === \"header\" ? \"hdr\" : \"ftr\";\n const nextTblIdx = hf.tables.length;\n const id = `${prefix}.${container.variant}.tbl${nextTblIdx}`;\n const normalized = normalizeTable(tableNode, id);\n hf.tables.push(normalized);\n}\n","/**\n * LLM projection formats: convert a NormalizedDocument into\n * simplified representations that are easy for LLMs to understand\n * and generate patches against.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n RevisionMark,\n RunFormatting,\n RunFilter,\n RunMapping,\n StyleInfo,\n ProjectionOptions,\n} from \"../types.js\";\nimport { getLevelInfo } from \"../patches/numbering.js\";\n\n// =============================================================================\n// JSON Projection\n// =============================================================================\n\nexport interface JsonProjectionRunFormatting extends RunFormatting {\n /** The run's text content. */\n text: string;\n /** Start character offset in paragraph text (inclusive). */\n startOffset: number;\n /** End character offset in paragraph text (exclusive). */\n endOffset: number;\n}\n\ninterface JsonProjectionParagraph {\n id: string;\n /** Native `w14:paraId`, when the paragraph has one. */\n paraId?: string;\n text: string;\n style?: string;\n listLevel?: number;\n listType?: string;\n numId?: string;\n formatting?: JsonProjectionRunFormatting[];\n revisions?: RevisionMark[];\n hasPageBreak?: boolean;\n pageBreakOffset?: number;\n}\n\ninterface JsonProjectionTableCell {\n id: string;\n /** Native `w14:paraId` of the cell's first paragraph, when it has one. */\n paraId?: string;\n text: string;\n paragraphs?: JsonProjectionParagraph[];\n}\n\ninterface JsonProjectionTable {\n id: string;\n style?: string;\n rows: JsonProjectionTableCell[][];\n nestedTables?: JsonProjectionTable[];\n}\n\ninterface JsonProjectionHeaderFooter {\n type: \"header\" | \"footer\";\n variant: string;\n paragraphs: JsonProjectionParagraph[];\n tables?: JsonProjectionTable[];\n}\n\nexport interface JsonProjectionPageBreak {\n id: string;\n type: \"page_break\";\n}\n\nexport interface JsonProjection {\n body: (JsonProjectionParagraph | JsonProjectionTable | JsonProjectionPageBreak)[];\n headers?: JsonProjectionHeaderFooter[];\n footers?: JsonProjectionHeaderFooter[];\n styles?: StyleInfo[];\n}\n\n/**\n * Project a NormalizedDocument to a JSON structure suitable for LLMs.\n */\nexport function projectToJson(\n doc: NormalizedDocument,\n options: ProjectionOptions = {},\n): JsonProjection {\n const runFilter = options.runFilter;\n const includeFormatting = (options.includeFormatting ?? false) || runFilter !== undefined;\n const includeHeadersFooters = options.includeHeadersFooters ?? true;\n const includeTables = options.includeTables ?? true;\n const includeRevisions = options.includeRevisions ?? false;\n const includePageBreaks = options.includePageBreaks ?? false;\n\n const body: (JsonProjectionParagraph | JsonProjectionTable | JsonProjectionPageBreak)[] = [];\n\n for (const element of doc.body) {\n if (element.kind === \"paragraph\") {\n body.push(projectParagraphToJson(element, includeFormatting, includeRevisions, includePageBreaks, runFilter));\n } else if (element.kind === \"table\" && includeTables) {\n body.push(projectTableToJson(element, includeFormatting, includeRevisions, runFilter));\n } else if (element.kind === \"page_break\" && includePageBreaks) {\n body.push({ id: element.id, type: \"page_break\" });\n }\n }\n\n const result: JsonProjection = { body };\n\n // Include available styles if present\n if (doc.styles.length > 0) {\n result.styles = doc.styles;\n }\n\n if (includeHeadersFooters) {\n if (doc.headers.size > 0) {\n result.headers = [];\n for (const header of doc.headers.values()) {\n const hfEntry: JsonProjectionHeaderFooter = {\n type: \"header\",\n variant: header.variant,\n paragraphs: header.paragraphs.map((p) =>\n projectParagraphToJson(p, includeFormatting, includeRevisions, false, runFilter),\n ),\n };\n if (includeTables && header.tables.length > 0) {\n hfEntry.tables = header.tables.map((t) =>\n projectTableToJson(t, includeFormatting, includeRevisions, runFilter),\n );\n }\n result.headers.push(hfEntry);\n }\n }\n if (doc.footers.size > 0) {\n result.footers = [];\n for (const footer of doc.footers.values()) {\n const hfEntry: JsonProjectionHeaderFooter = {\n type: \"footer\",\n variant: footer.variant,\n paragraphs: footer.paragraphs.map((p) =>\n projectParagraphToJson(p, includeFormatting, includeRevisions, false, runFilter),\n ),\n };\n if (includeTables && footer.tables.length > 0) {\n hfEntry.tables = footer.tables.map((t) =>\n projectTableToJson(t, includeFormatting, includeRevisions, runFilter),\n );\n }\n result.footers.push(hfEntry);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Whether a run's formatting satisfies every property of a RunFilter.\n * String comparisons are case-insensitive so colour hex casing doesn't matter.\n */\nexport function runMatchesFilter(formatting: RunFormatting, filter: RunFilter): boolean {\n const eqStr = (a: string | undefined, b: string | undefined): boolean =>\n a !== undefined && b !== undefined && a.toUpperCase() === b.toUpperCase();\n\n if (filter.bold !== undefined && (formatting.bold ?? false) !== filter.bold) return false;\n if (filter.italic !== undefined && (formatting.italic ?? false) !== filter.italic) return false;\n if (filter.strikethrough !== undefined && (formatting.strikethrough ?? false) !== filter.strikethrough) return false;\n if (filter.underline !== undefined && !eqStr(formatting.underline, filter.underline)) return false;\n if (filter.color !== undefined && !eqStr(formatting.color, filter.color)) return false;\n if (filter.highlight !== undefined && !eqStr(formatting.highlight, filter.highlight)) return false;\n return true;\n}\n\nfunction projectParagraphToJson(\n para: NormalizedParagraph,\n includeFormatting: boolean,\n includeRevisions: boolean = false,\n includePageBreaks: boolean = false,\n runFilter?: RunFilter,\n): JsonProjectionParagraph {\n const result: JsonProjectionParagraph = {\n id: para.id,\n text: para.text,\n };\n\n if (para.paraId) {\n result.paraId = para.paraId;\n }\n\n if (para.style) {\n result.style = para.style;\n }\n\n if (para.listInfo) {\n result.listLevel = para.listInfo.level;\n if (para.listInfo.listType) {\n result.listType = para.listInfo.listType;\n }\n result.numId = para.listInfo.numId;\n }\n\n if (includeFormatting && para.runs.length > 0) {\n const fmtRuns: JsonProjectionRunFormatting[] = [];\n for (const r of para.runs) {\n if (runFilter) {\n if (!runMatchesFilter(r.formatting, runFilter)) continue;\n } else {\n // Only include runs that have at least one formatting property set\n const hasAny =\n r.formatting.bold ||\n r.formatting.italic ||\n r.formatting.underline ||\n r.formatting.strikethrough ||\n r.formatting.fontSize ||\n r.formatting.fontFamily ||\n r.formatting.color ||\n r.formatting.highlight;\n if (!hasAny) continue;\n }\n\n const fmt: JsonProjectionRunFormatting = {\n text: r.text,\n startOffset: r.startOffset,\n endOffset: r.endOffset,\n };\n if (r.formatting.bold) fmt.bold = true;\n if (r.formatting.italic) fmt.italic = true;\n if (r.formatting.underline) fmt.underline = r.formatting.underline;\n if (r.formatting.strikethrough) fmt.strikethrough = true;\n if (r.formatting.fontSize) fmt.fontSize = r.formatting.fontSize;\n if (r.formatting.fontFamily) fmt.fontFamily = r.formatting.fontFamily;\n if (r.formatting.color) fmt.color = r.formatting.color;\n if (r.formatting.themeColor) fmt.themeColor = r.formatting.themeColor;\n if (r.formatting.highlight) fmt.highlight = r.formatting.highlight;\n fmtRuns.push(fmt);\n }\n if (fmtRuns.length > 0) {\n result.formatting = fmtRuns;\n }\n }\n\n if (includeRevisions && para.revisions && para.revisions.length > 0) {\n result.revisions = para.revisions;\n }\n\n if (includePageBreaks && para.hasPageBreak) {\n result.hasPageBreak = true;\n if (para.pageBreakOffset !== undefined) {\n result.pageBreakOffset = para.pageBreakOffset;\n }\n }\n\n return result;\n}\n\nfunction projectTableToJson(\n table: NormalizedTable,\n includeFormatting: boolean = false,\n includeRevisions: boolean = false,\n runFilter?: RunFilter,\n): JsonProjectionTable {\n const result: JsonProjectionTable = {\n id: table.id,\n style: table.style,\n rows: table.rows.map((row) =>\n row.cells.map((cell) => {\n const cellResult: JsonProjectionTableCell = {\n id: cell.paragraphs[0]?.id ?? `${table.id}.r${row.index}.c${cell.colIndex}.p0`,\n text: cell.paragraphs.map((p) => p.text).join(\"\\n\"),\n };\n if (cell.paragraphs[0]?.paraId) {\n cellResult.paraId = cell.paragraphs[0].paraId;\n }\n // Include individual paragraph details for multi-paragraph cells,\n // or when filtering runs (so matching runs in cells are reported)\n if (cell.paragraphs.length > 1 || runFilter !== undefined) {\n cellResult.paragraphs = cell.paragraphs.map((p) =>\n projectParagraphToJson(p, includeFormatting, includeRevisions, false, runFilter),\n );\n }\n return cellResult;\n }),\n ),\n };\n\n // Include nested tables if any exist\n const allNested: JsonProjectionTable[] = [];\n for (const row of table.rows) {\n for (const cell of row.cells) {\n for (const nested of cell.nestedTables) {\n allNested.push(projectTableToJson(nested, includeFormatting, includeRevisions, runFilter));\n }\n }\n }\n if (allNested.length > 0) {\n result.nestedTables = allNested;\n }\n\n return result;\n}\n\n// =============================================================================\n// List prefix rendering\n// =============================================================================\n\n/**\n * Format a number according to a w:numFmt value.\n */\nfunction formatNumber(n: number, fmt: string): string {\n switch (fmt) {\n case \"decimal\":\n return String(n);\n case \"lowerLetter\": {\n // 1→a, 2→b, ..., 26→z, 27→aa\n let result = \"\";\n let v = n;\n while (v > 0) {\n v--;\n result = String.fromCharCode(97 + (v % 26)) + result;\n v = Math.floor(v / 26);\n }\n return result;\n }\n case \"upperLetter\": {\n let result = \"\";\n let v = n;\n while (v > 0) {\n v--;\n result = String.fromCharCode(65 + (v % 26)) + result;\n v = Math.floor(v / 26);\n }\n return result;\n }\n case \"lowerRoman\":\n return toRoman(n).toLowerCase();\n case \"upperRoman\":\n return toRoman(n);\n default:\n return String(n);\n }\n}\n\nfunction toRoman(n: number): string {\n const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];\n const syms = [\"M\", \"CM\", \"D\", \"CD\", \"C\", \"XC\", \"L\", \"XL\", \"X\", \"IX\", \"V\", \"IV\", \"I\"];\n let result = \"\";\n let v = n;\n for (let i = 0; i < vals.length; i++) {\n while (v >= vals[i]!) {\n result += syms[i];\n v -= vals[i]!;\n }\n }\n return result;\n}\n\n/** Default ordered formats per level (cycling). */\nconst DEFAULT_ORDERED_FMTS = [\"decimal\", \"lowerLetter\", \"lowerRoman\"];\n\n/**\n * Build list prefix string for a paragraph, updating counter state.\n * Returns empty string for non-list paragraphs.\n */\nfunction buildListPrefix(\n para: NormalizedParagraph,\n counters: Map<string, number>,\n numberingFxp?: import(\"../types.js\").FXPNode[],\n): string {\n if (!para.listInfo) return \"\";\n\n const { numId, level, listType } = para.listInfo;\n const indent = \" \".repeat(level);\n\n // Clear all deeper-level counters for this numId\n for (const key of counters.keys()) {\n const [kid, klvl] = key.split(\":\");\n if (kid === numId && Number(klvl) > level) {\n counters.delete(key);\n }\n }\n\n // Increment this level's counter\n const counterKey = `${numId}:${level}`;\n const count = (counters.get(counterKey) ?? 0) + 1;\n counters.set(counterKey, count);\n\n if (listType === \"bullet\" || !listType) {\n return `${indent}- `;\n }\n\n if (listType === \"multilevel\" && numberingFxp) {\n // Resolve compound prefix from w:lvlText pattern\n const info = getLevelInfo(numberingFxp, numId, level);\n if (info) {\n // Replace %N placeholders with counter values\n let prefix = info.pattern;\n const placeholders = prefix.match(/%(\\d+)/g);\n if (placeholders) {\n for (const ph of placeholders) {\n const lvlIdx = parseInt(ph.slice(1), 10) - 1;\n const lvlCount = counters.get(`${numId}:${lvlIdx}`) ?? 1;\n const fmt = info.ancestorFormats.get(lvlIdx) ?? \"decimal\";\n prefix = prefix.replaceAll(ph, formatNumber(lvlCount, fmt));\n }\n }\n return `${indent}${prefix} `;\n }\n }\n\n // Ordered (independent counters) — resolve format from numbering.xml or use defaults\n let fmt = DEFAULT_ORDERED_FMTS[level % DEFAULT_ORDERED_FMTS.length] ?? \"decimal\";\n if (numberingFxp) {\n const info = getLevelInfo(numberingFxp, numId, level);\n if (info) {\n fmt = info.format;\n }\n }\n return `${indent}${formatNumber(count, fmt)}. `;\n}\n\n// =============================================================================\n// Markdown Projection\n// =============================================================================\n\n/**\n * Project a NormalizedDocument to a markdown-like string for LLMs.\n *\n * Format:\n * ```\n * [p0] This is a paragraph\n * [p1] (Heading1) This is a heading\n *\n * [tbl0] Table\n * | [tbl0.r0.c0.p0] Header A | [tbl0.r0.c1.p0] Header B |\n * | [tbl0.r1.c0.p0] Cell 1 | [tbl0.r1.c1.p0] Cell 2 |\n *\n * --- Header (default) ---\n * [hdr.default.p0] Header text\n *\n * --- Footer (default) ---\n * [ftr.default.p0] Footer text\n * ```\n */\nexport function projectToMarkdown(\n doc: NormalizedDocument,\n options: ProjectionOptions = {},\n): string {\n const includeHeadersFooters = options.includeHeadersFooters ?? true;\n const includeTables = options.includeTables ?? true;\n const includeRevisions = options.includeRevisions ?? false;\n const includePageBreaks = options.includePageBreaks ?? false;\n const includeFormatting = options.includeFormatting ?? false;\n const lines: string[] = [];\n\n // List counter state: \"numId:level\" → current count\n const listCounters = new Map<string, number>();\n const numberingFxp = doc.xmlParts.numbering;\n\n for (const element of doc.body) {\n if (element.kind === \"paragraph\") {\n const listPrefix = buildListPrefix(element, listCounters, numberingFxp);\n lines.push(formatParagraphMarkdown(element, includeRevisions, includePageBreaks, includeFormatting, listPrefix));\n } else if (element.kind === \"table\" && includeTables) {\n lines.push(\"\"); // blank line before table\n lines.push(...formatTableMarkdown(element, includeRevisions, includeFormatting));\n lines.push(\"\"); // blank line after table\n } else if (element.kind === \"page_break\" && includePageBreaks) {\n lines.push(\"\");\n lines.push(`[${element.id}] --- PAGE BREAK ---`);\n lines.push(\"\");\n }\n }\n\n if (includeHeadersFooters) {\n for (const header of doc.headers.values()) {\n lines.push(\"\");\n lines.push(`--- Header (${header.variant}) ---`);\n const hdrCounters = new Map<string, number>();\n for (const para of header.paragraphs) {\n const listPrefix = buildListPrefix(para, hdrCounters, numberingFxp);\n lines.push(formatParagraphMarkdown(para, includeRevisions, false, includeFormatting, listPrefix));\n }\n if (includeTables) {\n for (const table of header.tables) {\n lines.push(\"\");\n lines.push(...formatTableMarkdown(table, includeRevisions));\n }\n }\n }\n\n for (const footer of doc.footers.values()) {\n lines.push(\"\");\n lines.push(`--- Footer (${footer.variant}) ---`);\n const ftrCounters = new Map<string, number>();\n for (const para of footer.paragraphs) {\n const listPrefix = buildListPrefix(para, ftrCounters, numberingFxp);\n lines.push(formatParagraphMarkdown(para, includeRevisions, false, includeFormatting, listPrefix));\n }\n if (includeTables) {\n for (const table of footer.tables) {\n lines.push(\"\");\n lines.push(...formatTableMarkdown(table, includeRevisions));\n }\n }\n }\n }\n\n // Include available styles\n if (doc.styles.length > 0) {\n lines.push(\"\");\n lines.push(\"--- Available Styles ---\");\n for (const style of doc.styles) {\n lines.push(`[${style.type}] ${style.id} — ${style.name}`);\n }\n }\n\n return lines.join(\"\\n\").trim();\n}\n\nfunction formatParagraphMarkdown(\n para: NormalizedParagraph,\n includeRevisions: boolean = false,\n includePageBreaks: boolean = false,\n includeFormatting: boolean = false,\n listPrefix: string = \"\",\n): string {\n const styleHint = para.style ? ` (${para.style})` : \"\";\n\n let text = includeFormatting\n ? annotateTextWithFormatting(para.text, para.runs)\n : para.text;\n const hasRevisions = includeRevisions && para.revisions && para.revisions.length > 0;\n\n if (hasRevisions) {\n if (includeFormatting) {\n // Map revision offsets through formatting annotation shift\n const mappedRevisions = para.revisions!.map((rev) => ({\n ...rev,\n startOffset: mapOffsetToFormattedText(rev.startOffset, para.runs),\n endOffset: mapOffsetToFormattedText(rev.endOffset, para.runs),\n }));\n text = annotateTextWithRevisions(text, mappedRevisions);\n } else {\n text = annotateTextWithRevisions(para.text, para.revisions!);\n }\n }\n\n // Insert [PAGE BREAK] marker at the correct position in the text.\n if (includePageBreaks && para.hasPageBreak) {\n let offset = para.pageBreakOffset ?? 0;\n\n if (includeFormatting) {\n offset = mapOffsetToFormattedText(offset, para.runs);\n }\n\n if (hasRevisions) {\n if (includeFormatting) {\n // Revisions have already been mapped through formatting, so use mapped revisions\n const mappedRevisions = para.revisions!.map((rev) => ({\n ...rev,\n startOffset: mapOffsetToFormattedText(rev.startOffset, para.runs),\n endOffset: mapOffsetToFormattedText(rev.endOffset, para.runs),\n }));\n offset = mapOffsetToAnnotatedText(offset, mappedRevisions);\n } else {\n offset = mapOffsetToAnnotatedText(offset, para.revisions!);\n }\n }\n\n const before = text.slice(0, offset);\n const after = text.slice(offset);\n if (before.length === 0) {\n text = `[PAGE BREAK] ${after}`;\n } else if (after.length === 0) {\n text = `${before} [PAGE BREAK]`;\n } else {\n text = `${before} [PAGE BREAK] ${after}`;\n }\n }\n\n const listHint = para.listInfo ? ` {list:${para.listInfo.numId} L${para.listInfo.level}}` : \"\";\n return `[${formatIdToken(para)}]${styleHint}${listHint} ${listPrefix}${text}`;\n}\n\n/**\n * The id token shown in markdown brackets: `p12`, or `p12 ¶4F2A1C7B` when\n * the paragraph carries a native paraId.\n */\nfunction formatIdToken(para: NormalizedParagraph): string {\n return para.paraId ? `${para.id} ¶${para.paraId}` : para.id;\n}\n\n/**\n * Annotate paragraph text with GFM inline formatting markers.\n *\n * Walks the runs and wraps text spans with:\n * - **bold**\n * - _italic_\n * - ~~strikethrough~~\n * - <u>underline</u>\n * - Combined: **_bold+italic_**\n *\n * Each run is wrapped independently. Runs with no representable\n * formatting are emitted as plain text.\n */\nfunction annotateTextWithFormatting(\n text: string,\n runs: RunMapping[],\n): string {\n if (runs.length === 0) return text;\n\n const parts: string[] = [];\n let lastEnd = 0;\n\n for (const run of runs) {\n // Fill any gap between runs with plain text\n if (run.startOffset > lastEnd) {\n parts.push(text.slice(lastEnd, run.startOffset));\n }\n\n const runText = text.slice(run.startOffset, run.endOffset);\n const { bold, italic, strikethrough, underline } = run.formatting;\n\n if (!bold && !italic && !strikethrough && !underline) {\n parts.push(runText);\n } else {\n let wrapped = runText;\n // Inner wrapping first, then outer\n if (underline) wrapped = `<u>${wrapped}</u>`;\n if (italic) wrapped = `_${wrapped}_`;\n if (bold) wrapped = `**${wrapped}**`;\n if (strikethrough) wrapped = `~~${wrapped}~~`;\n parts.push(wrapped);\n }\n\n lastEnd = run.endOffset;\n }\n\n // Trailing text after last run\n if (lastEnd < text.length) {\n parts.push(text.slice(lastEnd));\n }\n\n return parts.join(\"\");\n}\n\n/**\n * Map an offset in the original paragraph text to the corresponding\n * position in the formatting-annotated string.\n *\n * Formatting markers (**, _, ~~) add characters that shift positions.\n * This walks runs in document order and accumulates the shift.\n */\nfunction mapOffsetToFormattedText(\n originalOffset: number,\n runs: RunMapping[],\n): number {\n let shift = 0;\n\n for (const run of runs) {\n const { bold, italic, strikethrough, underline } = run.formatting;\n if (!bold && !italic && !strikethrough && !underline) continue;\n\n // Calculate marker lengths for this run\n let prefixLen = 0;\n let suffixLen = 0;\n if (strikethrough) { prefixLen += 2; suffixLen += 2; }\n if (bold) { prefixLen += 2; suffixLen += 2; }\n if (italic) { prefixLen += 1; suffixLen += 1; }\n if (underline) { prefixLen += 3; suffixLen += 4; } // <u> and </u>\n\n if (originalOffset >= run.startOffset) {\n shift += prefixLen;\n }\n if (originalOffset >= run.endOffset) {\n shift += suffixLen;\n }\n }\n\n return originalOffset + shift;\n}\n\n/**\n * Map an offset in the original paragraph text to the corresponding\n * position in the revision-annotated string.\n *\n * Revision markers ({+...+} for inserts, {-...-} for deletes) add\n * characters that shift later positions. This walks revisions in\n * document order (ascending startOffset) and accumulates the shift.\n */\nfunction mapOffsetToAnnotatedText(\n originalOffset: number,\n revisions: RevisionMark[],\n): number {\n // Sort by startOffset ascending — we accumulate shift left-to-right\n const sorted = [...revisions].sort((a, b) => a.startOffset - b.startOffset);\n let shift = 0;\n\n for (const rev of sorted) {\n if (rev.type === \"insert\") {\n // {+...+} wraps text at [startOffset, endOffset).\n // The 2-char prefix \"{+\" is inserted at startOffset.\n // If the break is past (or at) startOffset, it shifts by +2 for the prefix.\n // The 2-char suffix \"+}\" is inserted at endOffset.\n // If the break is past (or at) endOffset, it shifts by another +2.\n if (originalOffset >= rev.startOffset) shift += 2; // \"{+\"\n if (originalOffset >= rev.endOffset) shift += 2; // \"+}\"\n } else if (rev.type === \"delete\") {\n // {-text-} is inserted at startOffset (zero-width in original).\n // Total insertion = text.length + 4 chars (\"{-\" + text + \"-}\").\n // If the break is at or past the delete position, shift by the full marker length.\n if (originalOffset >= rev.startOffset) {\n shift += rev.text.length + 4;\n }\n }\n }\n\n return originalOffset + shift;\n}\n\n/**\n * Annotate paragraph text with revision markers.\n *\n * Inserts are shown as `{+text+}` and deletes as `{-text-}`.\n * Revisions are applied from right-to-left to avoid offset shifting.\n */\nfunction annotateTextWithRevisions(\n text: string,\n revisions: RevisionMark[],\n): string {\n // Sort by startOffset descending so we can splice from right-to-left\n const sorted = [...revisions].sort((a, b) => b.startOffset - a.startOffset);\n\n let result = text;\n for (const rev of sorted) {\n if (rev.type === \"insert\") {\n // The inserted text is already in .text at [startOffset, endOffset)\n // Wrap it with {+...+}\n const before = result.slice(0, rev.startOffset);\n const inserted = result.slice(rev.startOffset, rev.endOffset);\n const after = result.slice(rev.endOffset);\n result = `${before}{+${inserted}+}${after}`;\n } else if (rev.type === \"delete\") {\n // Deleted text is NOT in .text (startOffset === endOffset is a zero-width position)\n // Insert the deleted text marker at that position\n const before = result.slice(0, rev.startOffset);\n const after = result.slice(rev.startOffset);\n result = `${before}{-${rev.text}-}${after}`;\n }\n }\n\n return result;\n}\n\nfunction formatTableMarkdown(\n table: NormalizedTable,\n includeRevisions: boolean = false,\n includeFormatting: boolean = false,\n): string[] {\n const lines: string[] = [];\n\n for (const row of table.rows) {\n const cells = row.cells.map((cell) => {\n let text: string;\n if (includeFormatting) {\n text = cell.paragraphs\n .map((p) => {\n let t = annotateTextWithFormatting(p.text, p.runs);\n if (includeRevisions && p.revisions && p.revisions.length > 0) {\n const mappedRevisions = p.revisions.map((rev) => ({\n ...rev,\n startOffset: mapOffsetToFormattedText(rev.startOffset, p.runs),\n endOffset: mapOffsetToFormattedText(rev.endOffset, p.runs),\n }));\n t = annotateTextWithRevisions(t, mappedRevisions);\n }\n return t;\n })\n .join(\" \");\n } else if (includeRevisions) {\n const annotated = cell.paragraphs.map((p) => {\n if (p.revisions && p.revisions.length > 0) {\n return annotateTextWithRevisions(p.text, p.revisions);\n }\n return p.text;\n });\n text = annotated.join(\" \");\n } else {\n text = cell.paragraphs.map((p) => p.text).join(\" \");\n }\n const first = cell.paragraphs[0];\n const id = first ? formatIdToken(first) : `${table.id}.r${row.index}.c${cell.colIndex}.p0`;\n return `[${id}] ${text}`;\n });\n lines.push(`| ${cells.join(\" | \")} |`);\n }\n\n return lines;\n}\n","/**\n * LLM projection: generates simplified document representations\n * for LLM consumption.\n *\n * The projection strips away XML complexity and presents the document\n * as a flat list of paragraphs with IDs and text content. The LLM\n * can then emit patches referencing these IDs.\n */\n\nimport type {\n NormalizedDocument,\n ProjectionFormat,\n ProjectionOptions,\n} from \"../types.js\";\nimport { projectToJson, projectToMarkdown } from \"./formats.js\";\nimport type { JsonProjection, JsonProjectionPageBreak, JsonProjectionRunFormatting } from \"./formats.js\";\n\n/**\n * Project a NormalizedDocument to an LLM-friendly format.\n *\n * @param doc - The normalized document\n * @param format - \"json\" or \"markdown\"\n * @param options - Projection options\n * @returns The projected representation (object for JSON, string for markdown)\n */\nexport function project(\n doc: NormalizedDocument,\n format: ProjectionFormat = \"json\",\n options: ProjectionOptions = {},\n): JsonProjection | string {\n switch (format) {\n case \"json\":\n return projectToJson(doc, options);\n case \"markdown\":\n return projectToMarkdown(doc, options);\n default:\n throw new Error(`Unknown projection format: ${format}`);\n }\n}\n\nexport { projectToJson, projectToMarkdown };\nexport type { JsonProjection, JsonProjectionPageBreak, JsonProjectionRunFormatting };\n","/**\n * Accept or reject every tracked change in a document.\n *\n * Walks document.xml, headers, footers, footnotes, endnotes and comments,\n * resolving each revision element according to the mode:\n *\n * | Element | Accept | Reject |\n * |--------------------------------------|--------------------------------|-----------------------------------|\n * | `w:ins` (runs) | unwrap | remove |\n * | `w:del` (runs) | remove | unwrap, w:delText → w:t |\n * | `w:moveTo` / `w:moveFrom` | as ins / as del | as ins / as del |\n * | `w:*PrChange` | drop | restore old properties |\n * | row `w:trPr/w:ins` / `w:del` | drop marker / remove row | remove row / drop marker |\n * | cell `w:cellIns` / `w:cellDel` | drop marker / remove cell | remove cell / drop marker |\n * | paragraph mark `w:ins` | drop marker | merge paragraph into the next |\n * | paragraph mark `w:del` | merge paragraph into the next | drop marker |\n *\n * With an `authors` filter only revisions by those authors are touched.\n * The caller must re-normalize the document afterwards (positional ids may\n * change; `paraId`s do not).\n */\n\nimport type { NormalizedDocument, FXPNode, RevisionFilterOptions } from \"../types.js\";\nimport {\n cloneNode,\n findChild,\n getAttr,\n getChildren,\n getTagName,\n needsSpacePreserve,\n setAttr,\n unescapeXmlText,\n} from \"../xml/helpers.js\";\n\ntype Mode = \"accept\" | \"reject\";\n\ninterface WalkContext {\n mode: Mode;\n authors?: Set<string>;\n count: number;\n /**\n * Ids of `w:moveFromRangeStart` / `w:moveToRangeStart` markers that match\n * the author filter, collected per part. Range *end* markers carry only an\n * id, so they are matched through this set.\n */\n moveRangeIds: Set<string>;\n}\n\n/** Property-change elements and the siblings to keep when restoring the snapshot. */\nconst PROP_CHANGE_KEEP: Record<string, Set<string>> = {\n \"w:rPrChange\": new Set([\"w:ins\", \"w:del\", \"w:moveFrom\", \"w:moveTo\"]),\n \"w:pPrChange\": new Set([\"w:rPr\", \"w:sectPr\"]),\n \"w:trPrChange\": new Set([\"w:ins\", \"w:del\"]),\n \"w:tcPrChange\": new Set([\"w:cellIns\", \"w:cellDel\", \"w:cellMerge\"]),\n \"w:tblPrChange\": new Set(),\n \"w:tblPrExChange\": new Set(),\n \"w:tblGridChange\": new Set(),\n \"w:sectPrChange\": new Set(),\n};\n\n/** Parents whose `w:ins` / `w:del` children are markers owned by w:p / w:tr, not run containers. */\nconst MARKER_PARENTS = new Set([\"w:rPr\", \"w:trPr\", \"w:tcPr\"]);\n\nconst MOVE_RANGE_START_TAGS = new Set([\"w:moveFromRangeStart\", \"w:moveToRangeStart\"]);\nconst MOVE_RANGE_END_TAGS = new Set([\"w:moveFromRangeEnd\", \"w:moveToRangeEnd\"]);\n\n/** Elements that may sit between two paragraphs without separating them. */\nconst TRANSPARENT_SIBLINGS = new Set([\n \"w:bookmarkStart\",\n \"w:bookmarkEnd\",\n \"w:proofErr\",\n \"w:permStart\",\n \"w:permEnd\",\n \"w:commentRangeStart\",\n \"w:commentRangeEnd\",\n \"w:customXmlInsRangeStart\",\n \"w:customXmlInsRangeEnd\",\n \"w:customXmlDelRangeStart\",\n \"w:customXmlDelRangeEnd\",\n]);\n\n// =============================================================================\n// Public API\n// =============================================================================\n\nexport function acceptAllRevisions(\n doc: NormalizedDocument,\n options?: RevisionFilterOptions,\n): { accepted: number } {\n const ctx = makeContext(\"accept\", options);\n walkAllParts(doc, ctx);\n return { accepted: ctx.count };\n}\n\nexport function rejectAllRevisions(\n doc: NormalizedDocument,\n options?: RevisionFilterOptions,\n): { rejected: number } {\n const ctx = makeContext(\"reject\", options);\n walkAllParts(doc, ctx);\n return { rejected: ctx.count };\n}\n\nfunction makeContext(mode: Mode, options?: RevisionFilterOptions): WalkContext {\n return {\n mode,\n authors: options?.authors ? new Set(options.authors) : undefined,\n count: 0,\n moveRangeIds: new Set(),\n };\n}\n\nfunction walkAllParts(doc: NormalizedDocument, ctx: WalkContext): void {\n const parts: FXPNode[][] = [\n doc.xmlParts.document,\n ...doc.xmlParts.headers.values(),\n ...doc.xmlParts.footers.values(),\n ];\n for (const key of [\"footnotes\", \"endnotes\", \"comments\"] as const) {\n const part = doc.xmlParts[key];\n if (part) parts.push(part);\n }\n for (const part of parts) {\n ctx.moveRangeIds = collectMatchingMoveRangeIds(part, ctx);\n processChildren(part, undefined, ctx);\n }\n}\n\nfunction matches(node: FXPNode, ctx: WalkContext): boolean {\n if (!ctx.authors) return true;\n const author = getAttr(node, \"w:author\");\n return author !== undefined && ctx.authors.has(author);\n}\n\n/**\n * Pre-pass: ids of move range start markers that match the author filter,\n * so the author-less end markers can be removed together with them.\n */\nfunction collectMatchingMoveRangeIds(nodes: FXPNode[], ctx: WalkContext): Set<string> {\n const ids = new Set<string>();\n const visit = (list: FXPNode[]): void => {\n for (const node of list) {\n const tag = getTagName(node);\n if (!tag) continue;\n if (MOVE_RANGE_START_TAGS.has(tag) && matches(node, ctx)) {\n const id = getAttr(node, \"w:id\");\n if (id !== undefined) ids.add(id);\n }\n const children = getChildren(node);\n if (children.length > 0) visit(children);\n }\n };\n visit(nodes);\n return ids;\n}\n\n// =============================================================================\n// Walker\n// =============================================================================\n\n/**\n * Process a children array in place. Elements may be removed, unwrapped, or\n * merged, so the loop manages its own index.\n */\nfunction processChildren(\n children: FXPNode[],\n parentTag: string | undefined,\n ctx: WalkContext,\n): void {\n let i = 0;\n while (i < children.length) {\n const child = children[i]!;\n const tag = getTagName(child);\n if (!tag) {\n i++;\n continue;\n }\n\n const inMarkerParent = parentTag !== undefined && MARKER_PARENTS.has(parentTag);\n\n // --- Run-level insertions -------------------------------------------\n if ((tag === \"w:ins\" || tag === \"w:moveTo\") && !inMarkerParent) {\n if (matches(child, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n children.splice(i, 1, ...getChildren(child)); // unwrap; reprocess from i\n } else {\n children.splice(i, 1);\n }\n continue;\n }\n processChildren(getChildren(child), tag, ctx);\n i++;\n continue;\n }\n\n // --- Run-level deletions --------------------------------------------\n if ((tag === \"w:del\" || tag === \"w:moveFrom\") && !inMarkerParent) {\n if (matches(child, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n children.splice(i, 1);\n } else {\n const inner = getChildren(child);\n for (const node of inner) restoreDeletedContent(node);\n children.splice(i, 1, ...inner);\n }\n continue;\n }\n processChildren(getChildren(child), tag, ctx);\n i++;\n continue;\n }\n\n // --- Move range markers ---------------------------------------------\n // Start markers carry w:author; end markers only an id, matched through\n // the ids collected from their start markers.\n if (MOVE_RANGE_START_TAGS.has(tag) || MOVE_RANGE_END_TAGS.has(tag)) {\n const id = getAttr(child, \"w:id\");\n const remove = MOVE_RANGE_START_TAGS.has(tag)\n ? matches(child, ctx)\n : id !== undefined && ctx.moveRangeIds.has(id);\n if (remove) {\n children.splice(i, 1);\n continue;\n }\n i++;\n continue;\n }\n\n // --- Property changes -----------------------------------------------\n if (tag in PROP_CHANGE_KEEP) {\n if (matches(child, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n children.splice(i, 1);\n } else {\n restorePropertyChange(children, child, tag);\n }\n i = 0; // siblings may have been replaced\n continue;\n }\n i++;\n continue;\n }\n\n // --- Paragraphs: content first, then the paragraph mark ------------\n if (tag === \"w:p\") {\n processChildren(getChildren(child), tag, ctx);\n if (handleParagraphMark(children, i, child, ctx)) continue; // merged away\n i++;\n continue;\n }\n\n // --- Table rows -----------------------------------------------------\n if (tag === \"w:tr\") {\n if (handleRowMarkers(children, i, child, ctx)) continue; // row removed\n processChildren(getChildren(child), tag, ctx);\n i++;\n continue;\n }\n\n // --- Table cells ----------------------------------------------------\n if (tag === \"w:tc\") {\n if (handleCellMarkers(children, i, child, ctx)) continue; // cell removed\n processChildren(getChildren(child), tag, ctx);\n i++;\n continue;\n }\n\n // --- Everything else: recurse ---------------------------------------\n const grandChildren = getChildren(child);\n if (grandChildren.length > 0) processChildren(grandChildren, tag, ctx);\n i++;\n }\n}\n\n// =============================================================================\n// Deleted content restoration (reject)\n// =============================================================================\n\n/**\n * Turn deleted run content back into live content: `w:delText` → `w:t`,\n * `w:delInstrText` → `w:instrText`. Recurses through inline containers.\n */\nfunction restoreDeletedContent(node: FXPNode): void {\n const tag = getTagName(node);\n if (!tag) return;\n\n if (tag === \"w:r\") {\n const children = getChildren(node);\n for (let i = 0; i < children.length; i++) {\n const c = children[i]!;\n if (\"w:delText\" in c) {\n const replacement: FXPNode = { \"w:t\": c[\"w:delText\"] as FXPNode[] };\n if (c[\":@\"]) replacement[\":@\"] = c[\":@\"];\n const text = textOf(replacement[\"w:t\"] as FXPNode[]);\n if (needsSpacePreserve(text)) setAttr(replacement, \"xml:space\", \"preserve\");\n children[i] = replacement;\n } else if (\"w:delInstrText\" in c) {\n const replacement: FXPNode = { \"w:instrText\": c[\"w:delInstrText\"] as FXPNode[] };\n if (c[\":@\"]) replacement[\":@\"] = c[\":@\"];\n children[i] = replacement;\n }\n }\n return;\n }\n\n for (const child of getChildren(node)) restoreDeletedContent(child);\n}\n\nfunction textOf(nodes: FXPNode[]): string {\n let text = \"\";\n for (const n of nodes) {\n if (\"#text\" in n) text += unescapeXmlText(String(n[\"#text\"]));\n }\n return text;\n}\n\n// =============================================================================\n// Property change restoration (reject)\n// =============================================================================\n\n/**\n * Replace the contents of a property container (`w:rPr`, `w:pPr`, ...) with\n * the snapshot stored inside its `*Change` child, keeping the siblings that\n * are not part of the snapshot (revision markers, paragraph-mark rPr, sectPr).\n */\nfunction restorePropertyChange(\n containerChildren: FXPNode[],\n changeNode: FXPNode,\n changeTag: string,\n): void {\n const keep = PROP_CHANGE_KEEP[changeTag] ?? new Set<string>();\n const snapshot = getChildren(changeNode).find((c) => getTagName(c) !== undefined);\n const snapshotChildren = snapshot ? getChildren(snapshot).map(cloneNode) : [];\n\n const kept = containerChildren.filter((c) => {\n const t = getTagName(c);\n return t !== undefined && keep.has(t) && c !== changeNode;\n });\n\n containerChildren.length = 0;\n if (changeTag === \"w:rPrChange\") {\n // Paragraph-mark markers come first in CT_ParaRPr\n containerChildren.push(...kept, ...snapshotChildren);\n } else {\n containerChildren.push(...snapshotChildren, ...kept);\n }\n}\n\n// =============================================================================\n// Paragraph marks\n// =============================================================================\n\n/**\n * Resolve `w:pPr/w:rPr/w:ins|w:del` on a paragraph.\n * Returns true if the paragraph was merged into the next one (and removed).\n */\nfunction handleParagraphMark(\n siblings: FXPNode[],\n index: number,\n pNode: FXPNode,\n ctx: WalkContext,\n): boolean {\n const pPr = findChild(pNode, \"w:pPr\");\n if (!pPr) return false;\n const rPr = findChild(pPr, \"w:rPr\");\n if (!rPr) return false;\n const rPrChildren = getChildren(rPr);\n\n let removeParagraph = false;\n\n const insIdx = rPrChildren.findIndex((c) => getTagName(c) === \"w:ins\");\n if (insIdx !== -1 && matches(rPrChildren[insIdx]!, ctx)) {\n ctx.count++;\n rPrChildren.splice(insIdx, 1);\n if (ctx.mode === \"reject\") removeParagraph = true;\n }\n\n const delIdx = rPrChildren.findIndex((c) => getTagName(c) === \"w:del\");\n if (delIdx !== -1 && matches(rPrChildren[delIdx]!, ctx)) {\n ctx.count++;\n rPrChildren.splice(delIdx, 1);\n if (ctx.mode === \"accept\") removeParagraph = true;\n }\n\n // Tidy empty property containers\n if (rPrChildren.length === 0) {\n const pPrChildren = getChildren(pPr);\n pPrChildren.splice(pPrChildren.indexOf(rPr), 1);\n if (pPrChildren.length === 0) {\n const pChildren = getChildren(pNode);\n pChildren.splice(pChildren.indexOf(pPr), 1);\n }\n }\n\n if (!removeParagraph) return false;\n return mergeIntoNextParagraph(siblings, index, pNode);\n}\n\n/**\n * Merge a paragraph's content into the following paragraph and remove it.\n * If there is no following paragraph (end of body / cell, or a table comes\n * next) the paragraph is kept, as Word does.\n */\nfunction mergeIntoNextParagraph(\n siblings: FXPNode[],\n index: number,\n pNode: FXPNode,\n): boolean {\n // A section-break paragraph must survive or the section is lost\n const pPr = findChild(pNode, \"w:pPr\");\n if (pPr && findChild(pPr, \"w:sectPr\")) return false;\n\n let j = index + 1;\n while (j < siblings.length) {\n const t = getTagName(siblings[j]!);\n if (t === \"w:p\") break;\n // Whitespace text nodes and range markers do not separate paragraphs\n if (t === undefined || TRANSPARENT_SIBLINGS.has(t)) {\n j++;\n continue;\n }\n return false;\n }\n if (j >= siblings.length) return false;\n\n const next = siblings[j]!;\n const nextChildren = getChildren(next);\n const nextPPr = findChild(next, \"w:pPr\");\n const insertAt = nextPPr ? nextChildren.indexOf(nextPPr) + 1 : 0;\n\n const content = getChildren(pNode).filter((c) => getTagName(c) !== \"w:pPr\");\n nextChildren.splice(insertAt, 0, ...content);\n siblings.splice(index, 1);\n return true;\n}\n\n// =============================================================================\n// Table rows and cells\n// =============================================================================\n\n/** Returns true if the row was removed. */\nfunction handleRowMarkers(\n siblings: FXPNode[],\n index: number,\n trNode: FXPNode,\n ctx: WalkContext,\n): boolean {\n const trPr = findChild(trNode, \"w:trPr\");\n if (!trPr) return false;\n const trPrChildren = getChildren(trPr);\n\n const insIdx = trPrChildren.findIndex((c) => getTagName(c) === \"w:ins\");\n if (insIdx !== -1 && matches(trPrChildren[insIdx]!, ctx)) {\n ctx.count++;\n if (ctx.mode === \"reject\") {\n siblings.splice(index, 1);\n return true;\n }\n trPrChildren.splice(insIdx, 1);\n }\n\n const delIdx = trPrChildren.findIndex((c) => getTagName(c) === \"w:del\");\n if (delIdx !== -1 && matches(trPrChildren[delIdx]!, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n siblings.splice(index, 1);\n return true;\n }\n trPrChildren.splice(delIdx, 1);\n }\n\n return false;\n}\n\n/** Returns true if the cell was removed. */\nfunction handleCellMarkers(\n siblings: FXPNode[],\n index: number,\n tcNode: FXPNode,\n ctx: WalkContext,\n): boolean {\n const tcPr = findChild(tcNode, \"w:tcPr\");\n if (!tcPr) return false;\n const tcPrChildren = getChildren(tcPr);\n\n const cellIns = tcPrChildren.findIndex((c) => getTagName(c) === \"w:cellIns\");\n if (cellIns !== -1 && matches(tcPrChildren[cellIns]!, ctx)) {\n ctx.count++;\n if (ctx.mode === \"reject\") {\n siblings.splice(index, 1);\n return true;\n }\n tcPrChildren.splice(cellIns, 1);\n }\n\n const cellDel = tcPrChildren.findIndex((c) => getTagName(c) === \"w:cellDel\");\n if (cellDel !== -1 && matches(tcPrChildren[cellDel]!, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n siblings.splice(index, 1);\n return true;\n }\n tcPrChildren.splice(cellDel, 1);\n }\n\n const cellMerge = tcPrChildren.find((c) => getTagName(c) === \"w:cellMerge\");\n if (cellMerge && matches(cellMerge, ctx)) {\n ctx.count++;\n if (ctx.mode === \"reject\") {\n restoreVerticalMerge(tcPrChildren, cellMerge); // may insert before the marker\n }\n tcPrChildren.splice(tcPrChildren.indexOf(cellMerge), 1);\n }\n\n return false;\n}\n\n/**\n * Rejecting a tracked vertical cell merge: put `w:vMerge` back to the state\n * recorded in `w:cellMerge/@w:vMergeOrig` (\"rest\" → restart, \"cont\" →\n * continue, absent → the cell was not merged, so drop `w:vMerge`).\n */\nfunction restoreVerticalMerge(tcPrChildren: FXPNode[], cellMerge: FXPNode): void {\n const orig = getAttr(cellMerge, \"w:vMergeOrig\");\n const existingIdx = tcPrChildren.findIndex((c) => getTagName(c) === \"w:vMerge\");\n\n if (orig === undefined) {\n if (existingIdx !== -1) tcPrChildren.splice(existingIdx, 1);\n return;\n }\n\n const vMerge: FXPNode = { \"w:vMerge\": [] };\n if (orig === \"rest\") setAttr(vMerge, \"w:val\", \"restart\");\n\n if (existingIdx !== -1) {\n tcPrChildren[existingIdx] = vMerge;\n } else {\n // Keep schema order: w:vMerge precedes w:tcBorders and the cell markers\n const beforeIdx = tcPrChildren.findIndex((c) => {\n const t = getTagName(c);\n return t === \"w:tcBorders\" || t === \"w:shd\" || t === \"w:noWrap\" || t === \"w:tcMar\" ||\n t === \"w:textDirection\" || t === \"w:tcFitText\" || t === \"w:vAlign\" || t === \"w:hideMark\" ||\n t === \"w:cellIns\" || t === \"w:cellDel\" || t === \"w:cellMerge\" || t === \"w:tcPrChange\";\n });\n if (beforeIdx === -1) tcPrChildren.push(vMerge);\n else tcPrChildren.splice(beforeIdx, 0, vMerge);\n }\n}\n","/**\n * Document metadata: docProps/core.xml, docProps/app.xml, docProps/custom.xml.\n *\n * - getCoreProperties / setCoreProperties read and write core.xml fields.\n * - scrubMetadata removes identifying information before a file leaves the\n * building: author fields in core.xml, Company/Manager/HyperlinkBase in\n * app.xml, and the whole custom.xml part.\n */\n\nimport type { NormalizedDocument, CoreProperties, FXPNode } from \"../types.js\";\nimport {\n createElement,\n escapeXmlText,\n getAttr,\n getChildren,\n getTagName,\n setAttr,\n unescapeXmlText,\n} from \"../xml/helpers.js\";\nimport {\n createRelsPart,\n createContentTypesPart,\n ensureRelationship,\n ensureContentTypeOverride,\n removeRelationshipsByType,\n removeContentTypeOverride,\n} from \"../patches/part-utils.js\";\n\nconst CP_NS = \"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\";\nconst DC_NS = \"http://purl.org/dc/elements/1.1/\";\nconst DCTERMS_NS = \"http://purl.org/dc/terms/\";\nconst DCMITYPE_NS = \"http://purl.org/dc/dcmitype/\";\nconst XSI_NS = \"http://www.w3.org/2001/XMLSchema-instance\";\n\nconst CORE_REL_TYPE =\n \"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties\";\nconst CORE_CONTENT_TYPE = \"application/vnd.openxmlformats-package.core-properties+xml\";\nconst CUSTOM_REL_TYPE =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties\";\n\n/** Core property → qualified element name as Word writes it. */\nconst CORE_TAGS: Record<keyof CoreProperties, string> = {\n creator: \"dc:creator\",\n lastModifiedBy: \"cp:lastModifiedBy\",\n title: \"dc:title\",\n subject: \"dc:subject\",\n description: \"dc:description\",\n keywords: \"cp:keywords\",\n category: \"cp:category\",\n contentStatus: \"cp:contentStatus\",\n revision: \"cp:revision\",\n created: \"dcterms:created\",\n modified: \"dcterms:modified\",\n};\n\n/** Fields cleared by scrubMetadata. */\nconst SCRUB_CORE_FIELDS: (keyof CoreProperties)[] = [\n \"creator\",\n \"lastModifiedBy\",\n \"description\",\n \"keywords\",\n \"category\",\n \"contentStatus\",\n];\n\n/** app.xml elements removed by scrubMetadata (matched by local name). */\nconst SCRUB_APP_FIELDS = new Set([\"Company\", \"Manager\", \"HyperlinkBase\"]);\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction localName(tag: string): string {\n const idx = tag.indexOf(\":\");\n return idx === -1 ? tag : tag.slice(idx + 1);\n}\n\nfunction findRoot(part: FXPNode[] | undefined, local: string): FXPNode | undefined {\n if (!part) return undefined;\n return part.find((n) => {\n const tag = getTagName(n);\n return tag !== undefined && localName(tag) === local;\n });\n}\n\nfunction findChildByLocalName(parent: FXPNode, local: string): FXPNode | undefined {\n return getChildren(parent).find((c) => {\n const tag = getTagName(c);\n return tag !== undefined && localName(tag) === local;\n });\n}\n\nfunction getElementText(node: FXPNode): string {\n let text = \"\";\n for (const c of getChildren(node)) {\n if (\"#text\" in c) text += unescapeXmlText(String(c[\"#text\"]));\n }\n return text;\n}\n\nfunction setElementText(node: FXPNode, text: string): void {\n const tag = getTagName(node)!;\n node[tag] = text.length > 0 ? [{ \"#text\": escapeXmlText(text) } as unknown as FXPNode] : [];\n}\n\nfunction removeChildByLocalName(parent: FXPNode, local: string): boolean {\n const children = getChildren(parent);\n const idx = children.findIndex((c) => {\n const tag = getTagName(c);\n return tag !== undefined && localName(tag) === local;\n });\n if (idx === -1) return false;\n children.splice(idx, 1);\n return true;\n}\n\n// =============================================================================\n// core.xml\n// =============================================================================\n\nfunction createCorePart(): FXPNode[] {\n return [\n { \"?xml\": [{ \"#text\": \"\" } as unknown as FXPNode], \":@\": { \"@_version\": \"1.0\", \"@_encoding\": \"UTF-8\", \"@_standalone\": \"yes\" } } as FXPNode,\n createElement(\"cp:coreProperties\", [], {\n \"xmlns:cp\": CP_NS,\n \"xmlns:dc\": DC_NS,\n \"xmlns:dcterms\": DCTERMS_NS,\n \"xmlns:dcmitype\": DCMITYPE_NS,\n \"xmlns:xsi\": XSI_NS,\n }),\n ];\n}\n\n/** Namespace declarations every element we may write into core.xml relies on. */\nconst CORE_NAMESPACES: Record<string, string> = {\n \"xmlns:cp\": CP_NS,\n \"xmlns:dc\": DC_NS,\n \"xmlns:dcterms\": DCTERMS_NS,\n \"xmlns:dcmitype\": DCMITYPE_NS,\n \"xmlns:xsi\": XSI_NS,\n};\n\n/**\n * Ensure docProps/core.xml exists as a parsed part, wired into the package,\n * and that its root declares every prefix we write (an existing file with no\n * date fields may lack `dcterms` / `xsi`).\n */\nfunction ensureCorePart(doc: NormalizedDocument): FXPNode {\n if (!doc.xmlParts.core) {\n doc.xmlParts.core = createCorePart();\n if (!doc.xmlParts.rootRels) doc.xmlParts.rootRels = createRelsPart();\n ensureRelationship(doc.xmlParts.rootRels, CORE_REL_TYPE, \"docProps/core.xml\");\n if (!doc.xmlParts.contentTypes) doc.xmlParts.contentTypes = createContentTypesPart();\n ensureContentTypeOverride(doc.xmlParts.contentTypes, \"/docProps/core.xml\", CORE_CONTENT_TYPE);\n }\n const root = findRoot(doc.xmlParts.core, \"coreProperties\");\n if (!root) throw new Error(\"docProps/core.xml has no coreProperties root\");\n for (const [attr, uri] of Object.entries(CORE_NAMESPACES)) {\n if (getAttr(root, attr) === undefined) setAttr(root, attr, uri);\n }\n return root;\n}\n\n/**\n * Read the core document properties. Missing fields are undefined.\n */\nexport function getCoreProperties(doc: NormalizedDocument): CoreProperties {\n const result: CoreProperties = {};\n const root = findRoot(doc.xmlParts.core, \"coreProperties\");\n if (!root) return result;\n\n for (const key of Object.keys(CORE_TAGS) as (keyof CoreProperties)[]) {\n const node = findChildByLocalName(root, localName(CORE_TAGS[key]));\n if (node) result[key] = getElementText(node);\n }\n return result;\n}\n\n/**\n * Set core document properties. Only the fields given are touched; an empty\n * string empties the element. Creates docProps/core.xml if the document has none.\n */\nexport function setCoreProperties(doc: NormalizedDocument, props: Partial<CoreProperties>): void {\n const root = ensureCorePart(doc);\n\n for (const key of Object.keys(props) as (keyof CoreProperties)[]) {\n const value = props[key];\n if (value === undefined || !(key in CORE_TAGS)) continue;\n\n const tag = CORE_TAGS[key];\n let node = findChildByLocalName(root, localName(tag));\n if (!node) {\n node = createElement(tag, []);\n getChildren(root).push(node);\n }\n setElementText(node, value);\n if (key === \"created\" || key === \"modified\") {\n setAttr(node, \"xsi:type\", \"dcterms:W3CDTF\");\n }\n }\n}\n\n// =============================================================================\n// Scrub\n// =============================================================================\n\n/**\n * Strip identifying metadata.\n *\n * - core.xml: removes creator, lastModifiedBy, description, keywords,\n * category, contentStatus; sets revision to \"1\". Title, created and\n * modified are left untouched.\n * - app.xml: removes Company, Manager, HyperlinkBase.\n * - custom.xml: removes the part, its package relationship and content type.\n *\n * @returns zip paths the caller must drop from the archive on save\n */\nexport function scrubMetadata(doc: NormalizedDocument): { removedParts: string[] } {\n const removedParts: string[] = [];\n\n const coreRoot = findRoot(doc.xmlParts.core, \"coreProperties\");\n if (coreRoot) {\n for (const key of SCRUB_CORE_FIELDS) {\n removeChildByLocalName(coreRoot, localName(CORE_TAGS[key]));\n }\n setCoreProperties(doc, { revision: \"1\" });\n }\n\n const appRoot = findRoot(doc.xmlParts.app, \"Properties\");\n if (appRoot) {\n const children = getChildren(appRoot);\n for (let i = children.length - 1; i >= 0; i--) {\n const tag = getTagName(children[i]!);\n if (tag !== undefined && SCRUB_APP_FIELDS.has(localName(tag))) {\n children.splice(i, 1);\n }\n }\n }\n\n const customTargets = removeRelationshipsByType(doc.xmlParts.rootRels, CUSTOM_REL_TYPE);\n removeContentTypeOverride(doc.xmlParts.contentTypes, \"/docProps/custom.xml\");\n removedParts.push(\"docProps/custom.xml\");\n for (const target of customTargets) {\n const path = target.replace(/^\\//, \"\");\n if (path && !removedParts.includes(path)) removedParts.push(path);\n }\n\n return { removedParts };\n}\n","/**\n * docxtor — Surgical, LLM-friendly DOCX editing.\n *\n * @example\n * ```ts\n * import { Docxtor } from \"docxtor\";\n * import { readFile, writeFile } from \"fs/promises\";\n *\n * // Load a document\n * const buffer = await readFile(\"contract.docx\");\n * const doc = await Docxtor.load(buffer);\n *\n * // Get an LLM-friendly projection\n * const projection = doc.project(\"markdown\");\n * // → \"[p0] (Heading1) Service Agreement\\n[p1] This agreement is valid...\"\n *\n * // Apply patches (e.g., from LLM output)\n * const results = doc.applyPatches([\n * { type: \"replace_text\", target: \"p1\", find: \"valid\", replace: \"enforceable\" },\n * ]);\n *\n * // Export the modified document\n * const output = await doc.toBuffer();\n * await writeFile(\"contract-edited.docx\", output);\n * ```\n *\n * @packageDocumentation\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport JSZip from \"jszip\";\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n Patch,\n PatchResult,\n PatchOptions,\n ProjectionFormat,\n ProjectionOptions,\n LoadOptions,\n BodyElement,\n HeaderFooter,\n StyleInfo,\n CoreProperties,\n RevisionFilterOptions,\n FXPNode,\n} from \"./types.js\";\nimport { readDocx, type DocxZipEntries } from \"./zip/reader.js\";\nimport { writeDocx } from \"./zip/writer.js\";\nimport { serializeXml } from \"./xml/serializer.js\";\nimport { normalizeDocument, reNormalizeDocument } from \"./normalizer/document.js\";\nimport { applyPatches } from \"./patches/engine.js\";\nimport { project, type JsonProjection } from \"./projection/llm.js\";\nimport {\n findParagraphById as _findParagraphById,\n findParagraphByParaId as _findParagraphByParaId,\n findTableById as _findTableById,\n validatePatch as _validatePatch,\n} from \"./patches/validator.js\";\nimport {\n acceptAllRevisions as _acceptAllRevisions,\n rejectAllRevisions as _rejectAllRevisions,\n} from \"./revisions/accept-reject.js\";\nimport {\n getCoreProperties as _getCoreProperties,\n setCoreProperties as _setCoreProperties,\n scrubMetadata as _scrubMetadata,\n} from \"./metadata/properties.js\";\n\n/**\n * The main entry point for docxtor.\n *\n * A Docxtor instance represents a loaded DOCX document with:\n * - A normalized AST for inspecting document structure\n * - An LLM projection system for generating simplified views\n * - A patch engine for applying surgical edits\n * - Export capabilities to produce modified DOCX files\n */\nexport class Docxtor {\n private zipEntries: DocxZipEntries;\n private doc: NormalizedDocument;\n /** Zip paths to drop on the next export (e.g., docProps/custom.xml after a scrub). */\n private removedParts = new Set<string>();\n\n private constructor(\n zipEntries: DocxZipEntries,\n doc: NormalizedDocument,\n ) {\n this.zipEntries = zipEntries;\n this.doc = doc;\n }\n\n /**\n * Load a DOCX document from a buffer or file path.\n */\n static async load(\n input: Buffer | ArrayBuffer | Uint8Array | string,\n options?: LoadOptions,\n ): Promise<Docxtor> {\n let buffer: Buffer | ArrayBuffer | Uint8Array;\n\n if (typeof input === \"string\") {\n buffer = await readFile(input);\n } else {\n buffer = input;\n }\n\n const zipEntries = await readDocx(buffer);\n const doc = normalizeDocument(zipEntries, options);\n\n return new Docxtor(zipEntries, doc);\n }\n\n /**\n * Create a new Docxtor instance.\n *\n * If `input` is provided, delegates to {@link Docxtor.load}.\n * Otherwise, creates a valid empty DOCX document in memory.\n *\n * @param input - Optional buffer, ArrayBuffer, Uint8Array, or file path to load from\n * @param options - Optional load options\n *\n * @example\n * ```ts\n * // Create a blank document\n * const doc = await Docxtor.create();\n *\n * // Equivalent to Docxtor.load(buffer)\n * const doc2 = await Docxtor.create(buffer);\n * ```\n */\n static async create(\n input?: Buffer | ArrayBuffer | Uint8Array | string,\n options?: LoadOptions,\n ): Promise<Docxtor> {\n if (input !== undefined) {\n return Docxtor.load(input, options);\n }\n\n // Build a minimal valid DOCX in memory\n const zip = new JSZip();\n\n const contentTypes = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n <Default Extension=\"xml\" ContentType=\"application/xml\"/>\n <Override PartName=\"/word/document.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml\"/>\n</Types>`;\n\n const topRels = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"word/document.xml\"/>\n</Relationships>`;\n\n const documentXml = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:document xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"\n xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n <w:body>\n <w:p>\n <w:r>\n <w:t></w:t>\n </w:r>\n </w:p>\n </w:body>\n</w:document>`;\n\n zip.file(\"[Content_Types].xml\", contentTypes);\n zip.file(\"_rels/.rels\", topRels);\n zip.file(\"word/document.xml\", documentXml);\n\n const buffer = await zip.generateAsync({\n type: \"nodebuffer\",\n compression: \"DEFLATE\",\n });\n\n return Docxtor.load(buffer, options);\n }\n\n // ===========================================================================\n // Projection\n // ===========================================================================\n\n /**\n * Generate an LLM-friendly projection of the document.\n *\n * @param format - \"json\" for structured data, \"markdown\" for text\n * @param options - Control what's included in the projection\n */\n project(format?: \"json\", options?: ProjectionOptions): JsonProjection;\n project(format: \"markdown\", options?: ProjectionOptions): string;\n project(\n format: ProjectionFormat = \"json\",\n options: ProjectionOptions = {},\n ): JsonProjection | string {\n return project(this.doc, format, options);\n }\n\n // ===========================================================================\n // Patching\n // ===========================================================================\n\n /**\n * Apply patches to the document.\n *\n * Patches are applied sequentially. After each patch, affected elements\n * are re-normalized so subsequent patches see updated text/mappings.\n *\n * @param patches - Array of patches to apply\n * @param options - Optional options (e.g., revision tracking)\n * @returns Array of results, one per patch\n */\n applyPatches(patches: Patch[], options?: PatchOptions): PatchResult[] {\n return applyPatches(this.doc, patches, options);\n }\n\n // ===========================================================================\n // Revisions\n // ===========================================================================\n\n /**\n * Accept every tracked change (optionally only those by `authors`):\n * insertions become plain content, deletions are removed, property changes\n * are dropped, deleted rows and paragraphs disappear.\n *\n * Positional ids are reassigned afterwards; `paraId`s are unchanged.\n */\n acceptAllRevisions(options?: RevisionFilterOptions): { accepted: number } {\n const result = _acceptAllRevisions(this.doc, options);\n reNormalizeDocument(this.doc);\n return result;\n }\n\n /**\n * Reject every tracked change (optionally only those by `authors`):\n * insertions are removed, deletions restored, old properties put back,\n * inserted rows and paragraphs disappear.\n *\n * Positional ids are reassigned afterwards; `paraId`s are unchanged.\n */\n rejectAllRevisions(options?: RevisionFilterOptions): { rejected: number } {\n const result = _rejectAllRevisions(this.doc, options);\n reNormalizeDocument(this.doc);\n return result;\n }\n\n // ===========================================================================\n // Metadata\n // ===========================================================================\n\n /** Read docProps/core.xml properties. */\n getCoreProperties(): CoreProperties {\n return _getCoreProperties(this.doc);\n }\n\n /** Set docProps/core.xml properties (creating the part if needed). */\n setCoreProperties(props: Partial<CoreProperties>): void {\n _setCoreProperties(this.doc, props);\n }\n\n /**\n * Strip identifying metadata: clears creator, lastModifiedBy, description,\n * keywords, category and contentStatus, sets revision to \"1\", clears\n * Company / Manager / HyperlinkBase in app.xml, and removes\n * docProps/custom.xml. Title, created and modified are left untouched.\n */\n scrubMetadata(): void {\n const { removedParts } = _scrubMetadata(this.doc);\n for (const path of removedParts) this.removedParts.add(path);\n }\n\n // ===========================================================================\n // Export\n // ===========================================================================\n\n /**\n * Export the current document as a raw OOXML string (word/document.xml content).\n *\n * Returns the serialized XML for the document body, reflecting any patches\n * that have been applied. This is the same XML that gets written into the\n * DOCX ZIP as word/document.xml.\n */\n toString(): string {\n return serializeXml(this.doc.xmlParts.document);\n }\n\n /**\n * Export the modified document as a Buffer.\n */\n async toBuffer(): Promise<Buffer> {\n const documentXml = serializeXml(this.doc.xmlParts.document);\n\n const headers = new Map<string, string>();\n for (const [path, fxp] of this.doc.xmlParts.headers) {\n headers.set(path, serializeXml(fxp));\n }\n\n const footers = new Map<string, string>();\n for (const [path, fxp] of this.doc.xmlParts.footers) {\n footers.set(path, serializeXml(fxp));\n }\n\n // Determine which header/footer zip entries should be removed from the\n // current in-memory zip state. This handles multi-export flows where a\n // part may be added in one export and removed in a later export.\n const removedParts: string[] = [];\n for (const path of Object.keys(this.zipEntries.zip.files)) {\n if (/^word\\/header\\d+\\.xml$/i.test(path)) {\n if (!this.doc.xmlParts.headers.has(path)) {\n removedParts.push(path);\n }\n } else if (/^word\\/footer\\d+\\.xml$/i.test(path)) {\n if (!this.doc.xmlParts.footers.has(path)) {\n removedParts.push(path);\n }\n }\n }\n\n for (const path of this.removedParts) {\n if (!removedParts.includes(path)) removedParts.push(path);\n }\n\n // Serialize every optional part that was parsed (or created on demand)\n const serialize = (part: FXPNode[] | undefined): string | undefined =>\n part ? serializeXml(part) : undefined;\n const parts = this.doc.xmlParts;\n\n return writeDocx({\n zip: this.zipEntries.zip,\n documentXml,\n headers: headers.size > 0 ? headers : undefined,\n footers: footers.size > 0 ? footers : undefined,\n documentRelsXml: serialize(parts.documentRels),\n contentTypesXml: serialize(parts.contentTypes),\n settingsXml: serialize(parts.settings),\n numberingXml: serialize(parts.numbering),\n footnotesXml: serialize(parts.footnotes),\n endnotesXml: serialize(parts.endnotes),\n commentsXml: serialize(parts.comments),\n coreXml: serialize(parts.core),\n appXml: serialize(parts.app),\n rootRelsXml: serialize(parts.rootRels),\n removedParts: removedParts.length > 0 ? removedParts : undefined,\n });\n }\n\n /**\n * Export the modified document to a file.\n */\n async toFile(filePath: string): Promise<void> {\n const { writeFile: fsWriteFile } = await import(\"node:fs/promises\");\n const buffer = await this.toBuffer();\n await fsWriteFile(filePath, buffer);\n }\n\n // ===========================================================================\n // Inspection\n // ===========================================================================\n\n /**\n * Get the full normalized AST.\n */\n getAST(): NormalizedDocument {\n return this.doc;\n }\n\n /**\n * Get all body-level elements (paragraphs and tables).\n */\n getBody(): BodyElement[] {\n return this.doc.body;\n }\n\n /**\n * Get all paragraphs in the document body (not inside tables).\n */\n getParagraphs(): NormalizedParagraph[] {\n return this.doc.body.filter(\n (e): e is NormalizedParagraph => e.kind === \"paragraph\",\n );\n }\n\n /**\n * Get all tables in the document body.\n */\n getTables(): NormalizedTable[] {\n return this.doc.body.filter(\n (e): e is NormalizedTable => e.kind === \"table\",\n );\n }\n\n /**\n * Get all headers.\n */\n getHeaders(): Map<string, HeaderFooter> {\n return this.doc.headers;\n }\n\n /**\n * Get all footers.\n */\n getFooters(): Map<string, HeaderFooter> {\n return this.doc.footers;\n }\n\n /**\n * Find a paragraph by its ID anywhere in the document\n * (body, tables, headers, footers, nested tables).\n */\n findParagraphById(id: string): NormalizedParagraph | undefined {\n return _findParagraphById(id, this.doc);\n }\n\n /**\n * Find a paragraph by its native `w14:paraId` (8 hex chars, case-insensitive).\n * Resolution order: body (including table cells), headers, footers.\n */\n findParagraphByParaId(paraId: string): NormalizedParagraph | undefined {\n return _findParagraphByParaId(paraId, this.doc);\n }\n\n /**\n * Find a table by its ID anywhere in the document body\n * (including nested tables).\n */\n findTableById(id: string): NormalizedTable | undefined {\n return _findTableById(id, this.doc);\n }\n\n /**\n * Get available styles from the document's styles.xml.\n * Useful for knowing which styles can be applied via apply_style patches.\n */\n getStyles(): StyleInfo[] {\n return this.doc.styles;\n }\n\n /**\n * Validate patches without applying them.\n * Returns validation results for each patch.\n */\n validatePatches(patches: Patch[]): { valid: boolean; error?: string }[] {\n return patches.map((patch) => _validatePatch(patch, this.doc));\n }\n}\n\n// Re-export types\nexport type {\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n NormalizedPageBreak,\n NormalizedTableRow,\n NormalizedTableCell,\n RunMapping,\n RunFormatting,\n RunFilter,\n ListInfo,\n HeaderFooter,\n BodyElement,\n Patch,\n ReplaceTextPatch,\n InsertTextPatch,\n DeleteTextPatch,\n AppendTextPatch,\n InsertParagraphPatch,\n DeleteParagraphPatch,\n AddCommentPatch,\n AddTablePatch,\n AddTableRowPatch,\n DeleteTableRowPatch,\n ReplaceCellTextPatch,\n ApplyFormattingPatch,\n ApplyStylePatch,\n AddHeaderPatch,\n AddFooterPatch,\n RemoveHeaderPatch,\n RemoveFooterPatch,\n AddListPatch,\n AddListItemPatch,\n RemoveListItemPatch,\n ConvertToListPatch,\n RemoveListPatch,\n SetListPropertiesPatch,\n HeaderFooterVariant,\n FormattingUpdate,\n StyleInfo,\n PatchResult,\n PatchOptions,\n RevisionMark,\n RevisionFilterOptions,\n CoreProperties,\n ProjectionFormat,\n ProjectionOptions,\n LoadOptions,\n FXPNode,\n} from \"./types.js\";\n\n// Re-export low-level utilities for advanced usage\nexport { readDocx } from \"./zip/reader.js\";\nexport { writeDocx } from \"./zip/writer.js\";\nexport { parseXml } from \"./xml/parser.js\";\nexport { serializeXml } from \"./xml/serializer.js\";\nexport { normalizeDocument, reNormalizeDocument } from \"./normalizer/document.js\";\nexport type { HeaderFooterRef } from \"./normalizer/document.js\";\nexport { applyPatches, cloneDocument } from \"./patches/engine.js\";\nexport { project, projectToJson, projectToMarkdown } from \"./projection/llm.js\";\nexport type { JsonProjection, JsonProjectionPageBreak, JsonProjectionRunFormatting } from \"./projection/llm.js\";\nexport { validatePatch, findParagraphById, findParagraphByParaId, findTableById } from \"./patches/validator.js\";\nexport type { ValidationResult } from \"./patches/validator.js\";\nexport { acceptAllRevisions, rejectAllRevisions } from \"./revisions/accept-reject.js\";\nexport { getCoreProperties, setCoreProperties, scrubMetadata } from \"./metadata/properties.js\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["getNextRelationshipId","removeContentTypeOverride","_acceptAllRevisions","_rejectAllRevisions","_getCoreProperties","_scrubMetadata","_findParagraphById","_findParagraphByParaId","_findTableById","_validatePatch"],"sources":["../src/zip/reader.ts","../src/zip/writer.ts","../src/xml/serializer.ts","../src/xml/parser.ts","../src/xml/helpers.ts","../src/normalizer/paragraph.ts","../src/normalizer/table.ts","../src/normalizer/header-footer.ts","../src/patches/numbering.ts","../src/normalizer/para-id.ts","../src/normalizer/document.ts","../src/patches/text-segments.ts","../src/patches/validator.ts","../src/mapping/text-to-runs.ts","../src/patches/run-content.ts","../src/patches/run-splitter.ts","../src/patches/revision.ts","../src/patches/markdown-parser.ts","../src/patches/text-ops.ts","../src/patches/table-ops.ts","../src/patches/format-ops.ts","../src/patches/style-ops.ts","../src/patches/field-codes.ts","../src/patches/header-footer-ops.ts","../src/patches/container-utils.ts","../src/patches/list-ops.ts","../src/patches/paragraph-ops.ts","../src/patches/part-utils.ts","../src/patches/comment-ops.ts","../src/patches/engine.ts","../src/projection/formats.ts","../src/projection/llm.ts","../src/revisions/accept-reject.ts","../src/metadata/properties.ts","../src/index.ts"],"sourcesContent":["/**\n * ZIP reader: extracts XML parts from a DOCX buffer using JSZip.\n *\n * DOCX files are ZIP archives. We extract the XML files we need to normalize\n * and keep the JSZip instance alive so we can write modifications back.\n */\n\nimport JSZip from \"jszip\";\n\n/** All the XML parts we extract from a DOCX file. */\nexport interface DocxZipEntries {\n /** The raw document.xml content. */\n documentXml: string;\n /** Header XML files keyed by filename (e.g., \"word/header1.xml\"). */\n headers: Map<string, string>;\n /** Footer XML files keyed by filename (e.g., \"word/footer1.xml\"). */\n footers: Map<string, string>;\n /** The relationships file for the main document part. */\n documentRels?: string;\n /** The content types file. */\n contentTypes?: string;\n /** The styles.xml file (may not be present). */\n stylesXml?: string;\n /** The settings.xml file (may not be present). */\n settingsXml?: string;\n /** The numbering.xml file (may not be present). */\n numberingXml?: string;\n /** The footnotes.xml file (may not be present). */\n footnotesXml?: string;\n /** The endnotes.xml file (may not be present). */\n endnotesXml?: string;\n /** The comments.xml file (may not be present). */\n commentsXml?: string;\n /** docProps/core.xml (may not be present). */\n coreXml?: string;\n /** docProps/app.xml (may not be present). */\n appXml?: string;\n /** _rels/.rels — package-level relationships (may not be present). */\n rootRels?: string;\n /** The JSZip instance (kept for writing back). */\n zip: JSZip;\n}\n\n/**\n * Read a DOCX buffer and extract all relevant XML parts.\n */\nexport async function readDocx(\n input: Buffer | ArrayBuffer | Uint8Array,\n): Promise<DocxZipEntries> {\n const zip = await JSZip.loadAsync(input);\n\n // document.xml is required\n const documentXml = await readEntry(zip, \"word/document.xml\");\n if (documentXml === undefined) {\n throw new Error(\n \"Invalid DOCX: missing word/document.xml\",\n );\n }\n\n // Read document relationships to discover headers/footers\n const documentRels = await readEntry(\n zip,\n \"word/_rels/document.xml.rels\",\n );\n const contentTypes = await readEntry(zip, \"[Content_Types].xml\");\n const stylesXml = await readEntry(zip, \"word/styles.xml\");\n const settingsXml = await readEntry(zip, \"word/settings.xml\");\n const numberingXml = await readEntry(zip, \"word/numbering.xml\");\n const footnotesXml = await readEntry(zip, \"word/footnotes.xml\");\n const endnotesXml = await readEntry(zip, \"word/endnotes.xml\");\n const commentsXml = await readEntry(zip, \"word/comments.xml\");\n const coreXml = await readEntry(zip, \"docProps/core.xml\");\n const appXml = await readEntry(zip, \"docProps/app.xml\");\n const rootRels = await readEntry(zip, \"_rels/.rels\");\n\n // Extract header and footer files\n const headers = new Map<string, string>();\n const footers = new Map<string, string>();\n\n // Scan zip for header/footer files\n for (const path of Object.keys(zip.files)) {\n if (/^word\\/header\\d+\\.xml$/i.test(path)) {\n const content = await readEntry(zip, path);\n if (content !== undefined) {\n headers.set(path, content);\n }\n } else if (/^word\\/footer\\d+\\.xml$/i.test(path)) {\n const content = await readEntry(zip, path);\n if (content !== undefined) {\n footers.set(path, content);\n }\n }\n }\n\n return {\n documentXml,\n headers,\n footers,\n documentRels,\n contentTypes,\n stylesXml,\n settingsXml,\n numberingXml,\n footnotesXml,\n endnotesXml,\n commentsXml,\n coreXml,\n appXml,\n rootRels,\n zip,\n };\n}\n\n/** Read a single zip entry as a string. Returns undefined if not found. */\nasync function readEntry(\n zip: JSZip,\n path: string,\n): Promise<string | undefined> {\n const file = zip.file(path);\n if (!file) return undefined;\n return file.async(\"string\");\n}\n","/**\n * ZIP writer: takes modified XML strings and writes them back into\n * a DOCX buffer using the original JSZip instance.\n *\n * Unmodified entries (images, styles, fonts, etc.) pass through untouched\n * because JSZip lazily decompresses — only entries we replace are reprocessed.\n */\n\nimport type JSZip from \"jszip\";\n\nexport interface WriteDocxOptions {\n /** The original JSZip instance from reading. */\n zip: JSZip;\n /** The modified document.xml content. */\n documentXml: string;\n /** Modified header XML files keyed by zip path. */\n headers?: Map<string, string>;\n /** Modified footer XML files keyed by zip path. */\n footers?: Map<string, string>;\n /** Modified document.xml.rels content. */\n documentRelsXml?: string;\n /** Modified [Content_Types].xml content. */\n contentTypesXml?: string;\n /** Modified word/settings.xml content. */\n settingsXml?: string;\n /** Modified word/numbering.xml content. */\n numberingXml?: string;\n /** Modified word/footnotes.xml content. */\n footnotesXml?: string;\n /** Modified word/endnotes.xml content. */\n endnotesXml?: string;\n /** Modified word/comments.xml content. */\n commentsXml?: string;\n /** Modified docProps/core.xml content. */\n coreXml?: string;\n /** Modified docProps/app.xml content. */\n appXml?: string;\n /** Modified _rels/.rels content. */\n rootRelsXml?: string;\n /** Zip paths to remove from the archive (e.g., deleted header/footer parts). */\n removedParts?: string[];\n}\n\n/** Fixed zip path for each optional XML part. */\nconst PART_PATHS: Array<[keyof WriteDocxOptions, string]> = [\n [\"documentRelsXml\", \"word/_rels/document.xml.rels\"],\n [\"contentTypesXml\", \"[Content_Types].xml\"],\n [\"settingsXml\", \"word/settings.xml\"],\n [\"numberingXml\", \"word/numbering.xml\"],\n [\"footnotesXml\", \"word/footnotes.xml\"],\n [\"endnotesXml\", \"word/endnotes.xml\"],\n [\"commentsXml\", \"word/comments.xml\"],\n [\"coreXml\", \"docProps/core.xml\"],\n [\"appXml\", \"docProps/app.xml\"],\n [\"rootRelsXml\", \"_rels/.rels\"],\n];\n\n/**\n * Write modified XML back into a DOCX buffer.\n *\n * Only the entries we explicitly set are recompressed.\n * Everything else (images, styles, fonts, etc.) passes through from the\n * original zip with minimal overhead.\n */\nexport async function writeDocx(\n options: WriteDocxOptions,\n): Promise<Buffer> {\n const { zip, documentXml, headers, footers, removedParts } = options;\n\n // Remove deleted parts (e.g., header/footer XML files after remove_header/remove_footer)\n if (removedParts) {\n for (const path of removedParts) {\n zip.remove(path);\n }\n }\n\n // Replace document.xml\n zip.file(\"word/document.xml\", documentXml);\n\n // Replace modified headers\n if (headers) {\n for (const [path, content] of headers) {\n zip.file(path, content);\n }\n }\n\n // Replace modified footers\n if (footers) {\n for (const [path, content] of footers) {\n zip.file(path, content);\n }\n }\n\n // Replace every other part that was supplied\n for (const [key, path] of PART_PATHS) {\n const content = options[key];\n if (typeof content === \"string\") {\n zip.file(path, content);\n }\n }\n\n // Generate the output buffer\n const buffer = await zip.generateAsync({\n type: \"nodebuffer\",\n compression: \"DEFLATE\",\n compressionOptions: { level: 6 },\n });\n\n return buffer;\n}\n","/**\n * XML serializer: converts a FXP ordered array back to an XML string.\n *\n * Uses the same options as the parser to ensure lossless roundtripping.\n * The FXP object passed here may have been mutated by the patch engine —\n * that's the whole point. We serialize the mutated state.\n */\n\nimport { XMLBuilder } from \"fast-xml-parser\";\nimport type { FXPNode } from \"../types.js\";\n\n/** Builder options must match parser options for correct roundtripping. */\nconst BUILDER_OPTIONS = {\n preserveOrder: true,\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n commentPropName: \"#comment\",\n cdataPropName: \"#cdata\",\n processEntities: false,\n // Produce well-formed XML\n format: false,\n suppressEmptyNode: false,\n suppressBooleanAttributes: false,\n};\n\nconst builder = new XMLBuilder(BUILDER_OPTIONS);\n\n/**\n * Serialize a FXP ordered array back to an XML string.\n */\nexport function serializeXml(nodes: FXPNode[]): string {\n return builder.build(nodes) as string;\n}\n","/**\n * XML parser: wraps fast-xml-parser with the correct options for\n * lossless OOXML roundtripping.\n *\n * Uses `preserveOrder: true` which produces an ordered array structure.\n * This is critical for DOCX because sibling element order matters\n * (e.g., w:pPr must come before w:r inside w:p).\n */\n\nimport { XMLParser } from \"fast-xml-parser\";\nimport type { FXPNode } from \"../types.js\";\n\n/** Parser options tuned for lossless OOXML roundtripping. */\nconst PARSER_OPTIONS = {\n preserveOrder: true,\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n // Preserve everything\n commentPropName: \"#comment\",\n cdataPropName: \"#cdata\",\n // Don't mangle values\n processEntities: false,\n trimValues: false,\n parseTagValue: false,\n parseAttributeValue: false,\n // Keep declarations\n ignoreDeclaration: false,\n ignorePiTags: false,\n};\n\nconst parser = new XMLParser(PARSER_OPTIONS);\n\n/**\n * Parse an XML string into a FXP ordered array.\n *\n * The returned structure is an array of FXPNode objects where\n * element order is preserved. This IS the mutable XML representation —\n * modifying these objects and serializing them back produces the output XML.\n */\nexport function parseXml(xml: string): FXPNode[] {\n return parser.parse(xml) as FXPNode[];\n}\n","/**\n * Utility functions for navigating fast-xml-parser's preserveOrder structure.\n *\n * With `preserveOrder: true`, FXP produces arrays of single-key objects:\n *\n * [{ \"w:p\": [...children...], \":@\": { \"@_w:rsidR\": \"00A7\" } }]\n *\n * These helpers abstract over that structure so the rest of the codebase\n * doesn't need to deal with the verbosity directly.\n */\n\nimport type { FXPNode } from \"../types.js\";\n\n// =============================================================================\n// Tag name extraction\n// =============================================================================\n\n/**\n * Get the tag name of an FXP node.\n * The tag name is the first key that isn't \":@\" (attributes) or \"#text\".\n */\nexport function getTagName(node: FXPNode): string | undefined {\n for (const key of Object.keys(node)) {\n if (key !== \":@\" && key !== \"#text\") {\n return key;\n }\n }\n return undefined;\n}\n\n/**\n * Check if a node has a specific tag name.\n */\nexport function isTag(node: FXPNode, tagName: string): boolean {\n return tagName in node;\n}\n\n/**\n * Check if a node is a text node.\n */\nexport function isTextNode(node: FXPNode): boolean {\n return \"#text\" in node;\n}\n\n// =============================================================================\n// Child navigation\n// =============================================================================\n\n/**\n * Get the children array of a node (the value under the tag name key).\n */\nexport function getChildren(node: FXPNode): FXPNode[] {\n const tagName = getTagName(node);\n if (!tagName) return [];\n const children = node[tagName];\n if (Array.isArray(children)) return children;\n return [];\n}\n\n/**\n * Find the first child with a specific tag name.\n */\nexport function findChild(\n node: FXPNode,\n tagName: string,\n): FXPNode | undefined {\n const children = getChildren(node);\n return children.find((child) => tagName in child);\n}\n\n/**\n * Find all children with a specific tag name.\n */\nexport function findChildren(\n node: FXPNode,\n tagName: string,\n): FXPNode[] {\n const children = getChildren(node);\n return children.filter((child) => tagName in child);\n}\n\n/**\n * Find the first descendant matching a path of tag names.\n * E.g., findByPath(node, [\"w:body\", \"w:p\"]) finds the first w:p inside w:body.\n */\nexport function findByPath(\n node: FXPNode,\n path: string[],\n): FXPNode | undefined {\n let current: FXPNode | undefined = node;\n for (const tag of path) {\n if (!current) return undefined;\n current = findChild(current, tag);\n }\n return current;\n}\n\n// =============================================================================\n// Attribute access\n// =============================================================================\n\n/**\n * Get an attribute value from a node.\n * Attribute names should NOT include the \"@_\" prefix — it's added automatically.\n */\nexport function getAttr(\n node: FXPNode,\n attrName: string,\n): string | undefined {\n const attrs = node[\":@\"] as Record<string, string> | undefined;\n if (!attrs) return undefined;\n return attrs[`@_${attrName}`];\n}\n\n/**\n * Set an attribute value on a node.\n */\nexport function setAttr(\n node: FXPNode,\n attrName: string,\n value: string,\n): void {\n if (!node[\":@\"]) {\n node[\":@\"] = {};\n }\n (node[\":@\"] as Record<string, string>)[`@_${attrName}`] = value;\n}\n\n/**\n * Remove an attribute from a node.\n */\nexport function removeAttr(node: FXPNode, attrName: string): void {\n const attrs = node[\":@\"] as Record<string, string> | undefined;\n if (attrs) {\n delete attrs[`@_${attrName}`];\n }\n}\n\n/**\n * Get all attributes as a plain object (without the \"@_\" prefix).\n */\nexport function getAttrs(\n node: FXPNode,\n): Record<string, string> {\n const attrs = node[\":@\"] as Record<string, string> | undefined;\n if (!attrs) return {};\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(attrs)) {\n if (key.startsWith(\"@_\")) {\n result[key.slice(2)] = value;\n }\n }\n return result;\n}\n\n// =============================================================================\n// Text access\n// =============================================================================\n\n/**\n * Escape special XML characters in text content.\n *\n * Required because fast-xml-parser with processEntities: false does NOT\n * auto-escape on build. Entities from the original XML survive as-is,\n * but any NEW text we inject must be manually escaped.\n */\nexport function escapeXmlText(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\n/**\n * Unescape XML entities in text content for display/comparison.\n *\n * Since processEntities: false keeps entities as-is during parse,\n * we unescape when extracting text for the normalized view so that\n * the paragraph text reads naturally (e.g., \"A & B\" not \"A &amp; B\").\n */\nexport function unescapeXmlText(text: string): string {\n return text\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\");\n}\n\n/**\n * Get the text content of a text node.\n */\nexport function getTextContent(node: FXPNode): string {\n if (\"#text\" in node) {\n return String(node[\"#text\"]);\n }\n return \"\";\n}\n\n/**\n * Set the text content of a text node.\n * Escapes XML special characters automatically.\n */\nexport function setTextContent(node: FXPNode, text: string): void {\n node[\"#text\"] = escapeXmlText(text);\n}\n\n/**\n * Get all text from w:t elements inside a w:r run node.\n * Unescapes XML entities so the returned text is natural.\n * Converts w:br (line break) elements to \\n characters.\n * Structural breaks — page breaks (w:br w:type=\"page\") and column\n * breaks (w:br w:type=\"column\") — are skipped (not included in text).\n */\nexport function getRunText(runNode: FXPNode): string {\n const children = getChildren(runNode);\n let text = \"\";\n for (const child of children) {\n if (\"w:t\" in child) {\n const tChildren = child[\"w:t\"];\n if (Array.isArray(tChildren)) {\n for (const tChild of tChildren) {\n if (\"#text\" in tChild) {\n text += unescapeXmlText(String(tChild[\"#text\"]));\n }\n }\n }\n } else if (\"w:br\" in child) {\n // Page breaks (w:type=\"page\") and column breaks (w:type=\"column\")\n // are structural breaks, not text — skip them\n const brType = getAttr(child, \"w:type\");\n if (!brType || brType === \"textWrapping\") {\n // Regular line break\n text += \"\\n\";\n }\n // page/column breaks produce no text\n }\n }\n return text;\n}\n\n/**\n * Find the w:t node inside a w:r run node.\n * Returns the first w:t element found.\n */\nexport function findTextNode(runNode: FXPNode): FXPNode | undefined {\n const children = getChildren(runNode);\n return children.find((child) => \"w:t\" in child);\n}\n\n/**\n * Check if a run contains a page break.\n *\n * Detects two OOXML mechanisms:\n * - `<w:br w:type=\"page\"/>` — explicit page break inserted by the user\n * - `<w:lastRenderedPageBreak/>` — rendering hint marking where Word\n * paginated the document on last save\n */\nexport function runHasPageBreak(runNode: FXPNode): boolean {\n const children = getChildren(runNode);\n for (const child of children) {\n if (\"w:br\" in child) {\n const brType = getAttr(child, \"w:type\");\n if (brType === \"page\") return true;\n }\n if (\"w:lastRenderedPageBreak\" in child) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Find the character offset within a run's text where a page break occurs.\n *\n * Walks the run's children in document order, accumulating text from w:t\n * elements. When a page break element is found (w:br w:type=\"page\" or\n * w:lastRenderedPageBreak), returns the number of text characters seen so\n * far — i.e., the break position relative to the run's own text.\n *\n * Returns -1 if no page break is found in this run.\n */\nexport function getRunPageBreakOffset(runNode: FXPNode): number {\n const children = getChildren(runNode);\n let charsSeen = 0;\n\n for (const child of children) {\n if (\"w:t\" in child) {\n const tChildren = child[\"w:t\"];\n if (Array.isArray(tChildren)) {\n for (const tChild of tChildren) {\n if (\"#text\" in tChild) {\n charsSeen += unescapeXmlText(String(tChild[\"#text\"])).length;\n }\n }\n }\n } else if (\"w:br\" in child) {\n const brType = getAttr(child, \"w:type\");\n if (!brType || brType === \"textWrapping\") {\n // Regular line break counts as one char (\\n)\n charsSeen += 1;\n } else if (brType === \"page\") {\n return charsSeen;\n }\n } else if (\"w:lastRenderedPageBreak\" in child) {\n return charsSeen;\n }\n }\n\n return -1;\n}\n\n/**\n * Check if a paragraph contains a page break in any of its runs,\n * or has the `w:pageBreakBefore` property in its `w:pPr`.\n *\n * Covers three OOXML mechanisms:\n * 1. `<w:br w:type=\"page\"/>` in a run — explicit page break\n * 2. `<w:lastRenderedPageBreak/>` in a run — Word rendering hint\n * 3. `<w:pageBreakBefore/>` in `w:pPr` — paragraph-level \"page break before\"\n */\nexport function paragraphHasPageBreak(paragraphNode: FXPNode): boolean {\n // Check paragraph property: w:pPr > w:pageBreakBefore\n const pPr = findChild(paragraphNode, \"w:pPr\");\n if (pPr) {\n const pbb = findChild(pPr, \"w:pageBreakBefore\");\n if (pbb) {\n // <w:pageBreakBefore/> means on; <w:pageBreakBefore w:val=\"0\"/> means off\n const val = getAttr(pbb, \"w:val\");\n if (val !== \"0\" && val !== \"false\") {\n return true;\n }\n }\n }\n\n // Check runs for w:br type=\"page\" or w:lastRenderedPageBreak\n return _scanForPageBreak(getChildren(paragraphNode));\n}\n\nfunction _scanForPageBreak(children: FXPNode[]): boolean {\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n if (runHasPageBreak(child)) return true;\n } else if (tag === \"w:hyperlink\" || tag === \"w:ins\") {\n if (_scanForPageBreak(getChildren(child))) return true;\n }\n }\n return false;\n}\n\n// =============================================================================\n// Node manipulation\n// =============================================================================\n\n/**\n * Deep clone an FXP node.\n * Creates a completely independent copy with no shared references.\n */\nexport function cloneNode(node: FXPNode): FXPNode {\n return JSON.parse(JSON.stringify(node)) as FXPNode;\n}\n\n/**\n * Insert a child node after a reference child in a parent's children array.\n * Returns the index where the new child was inserted.\n */\nexport function insertAfter(\n parent: FXPNode,\n refChild: FXPNode,\n newChild: FXPNode,\n): number {\n const children = getChildren(parent);\n const index = children.indexOf(refChild);\n if (index === -1) {\n throw new Error(\"Reference child not found in parent\");\n }\n children.splice(index + 1, 0, newChild);\n return index + 1;\n}\n\n/**\n * Insert a child node before a reference child in a parent's children array.\n * Returns the index where the new child was inserted.\n */\nexport function insertBefore(\n parent: FXPNode,\n refChild: FXPNode,\n newChild: FXPNode,\n): number {\n const children = getChildren(parent);\n const index = children.indexOf(refChild);\n if (index === -1) {\n throw new Error(\"Reference child not found in parent\");\n }\n children.splice(index, 0, newChild);\n return index;\n}\n\n/**\n * Remove a child node from a parent's children array.\n * Returns true if the child was found and removed.\n */\nexport function removeChild(\n parent: FXPNode,\n child: FXPNode,\n): boolean {\n const children = getChildren(parent);\n const index = children.indexOf(child);\n if (index === -1) return false;\n children.splice(index, 1);\n return true;\n}\n\n/**\n * Insert a child node into a parent's children array at a specific index.\n */\nexport function insertChildAt(\n parent: FXPNode,\n index: number,\n newChild: FXPNode,\n): void {\n const children = getChildren(parent);\n children.splice(index, 0, newChild);\n}\n\n/**\n * Create a new FXP element node.\n */\nexport function createElement(\n tagName: string,\n children: FXPNode[] = [],\n attrs?: Record<string, string>,\n): FXPNode {\n const node: FXPNode = { [tagName]: children };\n if (attrs && Object.keys(attrs).length > 0) {\n const fxpAttrs: Record<string, string> = {};\n for (const [key, value] of Object.entries(attrs)) {\n fxpAttrs[`@_${key}`] = value;\n }\n node[\":@\"] = fxpAttrs;\n }\n return node;\n}\n\n/**\n * Create a text node.\n * Escapes XML special characters automatically.\n */\nexport function createTextNode(text: string): FXPNode {\n return { \"#text\": escapeXmlText(text) } as unknown as FXPNode;\n}\n\n/**\n * Create a w:t element with text content, preserving spaces.\n */\nexport function createWt(text: string): FXPNode {\n const node: FXPNode = {\n \"w:t\": [createTextNode(text)],\n };\n // Add xml:space=\"preserve\" if text has leading/trailing whitespace\n if (needsSpacePreserve(text)) {\n setAttr(node, \"xml:space\", \"preserve\");\n }\n return node;\n}\n\n/**\n * Check if text needs xml:space=\"preserve\" on a w:t element.\n * Returns true if text has leading/trailing whitespace or double spaces.\n */\nexport function needsSpacePreserve(text: string): boolean {\n return text.length > 0 && (text !== text.trim() || text.includes(\" \"));\n}\n\n/**\n * Create a w:r run element with text and optional formatting.\n * If rPrNode is provided, it's cloned and used as the run properties.\n *\n * Handles newlines (\\n) by interleaving w:t and w:br elements,\n * matching the behavior of getRunText() which converts w:br → \\n.\n */\nexport function createRun(\n text: string,\n rPrNode?: FXPNode,\n): FXPNode {\n const children: FXPNode[] = [];\n if (rPrNode) {\n children.push(cloneNode(rPrNode));\n }\n\n if (text.includes(\"\\n\")) {\n // Multi-line: interleave w:t and w:br\n const segments = text.split(\"\\n\");\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n if (seg.length > 0 || i === 0) {\n children.push(createWt(seg));\n }\n if (i < segments.length - 1) {\n children.push({ \"w:br\": [] as FXPNode[] });\n }\n }\n } else {\n children.push(createWt(text));\n }\n\n return { \"w:r\": children };\n}\n","/**\n * Paragraph normalizer: walks a w:p FXP node and produces a\n * NormalizedParagraph with text, run mappings, and xmlRefs.\n *\n * This is the core of the mapping layer. Each RunMapping holds:\n * - The run's text content\n * - Character offset range in the concatenated paragraph text\n * - Direct reference to the w:r and w:t FXP nodes\n * - Captured formatting\n *\n * Patches use these mappings to find the right XML nodes to mutate.\n *\n * Hyperlink support: w:hyperlink elements wrap w:r runs. We collect\n * runs from both direct children and from inside w:hyperlink wrappers,\n * so hyperlink text participates in the paragraph's normalized text\n * and can be targeted by patches.\n */\n\nimport type {\n FXPNode,\n NormalizedParagraph,\n RunMapping,\n RunFormatting,\n ListInfo,\n RevisionMark,\n} from \"../types.js\";\nimport {\n findChild,\n getAttr,\n getChildren,\n getTagName,\n getRunText,\n findTextNode,\n isTag,\n unescapeXmlText,\n paragraphHasPageBreak,\n runHasPageBreak,\n getRunPageBreakOffset,\n} from \"../xml/helpers.js\";\n\n/**\n * Collect all w:r run nodes from a paragraph, including those nested\n * inside w:hyperlink and w:ins elements. Skips w:del entirely.\n * Preserves document order.\n *\n * This ensures:\n * - Inserted text (w:ins) is included in .text — subsequent patches see it\n * - Deleted text (w:del) is excluded from .text — subsequent patches don't see it\n */\nfunction collectRuns(paragraphNode: FXPNode): FXPNode[] {\n const children = getChildren(paragraphNode);\n const runs: FXPNode[] = [];\n\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n runs.push(child);\n } else if (tag === \"w:hyperlink\") {\n // Collect runs inside the hyperlink (recurse for nested w:ins within hyperlink)\n collectRunsFromContainer(child, runs);\n } else if (tag === \"w:ins\") {\n // Inserted runs are \"current content\" — recurse into them\n collectRunsFromContainer(child, runs);\n }\n // w:del is intentionally skipped — deleted text is excluded from normalized text\n }\n\n return runs;\n}\n\n/**\n * Recursively collect runs from a container element (w:hyperlink, w:ins).\n * Handles nested containers (e.g., w:ins inside w:hyperlink or vice versa).\n */\nfunction collectRunsFromContainer(containerNode: FXPNode, runs: FXPNode[]): void {\n const children = getChildren(containerNode);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n runs.push(child);\n } else if (tag === \"w:hyperlink\" || tag === \"w:ins\") {\n collectRunsFromContainer(child, runs);\n }\n // w:del inside containers is also skipped\n }\n}\n\n/**\n * Normalize a single w:p FXP node into a NormalizedParagraph.\n *\n * @param paragraphNode - The w:p FXP node (must have \"w:p\" key)\n * @param id - The stable ID to assign (e.g., \"p0\", \"tbl0.r1.c0.p0\")\n */\nexport function normalizeParagraph(\n paragraphNode: FXPNode,\n id: string,\n): NormalizedParagraph {\n const runs = collectRuns(paragraphNode);\n const runMappings: RunMapping[] = [];\n let offset = 0;\n let pageBreakOffset: number | undefined;\n\n for (const runNode of runs) {\n const text = getRunText(runNode);\n\n // Check for page break in this run before skipping empty runs.\n // A page-break-only run has text.length === 0 but still contains a break.\n if (pageBreakOffset === undefined && runHasPageBreak(runNode)) {\n const withinRun = getRunPageBreakOffset(runNode);\n if (withinRun >= 0) {\n pageBreakOffset = offset + withinRun;\n }\n }\n\n // Skip runs that contribute nothing to the text (empty w:t, page-break-only,\n // field chars, drawings). A run holding only a line break still yields \"\\n\",\n // so every w:br is one character in .text — the same text the projection\n // shows, so offsets and occurrences computed from it resolve correctly.\n if (text.length === 0) continue;\n\n const textNode = findTextNode(runNode);\n const formatting = extractFormatting(runNode);\n\n const mapping: RunMapping = {\n text,\n startOffset: offset,\n endOffset: offset + text.length,\n xmlRef: runNode,\n formatting,\n };\n if (textNode) mapping.textXmlRef = textNode;\n runMappings.push(mapping);\n\n offset += text.length;\n }\n\n const fullText = runMappings.map((r) => r.text).join(\"\");\n\n // Capture revision marks from w:ins / w:del elements\n const revisions = collectRevisionMarks(paragraphNode, runMappings);\n\n const result: NormalizedParagraph = {\n kind: \"paragraph\",\n id,\n text: fullText,\n runs: runMappings,\n xmlRef: paragraphNode,\n style: extractStyle(paragraphNode),\n listInfo: extractListInfo(paragraphNode),\n };\n\n const paraId = getAttr(paragraphNode, \"w14:paraId\");\n if (paraId) {\n result.paraId = paraId.toUpperCase();\n }\n\n if (revisions.length > 0) {\n result.revisions = revisions;\n }\n\n // Detect page breaks — check w:pageBreakBefore (always offset 0) and run-level breaks\n if (paragraphHasPageBreak(paragraphNode)) {\n result.hasPageBreak = true;\n\n // w:pageBreakBefore is a paragraph-level property — always offset 0\n const pPr = findChild(paragraphNode, \"w:pPr\");\n if (pPr) {\n const pbb = findChild(pPr, \"w:pageBreakBefore\");\n if (pbb) {\n const val = getAttr(pbb, \"w:val\");\n if (val !== \"0\" && val !== \"false\") {\n result.pageBreakOffset = 0;\n return result;\n }\n }\n }\n\n // Run-level break — use the offset computed during the run loop\n if (pageBreakOffset !== undefined) {\n result.pageBreakOffset = pageBreakOffset;\n }\n }\n\n return result;\n}\n\n/**\n * Extract formatting properties from a w:r run node's w:rPr element.\n */\nfunction extractFormatting(runNode: FXPNode): RunFormatting {\n const rPr = findChild(runNode, \"w:rPr\");\n if (!rPr) return {};\n\n const formatting: RunFormatting = {};\n const children = getChildren(rPr);\n\n for (const child of children) {\n if (isTag(child, \"w:b\")) {\n // <w:b/> means bold on; <w:b w:val=\"0\"/> means bold off\n const val = getAttr(child, \"w:val\");\n formatting.bold = val !== \"0\" && val !== \"false\";\n } else if (isTag(child, \"w:i\")) {\n const val = getAttr(child, \"w:val\");\n formatting.italic = val !== \"0\" && val !== \"false\";\n } else if (isTag(child, \"w:u\")) {\n formatting.underline = getAttr(child, \"w:val\") ?? \"single\";\n } else if (isTag(child, \"w:strike\")) {\n const val = getAttr(child, \"w:val\");\n formatting.strikethrough = val !== \"0\" && val !== \"false\";\n } else if (isTag(child, \"w:sz\")) {\n const val = getAttr(child, \"w:val\");\n if (val) formatting.fontSize = parseInt(val, 10);\n } else if (isTag(child, \"w:rFonts\")) {\n formatting.fontFamily = getAttr(child, \"w:ascii\") ?? getAttr(child, \"w:hAnsi\");\n } else if (isTag(child, \"w:color\")) {\n formatting.color = getAttr(child, \"w:val\");\n const themeColor = getAttr(child, \"w:themeColor\");\n if (themeColor) formatting.themeColor = themeColor;\n } else if (isTag(child, \"w:highlight\")) {\n formatting.highlight = getAttr(child, \"w:val\");\n }\n }\n\n return formatting;\n}\n\n/**\n * Extract the paragraph style name from w:pPr/w:pStyle.\n */\nfunction extractStyle(paragraphNode: FXPNode): string | undefined {\n const pPr = findChild(paragraphNode, \"w:pPr\");\n if (!pPr) return undefined;\n\n const pStyle = findChild(pPr, \"w:pStyle\");\n if (!pStyle) return undefined;\n\n return getAttr(pStyle, \"w:val\");\n}\n\n/**\n * Extract list/numbering info from w:pPr/w:numPr.\n */\nfunction extractListInfo(paragraphNode: FXPNode): ListInfo | undefined {\n const pPr = findChild(paragraphNode, \"w:pPr\");\n if (!pPr) return undefined;\n\n const numPr = findChild(pPr, \"w:numPr\");\n if (!numPr) return undefined;\n\n const numIdNode = findChild(numPr, \"w:numId\");\n const ilvlNode = findChild(numPr, \"w:ilvl\");\n\n const numId = numIdNode ? getAttr(numIdNode, \"w:val\") : undefined;\n const level = ilvlNode ? getAttr(ilvlNode, \"w:val\") : undefined;\n\n if (!numId) return undefined;\n\n // numId \"0\" means \"no numbering\" in OOXML (removes inherited list formatting)\n if (numId === \"0\") return undefined;\n\n return {\n numId,\n level: level ? parseInt(level, 10) : 0,\n };\n}\n\n// =============================================================================\n// Revision mark extraction\n// =============================================================================\n\n/**\n * Collect revision marks (w:ins, w:del) from a paragraph's XML.\n *\n * - For w:ins runs: startOffset/endOffset point into the .text (runs are included in normalized text)\n * - For w:del runs: startOffset === endOffset (zero-width position) since deleted text is excluded\n * from .text; the deleted content is stored in the RevisionMark.text field\n */\nfunction collectRevisionMarks(\n paragraphNode: FXPNode,\n runMappings: RunMapping[],\n): RevisionMark[] {\n const revisions: RevisionMark[] = [];\n\n // Build a set of xmlRefs for quick lookup of which runs are \"ins\" runs\n // We need to track which runs came from w:ins to annotate their offsets\n const insRunRefs = new Set<FXPNode>();\n collectInsRunRefs(paragraphNode, insRunRefs);\n\n // Track revision marks for w:ins runs\n for (const mapping of runMappings) {\n if (insRunRefs.has(mapping.xmlRef)) {\n // Find the parent w:ins element to get author/date\n const insInfo = findParentInsInfo(paragraphNode, mapping.xmlRef);\n revisions.push({\n type: \"insert\",\n text: mapping.text,\n startOffset: mapping.startOffset,\n endOffset: mapping.endOffset,\n author: insInfo?.author ?? \"\",\n date: insInfo?.date ?? \"\",\n ...(insInfo?.id !== undefined ? { id: insInfo.id } : {}),\n });\n }\n }\n\n // Track revision marks for w:del (zero-width in normalized text)\n collectDelRevisionMarks(paragraphNode, runMappings, revisions);\n\n return revisions;\n}\n\n/**\n * Recursively collect xmlRef pointers for runs inside w:ins elements.\n */\nfunction collectInsRunRefs(node: FXPNode, refs: Set<FXPNode>): void {\n const children = getChildren(node);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:ins\") {\n // All runs inside this w:ins are \"inserted\"\n markAllRunsInContainer(child, refs);\n } else if (tag === \"w:hyperlink\") {\n collectInsRunRefs(child, refs);\n }\n // Don't recurse into w:del — those are excluded\n }\n}\n\nfunction markAllRunsInContainer(node: FXPNode, refs: Set<FXPNode>): void {\n const children = getChildren(node);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n refs.add(child);\n } else if (tag === \"w:hyperlink\" || tag === \"w:ins\") {\n markAllRunsInContainer(child, refs);\n }\n }\n}\n\n/**\n * Find the w:ins parent element's author and date for a given run node.\n */\nfunction findParentInsInfo(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n): { author: string; date: string; id?: string } | undefined {\n return findInsInfoInChildren(getChildren(paragraphNode), runNode);\n}\n\nfunction findInsInfoInChildren(\n children: FXPNode[],\n runNode: FXPNode,\n): { author: string; date: string; id?: string } | undefined {\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:ins\") {\n // Check if this w:ins contains the run\n if (containsRun(child, runNode)) {\n return {\n author: getAttr(child, \"w:author\") ?? \"\",\n date: getAttr(child, \"w:date\") ?? \"\",\n id: getAttr(child, \"w:id\"),\n };\n }\n } else if (tag === \"w:hyperlink\") {\n const result = findInsInfoInChildren(getChildren(child), runNode);\n if (result) return result;\n }\n }\n return undefined;\n}\n\nfunction containsRun(node: FXPNode, runNode: FXPNode): boolean {\n const children = getChildren(node);\n for (const child of children) {\n if (child === runNode) return true;\n const tag = getTagName(child);\n if (tag && tag !== \"w:del\") {\n if (containsRun(child, runNode)) return true;\n }\n }\n return false;\n}\n\n/**\n * Collect deletion revision marks from w:del elements.\n *\n * For each w:del, we need to figure out its position in the normalized text.\n * Since deleted runs are excluded from .text, the position is zero-width\n * and falls at the boundary where the deletion occurred.\n *\n * This walker recurses into containers (w:hyperlink, w:ins) to find\n * w:del elements nested inside them — e.g., when a deletion occurs inside\n * a hyperlink or inside a previous insertion.\n */\nfunction collectDelRevisionMarks(\n paragraphNode: FXPNode,\n runMappings: RunMapping[],\n revisions: RevisionMark[],\n): void {\n // Build a constant-time lookup map: xmlRef → RunMapping\n // Avoids O(n²) linear scans when walking many runs\n const runMap = new Map<FXPNode, RunMapping>();\n for (const mapping of runMappings) {\n runMap.set(mapping.xmlRef, mapping);\n }\n\n walkForDeletions(getChildren(paragraphNode), runMap, revisions, 0);\n}\n\n/**\n * Recursively walk children, advancing the text offset through runs,\n * recording w:del revision marks, and recursing into containers.\n *\n * Returns the text offset after processing all children.\n */\nfunction walkForDeletions(\n children: FXPNode[],\n runMap: Map<FXPNode, RunMapping>,\n revisions: RevisionMark[],\n offset: number,\n): number {\n for (const child of children) {\n const tag = getTagName(child);\n\n if (tag === \"w:r\") {\n const mapping = runMap.get(child);\n if (mapping) {\n offset = mapping.endOffset;\n }\n } else if (tag === \"w:del\") {\n // Collect deleted text from this w:del\n const delText = getDeletedText(child);\n if (delText.length > 0) {\n revisions.push({\n type: \"delete\",\n text: delText,\n startOffset: offset,\n endOffset: offset,\n author: getAttr(child, \"w:author\") ?? \"\",\n date: getAttr(child, \"w:date\") ?? \"\",\n ...(getAttr(child, \"w:id\") !== undefined ? { id: getAttr(child, \"w:id\")! } : {}),\n });\n }\n } else if (tag === \"w:hyperlink\" || tag === \"w:ins\") {\n // Recurse into containers — they may contain w:r, w:del, or further nesting\n offset = walkForDeletions(getChildren(child), runMap, revisions, offset);\n }\n }\n return offset;\n}\n\n/**\n * Extract text content from runs inside a w:del element.\n * Reads from both w:delText and w:t elements (some docs use w:t inside w:del);\n * a deleted soft line break (w:br) reads as \"\\n\", as in live text.\n */\nfunction getDeletedText(delNode: FXPNode): string {\n const children = getChildren(delNode);\n let text = \"\";\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n const runChildren = getChildren(child);\n for (const rc of runChildren) {\n if (\"w:br\" in rc) {\n const brType = getAttr(rc, \"w:type\");\n if (!brType || brType === \"textWrapping\") text += \"\\n\";\n } else if (\"w:delText\" in rc) {\n const dtChildren = rc[\"w:delText\"];\n if (Array.isArray(dtChildren)) {\n for (const dtChild of dtChildren as FXPNode[]) {\n if (\"#text\" in dtChild) {\n text += unescapeXmlText(String(dtChild[\"#text\"]));\n }\n }\n }\n } else if (\"w:t\" in rc) {\n const tChildren = rc[\"w:t\"];\n if (Array.isArray(tChildren)) {\n for (const tChild of tChildren as FXPNode[]) {\n if (\"#text\" in tChild) {\n text += unescapeXmlText(String(tChild[\"#text\"]));\n }\n }\n }\n }\n }\n }\n }\n return text;\n}\n","/**\n * Table normalizer: walks a w:tbl FXP node and produces a\n * NormalizedTable with rows, cells, and inner paragraphs.\n *\n * Tables are containers — their text lives in paragraphs inside cells.\n * We normalize the structure and delegate paragraph normalization to\n * the paragraph normalizer.\n *\n * Supports nested tables: if a table cell contains another w:tbl,\n * it is recursively normalized and included in the cell's nestedTables.\n */\n\nimport type {\n FXPNode,\n NormalizedTable,\n NormalizedTableRow,\n NormalizedTableCell,\n} from \"../types.js\";\nimport {\n findChild,\n findChildren,\n getAttr,\n} from \"../xml/helpers.js\";\nimport { normalizeParagraph } from \"./paragraph.js\";\n\n/**\n * Normalize a w:tbl FXP node into a NormalizedTable.\n *\n * @param tableNode - The w:tbl FXP node\n * @param id - The stable ID (e.g., \"tbl0\")\n */\nexport function normalizeTable(\n tableNode: FXPNode,\n id: string,\n): NormalizedTable {\n const rows: NormalizedTableRow[] = [];\n const trNodes = findChildren(tableNode, \"w:tr\");\n\n for (let rowIdx = 0; rowIdx < trNodes.length; rowIdx++) {\n const trNode = trNodes[rowIdx]!;\n const cells: NormalizedTableCell[] = [];\n const tcNodes = findChildren(trNode, \"w:tc\");\n\n for (let colIdx = 0; colIdx < tcNodes.length; colIdx++) {\n const tcNode = tcNodes[colIdx]!;\n const paragraphs = findChildren(tcNode, \"w:p\");\n const normalizedParas = paragraphs.map((pNode, pIdx) =>\n normalizeParagraph(\n pNode,\n `${id}.r${rowIdx}.c${colIdx}.p${pIdx}`,\n ),\n );\n\n // Normalize nested tables within this cell\n const nestedTblNodes = findChildren(tcNode, \"w:tbl\");\n const nestedTables = nestedTblNodes.map((tblNode, tblIdx) =>\n normalizeTable(\n tblNode,\n `${id}.r${rowIdx}.c${colIdx}.tbl${tblIdx}`,\n ),\n );\n\n cells.push({\n rowIndex: rowIdx,\n colIndex: colIdx,\n paragraphs: normalizedParas,\n nestedTables,\n xmlRef: tcNode,\n });\n }\n\n rows.push({\n index: rowIdx,\n cells,\n xmlRef: trNode,\n });\n }\n\n // Extract table style\n const tblPr = findChild(tableNode, \"w:tblPr\");\n const tblStyle = tblPr ? findChild(tblPr, \"w:tblStyle\") : undefined;\n const style = tblStyle ? getAttr(tblStyle, \"w:val\") : undefined;\n\n return {\n kind: \"table\",\n id,\n rows,\n xmlRef: tableNode,\n style,\n };\n}\n","/**\n * Header/footer normalizer: reads header/footer XML parts and\n * normalizes their paragraphs.\n *\n * Headers and footers are separate XML files (word/header1.xml, etc.)\n * with w:hdr or w:ftr as the root element. Inside, the structure is\n * the same as document.xml: paragraphs and tables.\n */\n\nimport type {\n FXPNode,\n HeaderFooter,\n NormalizedParagraph,\n NormalizedTable,\n} from \"../types.js\";\nimport { getChildren, getTagName } from \"../xml/helpers.js\";\nimport { normalizeParagraph } from \"./paragraph.js\";\nimport { normalizeTable } from \"./table.js\";\n\n/**\n * Normalize a header or footer FXP structure.\n *\n * @param fxpNodes - The parsed FXP nodes from the header/footer XML\n * @param type - \"header\" or \"footer\"\n * @param variant - \"default\", \"first\", or \"even\"\n * @param rId - The relationship ID\n */\nexport function normalizeHeaderFooter(\n fxpNodes: FXPNode[],\n type: \"header\" | \"footer\",\n variant: string,\n rId: string,\n): HeaderFooter {\n const prefix = type === \"header\" ? \"hdr\" : \"ftr\";\n\n // Find the root element (w:hdr or w:ftr)\n const rootTag = type === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const rootNode = fxpNodes.find((node) => getTagName(node) === rootTag);\n\n const paragraphs: NormalizedParagraph[] = [];\n const tables: NormalizedTable[] = [];\n\n if (rootNode) {\n const children = getChildren(rootNode);\n let paraIdx = 0;\n let tableIdx = 0;\n\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:p\") {\n paragraphs.push(\n normalizeParagraph(\n child,\n `${prefix}.${variant}.p${paraIdx}`,\n ),\n );\n paraIdx++;\n } else if (tag === \"w:tbl\") {\n tables.push(\n normalizeTable(\n child,\n `${prefix}.${variant}.tbl${tableIdx}`,\n ),\n );\n tableIdx++;\n }\n }\n }\n\n return {\n type,\n variant,\n rId,\n paragraphs,\n tables,\n xmlRef: fxpNodes,\n };\n}\n","/**\n * Numbering definition management for list support.\n *\n * Manages word/numbering.xml — creating numbering definitions for\n * bullet, ordered, and multilevel lists, and resolving list types\n * from existing definitions.\n */\n\nimport type { FXPNode, NormalizedDocument } from \"../types.js\";\nimport {\n createElement,\n findChild,\n findChildren,\n getChildren,\n getAttr,\n getTagName,\n} from \"../xml/helpers.js\";\n\n// =============================================================================\n// Ensure numbering part exists\n// =============================================================================\n\n/**\n * Ensure numbering.xml part exists in the document. Creates it if missing.\n * Also ensures content types and relationships are set up.\n * Returns the numbering FXP array.\n */\nexport function ensureNumberingPart(doc: NormalizedDocument): FXPNode[] {\n if (doc.xmlParts.numbering) {\n return doc.xmlParts.numbering;\n }\n\n // Create a minimal numbering.xml structure\n const numberingRoot = createElement(\n \"w:numbering\",\n [],\n {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n \"xmlns:r\": \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n },\n );\n\n doc.xmlParts.numbering = [numberingRoot];\n\n // Add content type override\n if (doc.xmlParts.contentTypes) {\n const typesNode = doc.xmlParts.contentTypes.find(\n (n) => getTagName(n) === \"Types\",\n );\n if (typesNode) {\n const children = getChildren(typesNode);\n // Check if override already exists\n const exists = children.some(\n (c) =>\n getTagName(c) === \"Override\" &&\n getAttr(c, \"PartName\") === \"/word/numbering.xml\",\n );\n if (!exists) {\n children.push(\n createElement(\"Override\", [], {\n PartName: \"/word/numbering.xml\",\n ContentType:\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml\",\n }),\n );\n }\n }\n }\n\n // Add relationship\n if (doc.xmlParts.documentRels) {\n const relsNode = doc.xmlParts.documentRels.find(\n (n) => getTagName(n) === \"Relationships\",\n );\n if (relsNode) {\n const rels = getChildren(relsNode);\n // Check if relationship already exists\n const exists = rels.some(\n (r) =>\n getAttr(r, \"Type\") ===\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering\",\n );\n if (!exists) {\n // Find next rId\n let maxId = 0;\n for (const r of rels) {\n const id = getAttr(r, \"Id\");\n if (id) {\n const num = parseInt(id.replace(\"rId\", \"\"), 10);\n if (!isNaN(num) && num > maxId) maxId = num;\n }\n }\n rels.push(\n createElement(\"Relationship\", [], {\n Id: `rId${maxId + 1}`,\n Type: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering\",\n Target: \"numbering.xml\",\n }),\n );\n }\n }\n }\n\n return doc.xmlParts.numbering;\n}\n\n// =============================================================================\n// ID generation\n// =============================================================================\n\n/** Scan existing w:num entries and return the next available numId. */\nexport function getNextNumId(numberingFxp: FXPNode[]): number {\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\");\n if (!root) return 1;\n\n let max = 0;\n for (const child of findChildren(root, \"w:num\")) {\n const id = getAttr(child, \"w:numId\");\n if (id) {\n const num = parseInt(id, 10);\n if (!isNaN(num) && num > max) max = num;\n }\n }\n return max + 1;\n}\n\n/** Scan existing w:abstractNum entries and return the next available abstractNumId. */\nexport function getNextAbstractNumId(numberingFxp: FXPNode[]): number {\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\");\n if (!root) return 0;\n\n let max = -1;\n for (const child of findChildren(root, \"w:abstractNum\")) {\n const id = getAttr(child, \"w:abstractNumId\");\n if (id) {\n const num = parseInt(id, 10);\n if (!isNaN(num) && num > max) max = num;\n }\n }\n return max + 1;\n}\n\n// =============================================================================\n// Create numbering definitions\n// =============================================================================\n\n/** Bullet symbols per level (cycling) with their required fonts. */\nconst BULLET_LEVELS: Array<{ char: string; font: string }> = [\n { char: \"\\uF0B7\", font: \"Symbol\" }, // Level 0: filled bullet (Symbol)\n { char: \"o\", font: \"Courier New\" }, // Level 1: open circle\n { char: \"\\uF0A7\", font: \"Wingdings\" }, // Level 2: filled square (Wingdings)\n { char: \"\\uF0B7\", font: \"Symbol\" },\n { char: \"o\", font: \"Courier New\" },\n { char: \"\\uF0A7\", font: \"Wingdings\" },\n { char: \"\\uF0B7\", font: \"Symbol\" },\n { char: \"o\", font: \"Courier New\" },\n { char: \"\\uF0A7\", font: \"Wingdings\" },\n];\n\n/** Ordered formats per level (cycling). */\nconst ORDERED_FORMATS: Array<{ fmt: string; textFn: (lvl: number) => string }> = [\n { fmt: \"decimal\", textFn: (l) => `%${l + 1}.` },\n { fmt: \"lowerLetter\", textFn: (l) => `%${l + 1}.` },\n { fmt: \"lowerRoman\", textFn: (l) => `%${l + 1}.` },\n];\n\n/**\n * Create a full numbering definition (w:abstractNum + w:num) for a list type.\n * Returns the numId string to use in w:numPr.\n */\nexport function createNumberingDefinition(\n doc: NormalizedDocument,\n listType: \"bullet\" | \"ordered\" | \"multilevel\",\n levels: number = 9,\n): string {\n const numberingFxp = ensureNumberingPart(doc);\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\")!;\n const rootChildren = getChildren(root);\n\n const abstractNumId = getNextAbstractNumId(numberingFxp);\n const numId = getNextNumId(numberingFxp);\n\n // Build level definitions\n const lvlNodes: FXPNode[] = [];\n for (let i = 0; i < levels; i++) {\n let fmt: string;\n let lvlText: string;\n\n if (listType === \"bullet\") {\n fmt = \"bullet\";\n const bulletInfo = BULLET_LEVELS[i % BULLET_LEVELS.length]!;\n lvlText = bulletInfo.char;\n\n // Add w:rPr with w:rFonts so the bullet glyph renders correctly\n const rPr = createElement(\"w:rPr\", [\n createElement(\"w:rFonts\", [], {\n \"w:ascii\": bulletInfo.font,\n \"w:hAnsi\": bulletInfo.font,\n \"w:hint\": \"default\",\n }),\n ]);\n\n lvlNodes.push(\n createElement(\"w:lvl\", [\n createElement(\"w:start\", [], { \"w:val\": \"1\" }),\n createElement(\"w:numFmt\", [], { \"w:val\": fmt }),\n createElement(\"w:lvlText\", [], { \"w:val\": lvlText }),\n createElement(\"w:lvlJc\", [], { \"w:val\": \"left\" }),\n rPr,\n ], { \"w:ilvl\": String(i) }),\n );\n continue;\n } else if (listType === \"multilevel\") {\n fmt = \"decimal\";\n // Compound: \"%1.%2.%3.\" etc.\n lvlText = Array.from({ length: i + 1 }, (_, k) => `%${k + 1}`).join(\".\") + \".\";\n } else {\n // ordered — independent counters\n const style = ORDERED_FORMATS[i % ORDERED_FORMATS.length]!;\n fmt = style.fmt;\n lvlText = style.textFn(i);\n }\n\n lvlNodes.push(\n createElement(\"w:lvl\", [\n createElement(\"w:start\", [], { \"w:val\": \"1\" }),\n createElement(\"w:numFmt\", [], { \"w:val\": fmt }),\n createElement(\"w:lvlText\", [], { \"w:val\": lvlText }),\n createElement(\"w:lvlJc\", [], { \"w:val\": \"left\" }),\n ], { \"w:ilvl\": String(i) }),\n );\n }\n\n const abstractNum = createElement(\n \"w:abstractNum\",\n lvlNodes,\n { \"w:abstractNumId\": String(abstractNumId) },\n );\n\n const num = createElement(\n \"w:num\",\n [createElement(\"w:abstractNumId\", [], { \"w:val\": String(abstractNumId) })],\n { \"w:numId\": String(numId) },\n );\n\n // Insert abstractNum before any w:num elements (OOXML ordering requirement)\n const firstNumIdx = rootChildren.findIndex((c) => getTagName(c) === \"w:num\");\n if (firstNumIdx >= 0) {\n rootChildren.splice(firstNumIdx, 0, abstractNum);\n } else {\n rootChildren.push(abstractNum);\n }\n // Now append the w:num\n rootChildren.push(num);\n\n return String(numId);\n}\n\n// =============================================================================\n// List type resolution\n// =============================================================================\n\n/**\n * Resolve list type from numId by inspecting numbering.xml definitions.\n */\nexport function resolveListType(\n numberingFxp: FXPNode[],\n numId: string,\n): \"bullet\" | \"ordered\" | \"multilevel\" | undefined {\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\");\n if (!root) return undefined;\n\n // Find the w:num with this numId\n const numNode = findChildren(root, \"w:num\").find(\n (n) => getAttr(n, \"w:numId\") === numId,\n );\n if (!numNode) return undefined;\n\n // Get the abstractNumId\n const abstractNumIdNode = findChild(numNode, \"w:abstractNumId\");\n if (!abstractNumIdNode) return undefined;\n const abstractNumId = getAttr(abstractNumIdNode, \"w:val\");\n if (!abstractNumId) return undefined;\n\n // Find the w:abstractNum\n const abstractNum = findChildren(root, \"w:abstractNum\").find(\n (n) => getAttr(n, \"w:abstractNumId\") === abstractNumId,\n );\n if (!abstractNum) return undefined;\n\n const lvls = findChildren(abstractNum, \"w:lvl\");\n\n // Check for bullet\n let hasBullet = false;\n let hasMultiPlaceholder = false;\n\n for (const lvl of lvls) {\n const numFmt = findChild(lvl, \"w:numFmt\");\n if (numFmt && getAttr(numFmt, \"w:val\") === \"bullet\") {\n hasBullet = true;\n }\n\n const lvlText = findChild(lvl, \"w:lvlText\");\n if (lvlText) {\n const pattern = getAttr(lvlText, \"w:val\") ?? \"\";\n // Count %N placeholders\n const matches = pattern.match(/%\\d+/g);\n if (matches && matches.length > 1) {\n hasMultiPlaceholder = true;\n }\n }\n }\n\n if (hasBullet) return \"bullet\";\n if (hasMultiPlaceholder) return \"multilevel\";\n return \"ordered\";\n}\n\n// =============================================================================\n// Level info for projection\n// =============================================================================\n\n/**\n * Get the w:lvlText pattern and w:numFmt for a given numId + level.\n */\nexport function getLevelInfo(\n numberingFxp: FXPNode[],\n numId: string,\n level: number,\n): {\n pattern: string;\n format: string;\n ancestorFormats: Map<number, string>;\n} | null {\n const root = numberingFxp.find((n) => getTagName(n) === \"w:numbering\");\n if (!root) return null;\n\n // Find w:num → abstractNumId\n const numNode = findChildren(root, \"w:num\").find(\n (n) => getAttr(n, \"w:numId\") === numId,\n );\n if (!numNode) return null;\n\n const abstractNumIdNode = findChild(numNode, \"w:abstractNumId\");\n if (!abstractNumIdNode) return null;\n const abstractNumId = getAttr(abstractNumIdNode, \"w:val\");\n if (!abstractNumId) return null;\n\n const abstractNum = findChildren(root, \"w:abstractNum\").find(\n (n) => getAttr(n, \"w:abstractNumId\") === abstractNumId,\n );\n if (!abstractNum) return null;\n\n const lvls = findChildren(abstractNum, \"w:lvl\");\n\n // Find the target level\n const targetLvl = lvls.find(\n (l) => getAttr(l, \"w:ilvl\") === String(level),\n );\n if (!targetLvl) return null;\n\n const fmtNode = findChild(targetLvl, \"w:numFmt\");\n const textNode = findChild(targetLvl, \"w:lvlText\");\n\n const format = fmtNode ? (getAttr(fmtNode, \"w:val\") ?? \"decimal\") : \"decimal\";\n const pattern = textNode ? (getAttr(textNode, \"w:val\") ?? \"\") : \"\";\n\n // Build ancestor formats map\n const ancestorFormats = new Map<number, string>();\n const placeholders = pattern.match(/%(\\d+)/g);\n if (placeholders) {\n for (const ph of placeholders) {\n const lvlIdx = parseInt(ph.slice(1), 10) - 1; // %1 → level 0\n const ancestorLvl = lvls.find(\n (l) => getAttr(l, \"w:ilvl\") === String(lvlIdx),\n );\n if (ancestorLvl) {\n const ancestorFmt = findChild(ancestorLvl, \"w:numFmt\");\n ancestorFormats.set(\n lvlIdx,\n ancestorFmt ? (getAttr(ancestorFmt, \"w:val\") ?? \"decimal\") : \"decimal\",\n );\n }\n }\n }\n\n return { pattern, format, ancestorFormats };\n}\n","/**\n * Native paragraph identity: `w14:paraId`.\n *\n * Word 2010+ stamps every `w:p` with a `w14:paraId` attribute — 8 uppercase\n * hex characters in the range (0, 0x80000000) — that survives round trips\n * through Word. Positional ids (`p12`) shift whenever a paragraph is inserted\n * or deleted; paraIds do not. This module:\n *\n * - generates fresh, document-unique paraIds\n * - stamps missing ids on load (`assignParaIds: true`)\n * - stamps new paragraphs created by patches, provided the part they live in\n * declares the `w14` namespace (otherwise Word would reject the attribute)\n */\n\nimport type { FXPNode, NormalizedDocument, DocxXmlParts } from \"../types.js\";\nimport { getAttr, setAttr, getChildren, getTagName } from \"../xml/helpers.js\";\n\nexport const W14_NAMESPACE = \"http://schemas.microsoft.com/office/word/2010/wordml\";\nconst MC_NAMESPACE = \"http://schemas.openxmlformats.org/markup-compatibility/2006\";\n\n/** Root element tags whose paragraphs may carry paraIds. */\nconst PART_ROOT_TAGS = new Set([\"w:document\", \"w:hdr\", \"w:ftr\", \"w:footnotes\", \"w:endnotes\", \"w:comments\"]);\n\n// =============================================================================\n// Registry of used ids (per xmlParts object, so a cloned document gets its own)\n// =============================================================================\n\nconst registry = new WeakMap<DocxXmlParts, Set<string>>();\n\n/**\n * Get (building lazily) the set of every paraId currently used in the document.\n */\nfunction getUsedParaIds(doc: NormalizedDocument): Set<string> {\n let used = registry.get(doc.xmlParts);\n if (!used) {\n used = new Set<string>();\n for (const nodes of iterateParts(doc.xmlParts)) {\n collectParaIds(nodes, used);\n }\n registry.set(doc.xmlParts, used);\n }\n return used;\n}\n\nfunction* iterateParts(xmlParts: DocxXmlParts): Generator<FXPNode[]> {\n for (const value of Object.values(xmlParts)) {\n if (Array.isArray(value)) {\n yield value;\n } else if (value instanceof Map) {\n for (const nodes of value.values()) yield nodes;\n }\n }\n}\n\nfunction collectParaIds(nodes: FXPNode[], into: Set<string>): void {\n for (const node of nodes) {\n const tag = getTagName(node);\n if (!tag) continue;\n if (tag === \"w:p\") {\n const id = getAttr(node, \"w14:paraId\");\n if (id) into.add(id.toUpperCase());\n }\n const children = getChildren(node);\n if (children.length > 0) collectParaIds(children, into);\n }\n}\n\n// =============================================================================\n// Generation\n// =============================================================================\n\n/**\n * Generate a fresh paraId not present in `used`, and record it.\n * Word accepts values in (0, 0x80000000); we emit 8 uppercase hex chars.\n */\nexport function generateParaId(used: Set<string>): string {\n for (;;) {\n const value = Math.floor(Math.random() * 0x7ffffffe) + 1;\n const id = value.toString(16).toUpperCase().padStart(8, \"0\");\n if (!used.has(id)) {\n used.add(id);\n return id;\n }\n }\n}\n\n/** Validate the shape of a paraId (8 hex chars, < 0x80000000, non-zero). */\nexport function isValidParaId(id: string): boolean {\n if (!/^[0-9A-Fa-f]{8}$/.test(id)) return false;\n const n = parseInt(id, 16);\n return n > 0 && n < 0x80000000;\n}\n\n// =============================================================================\n// Namespace handling\n// =============================================================================\n\n/** Whether a part root declares the `w14` namespace prefix. */\nexport function partDeclaresW14(rootNode: FXPNode | undefined): boolean {\n if (!rootNode) return false;\n return getAttr(rootNode, \"xmlns:w14\") !== undefined;\n}\n\n/**\n * Declare `xmlns:w14` and add `w14` to `mc:Ignorable` on a part root\n * (`w:document`, `w:hdr`, `w:ftr`). Idempotent.\n */\nexport function ensureW14Namespace(rootNode: FXPNode): void {\n if (getAttr(rootNode, \"xmlns:w14\") === undefined) {\n setAttr(rootNode, \"xmlns:w14\", W14_NAMESPACE);\n }\n if (getAttr(rootNode, \"xmlns:mc\") === undefined) {\n setAttr(rootNode, \"xmlns:mc\", MC_NAMESPACE);\n }\n const ignorable = getAttr(rootNode, \"mc:Ignorable\");\n if (ignorable === undefined) {\n setAttr(rootNode, \"mc:Ignorable\", \"w14\");\n } else {\n const prefixes = ignorable.split(/\\s+/).filter((p) => p.length > 0);\n if (!prefixes.includes(\"w14\")) {\n prefixes.push(\"w14\");\n setAttr(rootNode, \"mc:Ignorable\", prefixes.join(\" \"));\n }\n }\n}\n\n/** Find the root element node of a part (the first node with a known root tag). */\nexport function findPartRoot(nodes: FXPNode[]): FXPNode | undefined {\n return nodes.find((n) => {\n const tag = getTagName(n);\n return tag !== undefined && PART_ROOT_TAGS.has(tag);\n });\n}\n\n// =============================================================================\n// Stamping\n// =============================================================================\n\n/**\n * Stamp every `w:p` without a `w14:paraId` in the given part, declaring the\n * namespace on the root if needed. Used by `Docxtor.load(..., { assignParaIds: true })`.\n *\n * @returns number of paragraphs stamped\n */\nexport function assignMissingParaIds(\n partNodes: FXPNode[],\n used: Set<string>,\n): number {\n const root = findPartRoot(partNodes);\n if (!root) return 0;\n\n let stamped = 0;\n const visit = (nodes: FXPNode[]): void => {\n for (const node of nodes) {\n const tag = getTagName(node);\n if (!tag) continue;\n if (tag === \"w:p\") {\n const existing = getAttr(node, \"w14:paraId\");\n if (!existing || !isValidParaId(existing)) {\n const id = generateParaId(used);\n setAttr(node, \"w14:paraId\", id);\n stamped++;\n if (getAttr(node, \"w14:textId\") === undefined) {\n setAttr(node, \"w14:textId\", id);\n }\n } else if (getAttr(node, \"w14:textId\") === undefined) {\n setAttr(node, \"w14:textId\", existing.toUpperCase());\n }\n }\n const children = getChildren(node);\n if (children.length > 0) visit(children);\n }\n };\n\n // Count first so we only touch the namespace if there's something to stamp\n visit(getChildren(root));\n if (stamped > 0) ensureW14Namespace(root);\n return stamped;\n}\n\n/**\n * Assign paraIds across every part of a freshly parsed document.\n * Builds the registry so later patches generate collision-free ids.\n */\nexport function assignParaIdsToDocument(xmlParts: DocxXmlParts): void {\n const used = new Set<string>();\n for (const nodes of iterateParts(xmlParts)) collectParaIds(nodes, used);\n\n // Only the content parts get stamped (document, headers, footers).\n // Footnotes/endnotes/comments are left alone — Word stamps those itself.\n assignMissingParaIds(xmlParts.document, used);\n for (const nodes of xmlParts.headers.values()) assignMissingParaIds(nodes, used);\n for (const nodes of xmlParts.footers.values()) assignMissingParaIds(nodes, used);\n\n registry.set(xmlParts, used);\n}\n\n/**\n * Stamp fresh paraIds on new paragraph nodes created by a patch.\n *\n * Walks `nodes` recursively (so table cells inside a new `w:tbl` are covered).\n * Does nothing unless the part root declares `w14` — Word would otherwise\n * refuse to open the file. With `overwrite`, existing ids are replaced too\n * (used after cloning a table row, which would otherwise duplicate ids).\n */\nexport function stampNewParagraphs(\n doc: NormalizedDocument,\n nodes: FXPNode[],\n partRoot: FXPNode | undefined,\n overwrite: boolean = false,\n): void {\n if (!partDeclaresW14(partRoot)) return;\n const used = getUsedParaIds(doc);\n\n const visit = (list: FXPNode[]): void => {\n for (const node of list) {\n const tag = getTagName(node);\n if (!tag) continue;\n if (tag === \"w:p\" && (overwrite || getAttr(node, \"w14:paraId\") === undefined)) {\n const id = generateParaId(used);\n setAttr(node, \"w14:paraId\", id);\n setAttr(node, \"w14:textId\", id);\n }\n const children = getChildren(node);\n if (children.length > 0) visit(children);\n }\n };\n visit(nodes);\n}\n\n/**\n * Resolve the part root node that contains a normalized element, judged by\n * its positional id prefix (`hdr.` / `ftr.` → that header/footer, else body).\n */\nexport function findPartRootForId(doc: NormalizedDocument, id: string): FXPNode | undefined {\n const m = id.match(/^(hdr|ftr)\\.([^.]+)\\./);\n if (m) {\n const map = m[1] === \"hdr\" ? doc.headers : doc.footers;\n const rootTag = m[1] === \"hdr\" ? \"w:hdr\" : \"w:ftr\";\n for (const hf of map.values()) {\n if (hf.variant === m[2]) {\n return hf.xmlRef.find((n) => getTagName(n) === rootTag);\n }\n }\n return undefined;\n }\n return doc.xmlParts.document.find((n) => getTagName(n) === \"w:document\");\n}\n","/**\n * Document normalizer: orchestrates the full normalization pipeline.\n *\n * Takes the raw XML parts from the ZIP reader, parses them with\n * fast-xml-parser, and builds the full NormalizedDocument with\n * paragraphs, tables, headers, and footers — all holding xmlRef\n * pointers back to the mutable FXP objects.\n */\n\nimport type {\n FXPNode,\n NormalizedDocument,\n BodyElement,\n HeaderFooter,\n DocxXmlParts,\n LoadOptions,\n StyleInfo,\n NormalizedPageBreak,\n NormalizedParagraph,\n} from \"../types.js\";\nimport type { DocxZipEntries } from \"../zip/reader.js\";\nimport { parseXml } from \"../xml/parser.js\";\nimport {\n findChild,\n findChildren,\n getChildren,\n getTagName,\n getAttr,\n} from \"../xml/helpers.js\";\nimport { normalizeParagraph } from \"./paragraph.js\";\nimport { normalizeTable } from \"./table.js\";\nimport { normalizeHeaderFooter } from \"./header-footer.js\";\nimport { resolveListType } from \"../patches/numbering.js\";\nimport { assignParaIdsToDocument } from \"./para-id.js\";\n\n/**\n * Normalize a full DOCX document from zip entries.\n *\n * Returns a NormalizedDocument where every element holds xmlRef pointers\n * into the FXP objects. The FXP objects are the mutable XML — patches\n * mutate them, and serializing them produces the output document.\n */\nexport function normalizeDocument(\n zipEntries: DocxZipEntries,\n options?: LoadOptions,\n): NormalizedDocument {\n // Parse all XML parts\n const documentFxp = parseXml(zipEntries.documentXml);\n\n const headersFxp = new Map<string, FXPNode[]>();\n for (const [path, xml] of zipEntries.headers) {\n headersFxp.set(path, parseXml(xml));\n }\n\n const footersFxp = new Map<string, FXPNode[]>();\n for (const [path, xml] of zipEntries.footers) {\n footersFxp.set(path, parseXml(xml));\n }\n\n // Parse styles.xml if present\n const styles = zipEntries.stylesXml\n ? parseStyles(zipEntries.stylesXml)\n : [];\n\n const parseOptional = (xml: string | undefined): FXPNode[] | undefined =>\n xml ? parseXml(xml) : undefined;\n\n const xmlParts: DocxXmlParts = {\n document: documentFxp,\n headers: headersFxp,\n footers: footersFxp,\n documentRels: parseOptional(zipEntries.documentRels),\n contentTypes: parseOptional(zipEntries.contentTypes),\n settings: parseOptional(zipEntries.settingsXml),\n numbering: parseOptional(zipEntries.numberingXml),\n footnotes: parseOptional(zipEntries.footnotesXml),\n endnotes: parseOptional(zipEntries.endnotesXml),\n comments: parseOptional(zipEntries.commentsXml),\n core: parseOptional(zipEntries.coreXml),\n app: parseOptional(zipEntries.appXml),\n rootRels: parseOptional(zipEntries.rootRels),\n };\n\n // Stamp native paragraph ids before normalizing so they show up in the AST\n if (options?.assignParaIds) {\n assignParaIdsToDocument(xmlParts);\n }\n\n const doc: NormalizedDocument = {\n body: [],\n headers: new Map(),\n footers: new Map(),\n xmlParts,\n styles,\n loadOptions: options,\n };\n\n reNormalizeDocument(doc);\n\n return doc;\n}\n\n/**\n * Rebuild the normalized view (body, headers, footers, list types) of a\n * document from its current XML parts, in place.\n *\n * Call this after a structural change that invalidates positional ids\n * across the whole document — e.g., accepting/rejecting all revisions.\n * `paraId`s are read from the XML so they are unaffected.\n */\nexport function reNormalizeDocument(doc: NormalizedDocument): void {\n const includeHeadersFooters = doc.loadOptions?.includeHeadersFooters ?? true;\n\n doc.body = normalizeBody(doc.xmlParts.document);\n doc.headers = new Map<string, HeaderFooter>();\n doc.footers = new Map<string, HeaderFooter>();\n\n if (includeHeadersFooters) {\n const relMap = parseRelationshipsFxp(doc.xmlParts.documentRels);\n const sectPrRefs = findHeaderFooterRefs(doc.xmlParts.document);\n\n for (const ref of sectPrRefs) {\n const target = relMap.get(ref.rId);\n if (!target) continue;\n\n const fullPath = `word/${target}`;\n\n if (ref.type === \"header\") {\n const fxp = doc.xmlParts.headers.get(fullPath);\n if (fxp) {\n doc.headers.set(ref.rId, normalizeHeaderFooter(fxp, \"header\", ref.variant, ref.rId));\n }\n } else {\n const fxp = doc.xmlParts.footers.get(fullPath);\n if (fxp) {\n doc.footers.set(ref.rId, normalizeHeaderFooter(fxp, \"footer\", ref.variant, ref.rId));\n }\n }\n }\n }\n\n // Post-pass: enrich listType on all paragraphs with listInfo\n enrichAllListTypes(doc);\n}\n\n/**\n * Normalize the document body (w:document > w:body children).\n */\nfunction normalizeBody(documentFxp: FXPNode[]): BodyElement[] {\n const elements: BodyElement[] = [];\n\n // Find w:document > w:body\n const docNode = documentFxp.find((n) => getTagName(n) === \"w:document\");\n if (!docNode) return elements;\n\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) return elements;\n\n const bodyChildren = getChildren(bodyNode);\n let paraIdx = 0;\n let tableIdx = 0;\n let pageBreakIdx = 0;\n\n for (const child of bodyChildren) {\n const tag = getTagName(child);\n if (tag === \"w:p\") {\n // Normalize once and reuse the result for both the page-break-only\n // check and the final elements.push — avoids duplicating the full\n // run mapping + revision scanning work.\n const normalized = normalizeParagraph(child, `p${paraIdx}`);\n\n if (normalized.hasPageBreak && normalized.text.trim().length === 0) {\n // Page-break-only paragraph — emit as NormalizedPageBreak\n const pb: NormalizedPageBreak = {\n kind: \"page_break\",\n id: `pb${pageBreakIdx}`,\n xmlRef: child,\n };\n elements.push(pb);\n pageBreakIdx++;\n // Don't increment paraIdx — this isn't a paragraph in the projection\n continue;\n }\n\n elements.push(normalized);\n paraIdx++;\n } else if (tag === \"w:tbl\") {\n elements.push(normalizeTable(child, `tbl${tableIdx}`));\n tableIdx++;\n }\n // Other elements (w:sectPr, etc.) are ignored in the normalized view\n // but preserved in the FXP object\n }\n\n return elements;\n}\n\n/**\n * Build a relationship map (rId → target filename) from parsed\n * document.xml.rels nodes.\n */\nfunction parseRelationshipsFxp(\n parsed: FXPNode[] | undefined,\n): Map<string, string> {\n const map = new Map<string, string>();\n if (!parsed) return map;\n\n // Find the Relationships root element\n const relsNode = parsed.find(\n (n) => getTagName(n) === \"Relationships\",\n );\n if (!relsNode) return map;\n\n const rels = findChildren(relsNode, \"Relationship\");\n for (const rel of rels) {\n const id = getAttr(rel, \"Id\");\n const target = getAttr(rel, \"Target\");\n if (id && target) {\n map.set(id, target);\n }\n }\n\n return map;\n}\n\nexport interface HeaderFooterRef {\n type: \"header\" | \"footer\";\n variant: string; // \"default\", \"first\", \"even\"\n rId: string;\n}\n\n/**\n * Find header/footer references in w:sectPr elements.\n */\nexport function findHeaderFooterRefs(documentFxp: FXPNode[]): HeaderFooterRef[] {\n const refs: HeaderFooterRef[] = [];\n\n const docNode = documentFxp.find((n) => getTagName(n) === \"w:document\");\n if (!docNode) return refs;\n\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) return refs;\n\n // sectPr can be a child of w:body (last section) or w:pPr (section break)\n const sectPrs = findChildren(bodyNode, \"w:sectPr\");\n\n // Also check the last paragraph for embedded sectPr\n const bodyChildren = getChildren(bodyNode);\n for (const child of bodyChildren) {\n if (getTagName(child) === \"w:p\") {\n const pPr = findChild(child, \"w:pPr\");\n if (pPr) {\n const sectPr = findChild(pPr, \"w:sectPr\");\n if (sectPr) sectPrs.push(sectPr);\n }\n }\n }\n\n for (const sectPr of sectPrs) {\n const children = getChildren(sectPr);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:headerReference\") {\n const rId = getAttr(child, \"r:id\");\n const variant = getAttr(child, \"w:type\") ?? \"default\";\n if (rId) refs.push({ type: \"header\", variant, rId });\n } else if (tag === \"w:footerReference\") {\n const rId = getAttr(child, \"r:id\");\n const variant = getAttr(child, \"w:type\") ?? \"default\";\n if (rId) refs.push({ type: \"footer\", variant, rId });\n }\n }\n }\n\n return refs;\n}\n\n// =============================================================================\n// Styles\n// =============================================================================\n\n/**\n * Parse word/styles.xml and extract style definitions.\n *\n * We only extract the style ID, human-readable name, and type.\n * We never modify styles.xml — this is read-only metadata used for\n * validation and projection.\n */\nfunction parseStyles(stylesXml: string): StyleInfo[] {\n const parsed = parseXml(stylesXml);\n const styles: StyleInfo[] = [];\n\n // Find w:styles root\n const stylesNode = parsed.find((n) => getTagName(n) === \"w:styles\");\n if (!stylesNode) return styles;\n\n const styleNodes = findChildren(stylesNode, \"w:style\");\n for (const styleNode of styleNodes) {\n const id = getAttr(styleNode, \"w:styleId\");\n const type = getAttr(styleNode, \"w:type\") as StyleInfo[\"type\"] | undefined;\n\n if (!id || !type) continue;\n\n // Valid types: paragraph, character, table, numbering\n if (![\"paragraph\", \"character\", \"table\", \"numbering\"].includes(type)) continue;\n\n // Get the human-readable name from w:name\n const nameNode = findChild(styleNode, \"w:name\");\n const name = nameNode ? (getAttr(nameNode, \"w:val\") ?? id) : id;\n\n styles.push({ id, name, type });\n }\n\n return styles;\n}\n\n// =============================================================================\n// List type enrichment\n// =============================================================================\n\n/**\n * Enrich listType on all paragraphs with listInfo across the entire document.\n */\nfunction enrichAllListTypes(doc: NormalizedDocument): void {\n const numberingFxp = doc.xmlParts.numbering;\n if (!numberingFxp) return;\n\n for (const el of doc.body) {\n if (el.kind === \"paragraph\") {\n enrichListType(el, numberingFxp);\n } else if (el.kind === \"table\") {\n for (const row of el.rows) {\n for (const cell of row.cells) {\n for (const para of cell.paragraphs) {\n enrichListType(para, numberingFxp);\n }\n }\n }\n }\n }\n\n for (const hdr of doc.headers.values()) {\n for (const para of hdr.paragraphs) {\n enrichListType(para, numberingFxp);\n }\n }\n\n for (const ftr of doc.footers.values()) {\n for (const para of ftr.paragraphs) {\n enrichListType(para, numberingFxp);\n }\n }\n}\n\n/**\n * Enrich a single paragraph's listInfo with the resolved listType.\n * Exported for use by the patch engine after re-normalization.\n */\nexport function enrichListType(para: NormalizedParagraph, numberingFxp: FXPNode[]): void {\n if (!para.listInfo) return;\n const lt = resolveListType(numberingFxp, para.listInfo.numId);\n if (lt) {\n para.listInfo.listType = lt;\n }\n}\n","/**\n * Block-level markdown segmentation for multi-line patch text.\n *\n * Splits patch text (`append_text`, `insert_paragraph` validation) into\n * plain text lines, markdown tables, and markdown lists.\n */\n\n/** A contiguous block of text lines, a markdown table, or a markdown list. */\nexport type TextSegment =\n | { kind: \"text\"; lines: string[] }\n | { kind: \"table\"; headers: string[]; rows: string[][] }\n | { kind: \"list\"; listType: \"bullet\" | \"ordered\"; items: { text: string; level: number }[] };\n\n/**\n * Parse text into segments of plain lines, markdown tables, and markdown lists.\n *\n * A markdown table is detected as:\n * - A header row: `| col1 | col2 | ...`\n * - A separator row: `| --- | --- | ...` (dashes, colons, spaces)\n * - Zero or more data rows: `| val1 | val2 | ...`\n *\n * A markdown list is detected as consecutive lines starting with:\n * - Bullet: `- `, `* `, `+ ` (after optional whitespace)\n * - Ordered: `\\d+\\. ` (after optional whitespace)\n * Nesting uses 2-space increments. Blank lines within a list don't break it.\n */\nexport function parseTextSegments(text: string): TextSegment[] {\n const lines = text.split(\"\\n\");\n const segments: TextSegment[] = [];\n let i = 0;\n\n while (i < lines.length) {\n // Try to detect a markdown table starting at line i\n if (\n i + 1 < lines.length &&\n isMarkdownTableRow(lines[i]!) &&\n isMarkdownSeparator(lines[i + 1]!)\n ) {\n const headerCells = parseMarkdownRow(lines[i]!);\n // Skip header and separator\n i += 2;\n const dataRows: string[][] = [];\n while (i < lines.length && isMarkdownTableRow(lines[i]!)) {\n const cells = parseMarkdownRow(lines[i]!);\n // Pad or trim to match header count\n while (cells.length < headerCells.length) cells.push(\"\");\n if (cells.length > headerCells.length)\n cells.length = headerCells.length;\n dataRows.push(cells);\n i++;\n }\n segments.push({ kind: \"table\", headers: headerCells, rows: dataRows });\n continue;\n }\n\n // Try to detect a markdown list\n const listMatch = parseListLine(lines[i]!);\n if (listMatch) {\n const items: { text: string; level: number }[] = [];\n const listType = listMatch.listType;\n items.push({ text: listMatch.text, level: listMatch.level });\n i++;\n\n while (i < lines.length) {\n const line = lines[i]!;\n // Blank lines don't break the list\n if (line.trim().length === 0) {\n i++;\n continue;\n }\n const nextMatch = parseListLine(line);\n if (!nextMatch) break;\n // Type change breaks the list\n if (nextMatch.listType !== listType) break;\n\n items.push({ text: nextMatch.text, level: nextMatch.level });\n i++;\n }\n\n segments.push({ kind: \"list\", listType, items });\n continue;\n }\n\n // Plain text line\n const last = segments[segments.length - 1];\n if (last && last.kind === \"text\") {\n last.lines.push(lines[i]!);\n } else {\n segments.push({ kind: \"text\", lines: [lines[i]!] });\n }\n i++;\n }\n\n return segments;\n}\n\n/** True if the text contains a markdown table or list block. */\nexport function hasBlockMarkdown(text: string): boolean {\n return parseTextSegments(text).some((s) => s.kind !== \"text\");\n}\n\n/** Bullet pattern: optional whitespace + `- `, `* `, or `+ ` */\nconst BULLET_RE = /^(\\s*)[*+-]\\s(.*)$/;\n/** Ordered pattern: optional whitespace + `N. ` */\nconst ORDERED_RE = /^(\\s*)\\d+\\.\\s(.*)$/;\n\nfunction parseListLine(\n line: string,\n): { listType: \"bullet\" | \"ordered\"; text: string; level: number } | undefined {\n // Normalize tabs to 2 spaces\n const normalized = line.replace(/\\t/g, \" \");\n let m = BULLET_RE.exec(normalized);\n if (m) {\n const indent = m[1]!.length;\n return { listType: \"bullet\", text: m[2]!, level: Math.floor(indent / 2) };\n }\n m = ORDERED_RE.exec(normalized);\n if (m) {\n const indent = m[1]!.length;\n return { listType: \"ordered\", text: m[2]!, level: Math.floor(indent / 2) };\n }\n return undefined;\n}\n\nfunction isMarkdownTableRow(line: string): boolean {\n const trimmed = line.trim();\n return trimmed.startsWith(\"|\") && trimmed.endsWith(\"|\") && trimmed.length > 1;\n}\n\nfunction isMarkdownSeparator(line: string): boolean {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"|\") || !trimmed.endsWith(\"|\")) return false;\n // Each cell should be dashes (with optional colons for alignment)\n const cells = trimmed.slice(1, -1).split(\"|\");\n return cells.every((cell) => /^\\s*:?-+:?\\s*$/.test(cell));\n}\n\nfunction parseMarkdownRow(line: string): string[] {\n const trimmed = line.trim();\n // Remove leading/trailing pipes and split\n const inner = trimmed.startsWith(\"|\") ? trimmed.slice(1) : trimmed;\n const content = inner.endsWith(\"|\") ? inner.slice(0, -1) : inner;\n return content.split(\"|\").map((cell) => cell.trim());\n}\n","/**\n * Patch validator: validates patch structure before execution.\n */\n\nimport type {\n Patch,\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n BodyElement,\n} from \"../types.js\";\nimport { getChildren, getTagName, getAttr } from \"../xml/helpers.js\";\nimport { hasBlockMarkdown } from \"./text-segments.js\";\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/** Prefix for native paragraph identity targets: `para:4F2A1C7B`. */\nexport const PARA_ID_PREFIX = \"para:\";\n\n/**\n * Validate a single patch against the document.\n */\nexport function validatePatch(\n patch: Patch,\n doc: NormalizedDocument,\n): ValidationResult {\n switch (patch.type) {\n case \"insert_paragraph\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!patch.text || patch.text.length === 0) {\n return { valid: false, error: \"Empty paragraph text\" };\n }\n if (![\"before\", \"after\"].includes(patch.position)) {\n return { valid: false, error: `Invalid position: ${patch.position}` };\n }\n if (hasBlockMarkdown(patch.text)) {\n return {\n valid: false,\n error:\n \"Markdown tables and lists are not supported in insert_paragraph; use add_table or add_list with afterId\",\n };\n }\n return { valid: true };\n }\n case \"delete_paragraph\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n return { valid: true };\n }\n case \"add_comment\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!patch.text || patch.text.length === 0) {\n return { valid: false, error: \"Empty comment text\" };\n }\n if (patch.find !== undefined && patch.find.length === 0) {\n return { valid: false, error: \"Empty find string\" };\n }\n if (patch.find !== undefined && patch.occurrence !== undefined) {\n if (patch.occurrence < 1 || !Number.isInteger(patch.occurrence)) {\n return {\n valid: false,\n error: `Invalid occurrence: ${patch.occurrence} (must be a positive integer)`,\n };\n }\n }\n return { valid: true };\n }\n case \"replace_text\":\n case \"insert_text\":\n case \"delete_text\":\n case \"replace_cell_text\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return {\n valid: false,\n error: `Paragraph \"${patch.target}\" not found`,\n };\n }\n if (\"find\" in patch && patch.find.length === 0) {\n return { valid: false, error: \"Empty find string\" };\n }\n if (patch.type === \"insert_text\" && patch.text.length === 0) {\n return { valid: false, error: \"Empty insert text\" };\n }\n if (\"occurrence\" in patch && patch.occurrence !== undefined) {\n if (patch.occurrence < 1 || !Number.isInteger(patch.occurrence)) {\n return {\n valid: false,\n error: `Invalid occurrence: ${patch.occurrence} (must be a positive integer)`,\n };\n }\n }\n return { valid: true };\n }\n case \"add_table_row\": {\n const table = findTableById(patch.target, doc);\n if (!table) {\n return {\n valid: false,\n error: `Table \"${patch.target}\" not found`,\n };\n }\n if (patch.cells.length === 0) {\n return { valid: false, error: \"Empty cells array\" };\n }\n if (patch.afterRow < -1 || patch.afterRow >= table.rows.length) {\n return {\n valid: false,\n error: `Invalid afterRow ${patch.afterRow} for table \"${patch.target}\" with ${table.rows.length} rows`,\n };\n }\n return { valid: true };\n }\n case \"delete_table_row\": {\n const table = findTableById(patch.target, doc);\n if (!table) {\n return {\n valid: false,\n error: `Table \"${patch.target}\" not found`,\n };\n }\n if (patch.row < 0 || patch.row >= table.rows.length) {\n return {\n valid: false,\n error: `Invalid row index ${patch.row} for table \"${patch.target}\" with ${table.rows.length} rows`,\n };\n }\n return { valid: true };\n }\n case \"apply_formatting\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return {\n valid: false,\n error: `Paragraph \"${patch.target}\" not found`,\n };\n }\n if (patch.find !== undefined && patch.find.length === 0) {\n return { valid: false, error: \"Empty find string\" };\n }\n // Only validate occurrence when find is provided (it's ignored otherwise)\n if (patch.find !== undefined && patch.find.length > 0) {\n if (patch.occurrence !== undefined) {\n if (patch.occurrence < 1 || !Number.isInteger(patch.occurrence)) {\n return {\n valid: false,\n error: `Invalid occurrence: ${patch.occurrence} (must be a positive integer)`,\n };\n }\n }\n }\n // Must have at least one formatting property with a meaningful value\n // (undefined values are ignored, so { bold: undefined } is effectively empty)\n if (\n !patch.formatting ||\n !Object.values(patch.formatting).some((v) => v !== undefined)\n ) {\n return { valid: false, error: \"Empty formatting object\" };\n }\n return { valid: true };\n }\n case \"apply_style\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return {\n valid: false,\n error: `Paragraph \"${patch.target}\" not found`,\n };\n }\n if (patch.find !== undefined && patch.find.length === 0) {\n return { valid: false, error: \"Empty find string\" };\n }\n // Only validate occurrence when find is provided (it's ignored otherwise)\n if (patch.find !== undefined && patch.find.length > 0) {\n if (patch.occurrence !== undefined) {\n if (patch.occurrence < 1 || !Number.isInteger(patch.occurrence)) {\n return {\n valid: false,\n error: `Invalid occurrence: ${patch.occurrence} (must be a positive integer)`,\n };\n }\n }\n }\n if (!patch.style || patch.style.length === 0) {\n return { valid: false, error: \"Empty style ID\" };\n }\n // Validate style exists and is correct type\n const styleInfo = doc.styles.find((s) => s.id === patch.style);\n if (!styleInfo) {\n return {\n valid: false,\n error: `Style \"${patch.style}\" not found in document styles`,\n };\n }\n if (patch.find !== undefined && patch.find.length > 0) {\n // Character style mode\n if (styleInfo.type !== \"character\") {\n return {\n valid: false,\n error: `Style \"${patch.style}\" is type \"${styleInfo.type}\", expected \"character\" when \"find\" is specified`,\n };\n }\n } else {\n // Paragraph style mode\n if (styleInfo.type !== \"paragraph\") {\n return {\n valid: false,\n error: `Style \"${patch.style}\" is type \"${styleInfo.type}\", expected \"paragraph\" when \"find\" is omitted`,\n };\n }\n }\n return { valid: true };\n }\n case \"append_text\": {\n if (!patch.text || patch.text.length === 0) {\n return { valid: false, error: \"Empty append text\" };\n }\n if (patch.container) {\n const containerValidation = validateContainerTarget(patch.container, doc);\n if (!containerValidation.valid) {\n return containerValidation;\n }\n }\n return { valid: true };\n }\n case \"add_table\": {\n if (!patch.rows || patch.rows.length === 0) {\n if (!patch.headers || patch.headers.length === 0) {\n return { valid: false, error: \"Table must have at least one row or headers\" };\n }\n }\n // Determine column count from headers or first row\n const colCount = patch.headers?.length ?? (patch.rows.length > 0 ? patch.rows[0]!.length : 0);\n if (colCount === 0) {\n return { valid: false, error: \"Table must have at least one column\" };\n }\n // All rows must have the same column count\n for (let i = 0; i < patch.rows.length; i++) {\n if (patch.rows[i]!.length !== colCount) {\n return {\n valid: false,\n error: `Row ${i} has ${patch.rows[i]!.length} cells, expected ${colCount}`,\n };\n }\n }\n // If headers provided, must match column count\n if (patch.headers && patch.rows.length > 0 && patch.headers.length !== patch.rows[0]!.length) {\n return {\n valid: false,\n error: `Headers have ${patch.headers.length} cells, but rows have ${patch.rows[0]!.length}`,\n };\n }\n // Validate afterId target exists if provided\n if (patch.afterId) {\n if (patch.container) {\n return {\n valid: false,\n error: `\"afterId\" is not supported when \"container\" is set — container-targeted tables are appended to the end of the header/footer`,\n };\n }\n const found = findElementById(patch.afterId, doc);\n if (!found) {\n return {\n valid: false,\n error: `Element \"${patch.afterId}\" not found`,\n };\n }\n }\n // Validate container if provided\n if (patch.container) {\n const containerValidation = validateContainerTarget(patch.container, doc);\n if (!containerValidation.valid) {\n return containerValidation;\n }\n }\n return { valid: true };\n }\n case \"add_header\":\n case \"add_footer\": {\n if (![\"default\", \"first\", \"even\"].includes(patch.variant)) {\n return { valid: false, error: `Invalid variant: ${patch.variant}` };\n }\n if (!patch.paragraphs || patch.paragraphs.length === 0) {\n return { valid: false, error: \"At least one paragraph is required\" };\n }\n return { valid: true };\n }\n case \"remove_header\":\n case \"remove_footer\": {\n if (![\"default\", \"first\", \"even\"].includes(patch.variant)) {\n return { valid: false, error: `Invalid variant: ${patch.variant}` };\n }\n return { valid: true };\n }\n\n // =========================================================================\n // List operations\n // =========================================================================\n\n case \"add_list\": {\n const validTypes = [\"bullet\", \"ordered\", \"multilevel\"];\n if (!validTypes.includes(patch.listType)) {\n return { valid: false, error: `Invalid listType: ${patch.listType}` };\n }\n if (!patch.items || patch.items.length === 0) {\n return { valid: false, error: \"items must be non-empty\" };\n }\n if (patch.afterId && patch.container) {\n return {\n valid: false,\n error: `\"afterId\" is not supported when \"container\" is set`,\n };\n }\n if (patch.afterId) {\n const found = findElementById(patch.afterId, doc);\n if (!found) {\n return { valid: false, error: `Element \"${patch.afterId}\" not found` };\n }\n }\n if (patch.container) {\n const containerValidation = validateContainerTarget(patch.container, doc);\n if (!containerValidation.valid) return containerValidation;\n }\n if (patch.numId !== undefined) {\n // Validate that the numId exists in numbering.xml\n if (!doc.xmlParts.numbering) {\n return { valid: false, error: `numId \"${patch.numId}\" specified but document has no numbering.xml` };\n }\n const root = doc.xmlParts.numbering.find((n) => getTagName(n) === \"w:numbering\");\n const numExists = root\n ? getChildren(root).some(\n (c) => getTagName(c) === \"w:num\" && getAttr(c, \"w:numId\") === patch.numId,\n )\n : false;\n if (!numExists) {\n return { valid: false, error: `numId \"${patch.numId}\" not found in numbering.xml` };\n }\n }\n return { valid: true };\n }\n\n case \"add_list_item\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!para.listInfo) {\n return { valid: false, error: `Paragraph \"${patch.target}\" is not a list item` };\n }\n if (!patch.text && patch.text !== \"\") {\n return { valid: false, error: \"text is required\" };\n }\n if (![\"before\", \"after\"].includes(patch.position)) {\n return { valid: false, error: `Invalid position: ${patch.position}` };\n }\n if (patch.level !== undefined && patch.level < 0) {\n return { valid: false, error: \"level must be >= 0\" };\n }\n return { valid: true };\n }\n\n case \"remove_list_item\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!para.listInfo) {\n return { valid: false, error: `Paragraph \"${patch.target}\" is not a list item` };\n }\n return { valid: true };\n }\n\n case \"convert_to_list\": {\n const validTypes = [\"bullet\", \"ordered\", \"multilevel\"];\n if (!validTypes.includes(patch.listType)) {\n return { valid: false, error: `Invalid listType: ${patch.listType}` };\n }\n if (!patch.targets || patch.targets.length === 0) {\n return { valid: false, error: \"targets must be non-empty\" };\n }\n for (const target of patch.targets) {\n const para = findParagraphById(target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${target}\" not found` };\n }\n }\n return { valid: true };\n }\n\n case \"remove_list\": {\n if (!patch.targets || patch.targets.length === 0) {\n return { valid: false, error: \"targets must be non-empty\" };\n }\n for (const target of patch.targets) {\n const para = findParagraphById(target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${target}\" not found` };\n }\n if (!para.listInfo) {\n return { valid: false, error: `Paragraph \"${target}\" is not a list item` };\n }\n }\n return { valid: true };\n }\n\n case \"set_list_properties\": {\n const para = findParagraphById(patch.target, doc);\n if (!para) {\n return { valid: false, error: `Paragraph \"${patch.target}\" not found` };\n }\n if (!para.listInfo) {\n return { valid: false, error: `Paragraph \"${patch.target}\" is not a list item` };\n }\n if (patch.listType === undefined && patch.level === undefined) {\n return { valid: false, error: \"At least one of listType or level must be provided\" };\n }\n if (patch.listType !== undefined && ![\"bullet\", \"ordered\", \"multilevel\"].includes(patch.listType)) {\n return { valid: false, error: `Invalid listType: ${patch.listType}` };\n }\n if (patch.level !== undefined && patch.level < 0) {\n return { valid: false, error: \"level must be >= 0\" };\n }\n return { valid: true };\n }\n\n default:\n return {\n valid: false,\n error: `Unknown patch type: ${(patch as Patch).type}`,\n };\n }\n}\n\nfunction validateContainerTarget(\n container: { type: string; variant: string },\n doc: NormalizedDocument,\n): ValidationResult {\n if (![\"header\", \"footer\"].includes(container.type)) {\n return { valid: false, error: `Invalid container type: ${container.type}` };\n }\n if (![\"default\", \"first\", \"even\"].includes(container.variant)) {\n return { valid: false, error: `Invalid container variant: ${container.variant}` };\n }\n\n const parts = container.type === \"header\" ? doc.headers : doc.footers;\n const exists = [...parts.values()].some((part) => part.variant === container.variant);\n if (!exists) {\n return {\n valid: false,\n error: `No ${container.type} with variant \"${container.variant}\" found`,\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Find a paragraph by its ID anywhere in the document\n * (body, tables, headers, footers).\n *\n * Accepts either a positional id (`p12`, `tbl0.r1.c0.p0`, `hdr.default.p0`)\n * or a native id in the form `para:4F2A1C7B`.\n */\nexport function findParagraphById(\n id: string,\n doc: NormalizedDocument,\n): NormalizedParagraph | undefined {\n if (id.startsWith(PARA_ID_PREFIX)) {\n return findParagraphByParaId(id.slice(PARA_ID_PREFIX.length), doc);\n }\n return findParagraph(doc, (p) => p.id === id);\n}\n\n/**\n * Find a paragraph by its native `w14:paraId` anywhere in the document.\n * Resolution order: body (paragraphs and table cells in document order),\n * headers, footers. Case-insensitive.\n */\nexport function findParagraphByParaId(\n paraId: string,\n doc: NormalizedDocument,\n): NormalizedParagraph | undefined {\n const wanted = paraId.toUpperCase();\n return findParagraph(doc, (p) => p.paraId === wanted);\n}\n\nfunction findParagraph(\n doc: NormalizedDocument,\n predicate: (p: NormalizedParagraph) => boolean,\n): NormalizedParagraph | undefined {\n // Search body elements\n for (const element of doc.body) {\n const found = searchParagraphInElement(predicate, element);\n if (found) return found;\n }\n\n // Search headers (paragraphs, then tables)\n for (const header of doc.headers.values()) {\n for (const para of header.paragraphs) {\n if (predicate(para)) return para;\n }\n for (const table of header.tables) {\n const found = searchParagraphInTable(predicate, table);\n if (found) return found;\n }\n }\n\n // Search footers\n for (const footer of doc.footers.values()) {\n for (const para of footer.paragraphs) {\n if (predicate(para)) return para;\n }\n for (const table of footer.tables) {\n const found = searchParagraphInTable(predicate, table);\n if (found) return found;\n }\n }\n\n return undefined;\n}\n\n/**\n * Find a table by its ID in the document body, headers, and footers (including nested tables).\n */\nexport function findTableById(\n id: string,\n doc: NormalizedDocument,\n): NormalizedTable | undefined {\n for (const element of doc.body) {\n if (element.kind === \"table\") {\n const found = searchTableInTable(id, element);\n if (found) return found;\n }\n }\n\n // Search header tables\n for (const header of doc.headers.values()) {\n for (const table of header.tables) {\n const found = searchTableInTable(id, table);\n if (found) return found;\n }\n }\n\n // Search footer tables\n for (const footer of doc.footers.values()) {\n for (const table of footer.tables) {\n const found = searchTableInTable(id, table);\n if (found) return found;\n }\n }\n\n return undefined;\n}\n\nfunction searchTableInTable(\n id: string,\n table: NormalizedTable,\n): NormalizedTable | undefined {\n if (table.id === id) return table;\n\n // Search nested tables\n for (const row of table.rows) {\n for (const cell of row.cells) {\n for (const nested of cell.nestedTables) {\n const found = searchTableInTable(id, nested);\n if (found) return found;\n }\n }\n }\n\n return undefined;\n}\n\nfunction searchParagraphInElement(\n predicate: (p: NormalizedParagraph) => boolean,\n element: BodyElement,\n): NormalizedParagraph | undefined {\n if (element.kind === \"paragraph\") {\n return predicate(element) ? element : undefined;\n }\n\n if (element.kind === \"table\") {\n return searchParagraphInTable(predicate, element);\n }\n\n return undefined;\n}\n\nfunction searchParagraphInTable(\n predicate: (p: NormalizedParagraph) => boolean,\n table: NormalizedTable,\n): NormalizedParagraph | undefined {\n for (const row of table.rows) {\n for (const cell of row.cells) {\n for (const para of cell.paragraphs) {\n if (predicate(para)) return para;\n }\n // Search nested tables\n for (const nested of cell.nestedTables) {\n const found = searchParagraphInTable(predicate, nested);\n if (found) return found;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Find any top-level body element (paragraph or table) by its ID.\n * Accepts `para:` ids for body paragraphs.\n */\nexport function findElementById(\n id: string,\n doc: NormalizedDocument,\n): BodyElement | undefined {\n if (id.startsWith(PARA_ID_PREFIX)) {\n const wanted = id.slice(PARA_ID_PREFIX.length).toUpperCase();\n return doc.body.find((el) => el.kind === \"paragraph\" && el.paraId === wanted);\n }\n for (const element of doc.body) {\n if (element.kind === \"paragraph\" && element.id === id) return element;\n if (element.kind === \"table\") {\n if (element.id === id) return element;\n }\n }\n return undefined;\n}\n","/**\n * Text-to-runs mapping: given a character range in the normalized\n * paragraph text, resolves which RunMapping(s) are affected and\n * computes sub-run offsets.\n *\n * This is the bridge between \"find text in paragraph\" and\n * \"mutate the right XML runs.\"\n */\n\nimport type { RunMapping } from \"../types.js\";\n\n/**\n * A resolved range within a specific run.\n * Tells you exactly which portion of a run is affected by a text match.\n */\nexport interface ResolvedRunRange {\n /** The RunMapping this range falls in. */\n run: RunMapping;\n /** Start offset within this run's text (0-based, inclusive). */\n localStart: number;\n /** End offset within this run's text (0-based, exclusive). */\n localEnd: number;\n /** Whether this range covers the entire run text. */\n isFullRun: boolean;\n /** Whether this range starts at the beginning of the run. */\n isStartAligned: boolean;\n /** Whether this range ends at the end of the run. */\n isEndAligned: boolean;\n}\n\n/**\n * Find the Nth occurrence of a substring in a paragraph's text.\n *\n * @param paragraphText - The full normalized paragraph text\n * @param find - The text to search for\n * @param occurrence - Which occurrence (1-based). Default: 1.\n * @returns The start index of the match, or -1 if not found.\n */\nexport function findOccurrence(\n paragraphText: string,\n find: string,\n occurrence: number = 1,\n): number {\n if (find.length === 0) return -1;\n\n let count = 0;\n let startIdx = 0;\n\n while (startIdx <= paragraphText.length - find.length) {\n const idx = paragraphText.indexOf(find, startIdx);\n if (idx === -1) return -1;\n\n count++;\n if (count === occurrence) return idx;\n\n startIdx = idx + 1;\n }\n\n return -1;\n}\n\n/**\n * Resolve a character range to the affected RunMapping(s).\n *\n * Given a start and end offset in the paragraph's normalized text,\n * returns the list of runs that overlap with this range, along with\n * the local offsets within each run.\n *\n * @param runs - The paragraph's RunMapping array\n * @param rangeStart - Start offset in paragraph text (inclusive)\n * @param rangeEnd - End offset in paragraph text (exclusive)\n */\nexport function resolveRunRanges(\n runs: RunMapping[],\n rangeStart: number,\n rangeEnd: number,\n): ResolvedRunRange[] {\n const results: ResolvedRunRange[] = [];\n\n for (const run of runs) {\n // Check if this run overlaps with the range\n if (run.endOffset <= rangeStart || run.startOffset >= rangeEnd) {\n continue; // No overlap\n }\n\n // Calculate local offsets within this run\n const localStart = Math.max(0, rangeStart - run.startOffset);\n const localEnd = Math.min(run.text.length, rangeEnd - run.startOffset);\n\n results.push({\n run,\n localStart,\n localEnd,\n isFullRun: localStart === 0 && localEnd === run.text.length,\n isStartAligned: localStart === 0,\n isEndAligned: localEnd === run.text.length,\n });\n }\n\n return results;\n}\n\n/**\n * Find all occurrences of a substring in a paragraph's text.\n *\n * @param paragraphText - The full normalized paragraph text\n * @param find - The text to search for\n * @returns Array of start indices\n */\nexport function findAllOccurrences(\n paragraphText: string,\n find: string,\n): number[] {\n if (find.length === 0) return [];\n\n const indices: number[] = [];\n let startIdx = 0;\n\n while (startIdx <= paragraphText.length - find.length) {\n const idx = paragraphText.indexOf(find, startIdx);\n if (idx === -1) break;\n indices.push(idx);\n startIdx = idx + 1;\n }\n\n return indices;\n}\n","/**\n * Run content: a run's text plus the zero-width children that sit between\n * its characters.\n *\n * A `w:r` holds `w:rPr`, text (`w:t`, and `w:br` soft breaks that read as\n * `\\n`) and children that contribute nothing to the paragraph text: page and\n * column breaks, `w:lastRenderedPageBreak`, tabs, drawings, symbols, field\n * characters. Every text edit that rebuilds a run must carry those children\n * across at the character position they occupied, or a page break vanishes\n * because a word next to it was replaced.\n */\n\nimport type { FXPNode } from \"../types.js\";\nimport {\n getChildren,\n getAttr,\n cloneNode,\n createWt,\n unescapeXmlText,\n} from \"../xml/helpers.js\";\n\n/** A non-text child of a run, anchored before the character at `offset`. */\nexport interface AnchoredChild {\n node: FXPNode;\n /** Characters of run text that precede this child (0 = before the first). */\n offset: number;\n}\n\n/** A run taken apart: its properties, its text and its anchored children. */\nexport interface RunContent {\n rPr?: FXPNode;\n text: string;\n anchored: AnchoredChild[];\n}\n\n/** True for children that are part of the run's text: `w:t` and soft breaks. */\nexport function isTextChild(child: FXPNode): boolean {\n if (\"w:t\" in child) return true;\n if (\"w:br\" in child) {\n const brType = getAttr(child, \"w:type\");\n return !brType || brType === \"textWrapping\";\n }\n return false;\n}\n\n/**\n * Read a run as text plus anchored children. Mirrors `getRunText`: `w:t`\n * contributes its characters, a soft `w:br` one `\\n`, everything else none.\n */\nexport function readRunContent(runNode: FXPNode): RunContent {\n const content: RunContent = { text: \"\", anchored: [] };\n for (const child of getChildren(runNode)) {\n if (\"w:rPr\" in child) {\n content.rPr = child;\n } else if (\"w:t\" in child) {\n const tChildren = child[\"w:t\"];\n if (Array.isArray(tChildren)) {\n for (const tChild of tChildren) {\n if (\"#text\" in tChild) {\n content.text += unescapeXmlText(String(tChild[\"#text\"]));\n }\n }\n }\n } else if (isTextChild(child)) {\n content.text += \"\\n\";\n } else {\n content.anchored.push({ node: child, offset: content.text.length });\n }\n }\n return content;\n}\n\n/**\n * Rebuild a run's children from content: `w:rPr` first, then the text as\n * `w:t` / `w:br` pairs with each anchored child placed before the character\n * at its offset (offsets past the end go last). Anchored nodes are moved,\n * not cloned.\n */\nexport function writeRunContent(runNode: FXPNode, content: RunContent): void {\n const children = getChildren(runNode);\n children.length = 0;\n if (content.rPr) children.push(content.rPr);\n\n const anchored = [...content.anchored].sort((a, b) => a.offset - b.offset);\n const text = content.text;\n let next = 0;\n let buf = \"\";\n const flush = (): void => {\n if (buf.length > 0) children.push(createWt(buf));\n buf = \"\";\n };\n for (let i = 0; i < text.length; i++) {\n while (next < anchored.length && anchored[next]!.offset <= i) {\n flush();\n children.push(anchored[next]!.node);\n next++;\n }\n const ch = text[i]!;\n if (ch === \"\\n\") {\n flush();\n children.push({ \"w:br\": [] as FXPNode[] });\n } else {\n buf += ch;\n }\n }\n flush();\n while (next < anchored.length) {\n children.push(anchored[next]!.node);\n next++;\n }\n // A run that ends up with neither text nor anchored children keeps an\n // empty w:t, as a cleared run always has.\n if (text.length === 0 && anchored.length === 0) {\n children.push(createWt(\"\"));\n }\n}\n\n/** Build a fresh `w:r` from content, cloning the run properties. */\nexport function createRunFromContent(content: RunContent): FXPNode {\n const run: FXPNode = { \"w:r\": [] as FXPNode[] };\n writeRunContent(run, {\n rPr: content.rPr ? cloneNode(content.rPr) : undefined,\n text: content.text,\n anchored: content.anchored,\n });\n return run;\n}\n\n/**\n * Cut content at `[start, end)` into three parts. Anchored children at or\n * before `start` go to the first part and those at or after `end` to the\n * last, so a break that sits right next to a match stays outside it; only\n * children strictly inside the range travel with the middle part.\n */\nexport function partitionContent(\n content: RunContent,\n start: number,\n end: number,\n atStart: \"before\" | \"after\" = \"before\",\n): { before: RunContent; matched: RunContent; after: RunContent } {\n const before: RunContent = { rPr: content.rPr, text: content.text.slice(0, start), anchored: [] };\n const matched: RunContent = { rPr: content.rPr, text: content.text.slice(start, end), anchored: [] };\n const after: RunContent = { rPr: content.rPr, text: content.text.slice(end), anchored: [] };\n for (const a of content.anchored) {\n const leading = atStart === \"before\" ? a.offset <= start : a.offset < start;\n if (leading) before.anchored.push(a);\n else if (a.offset >= end) after.anchored.push({ node: a.node, offset: a.offset - end });\n else matched.anchored.push({ node: a.node, offset: a.offset - start });\n }\n return { before, matched, after };\n}\n\n/** True if the content has anything worth keeping as a run. */\nexport function hasContent(content: RunContent): boolean {\n return content.text.length > 0 || content.anchored.length > 0;\n}\n","/**\n * Run splitter: the critical piece of surgical editing.\n *\n * When a text match doesn't align with run boundaries, we need to split\n * runs so the edit can be applied precisely. For example:\n *\n * Run 1: \"This agreement is va\" (plain)\n * Run 2: \"lid and enforceable\" (bold)\n *\n * To replace \"valid\" → \"enforceable\", we need to:\n * 1. Split run 1 at offset 18: \"This agreement is \" | \"va\"\n * 2. Split run 2 at offset 3: \"lid\" | \" and enforceable\"\n * 3. Now \"va\" + \"lid\" are isolated runs that can be replaced\n *\n * The split preserves formatting (w:rPr) — new runs clone the original's\n * properties. This is what ensures minimal redlines and formatting preservation.\n */\n\nimport type { FXPNode } from \"../types.js\";\nimport type { ResolvedRunRange } from \"../mapping/text-to-runs.js\";\nimport { getChildren, getTagName } from \"../xml/helpers.js\";\nimport {\n readRunContent,\n partitionContent,\n createRunFromContent,\n hasContent,\n} from \"./run-content.js\";\n\n/**\n * Result of splitting a run.\n */\nexport interface SplitResult {\n /** The part before the matched range (may be undefined if start-aligned). */\n before?: FXPNode;\n /** The matched part (the run covering the matched text). */\n matched: FXPNode;\n /** The part after the matched range (may be undefined if end-aligned). */\n after?: FXPNode;\n}\n\n/**\n * Find the parent children array that directly contains a run node.\n *\n * Runs can be direct children of w:p, or nested inside w:hyperlink,\n * w:ins, or w:del. We need the actual parent's children array to do\n * splice operations.\n */\nexport function findRunParentChildren(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n): FXPNode[] | undefined {\n // Check direct children first\n const children = getChildren(paragraphNode);\n if (children.includes(runNode)) {\n return children;\n }\n\n // Check inside container elements (w:hyperlink, w:ins, w:del)\n return findRunInContainers(children, runNode);\n}\n\n/**\n * Recursively search container elements for a run node.\n */\nfunction findRunInContainers(\n children: FXPNode[],\n runNode: FXPNode,\n): FXPNode[] | undefined {\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:hyperlink\" || tag === \"w:ins\" || tag === \"w:del\") {\n const containerChildren = getChildren(child);\n if (containerChildren.includes(runNode)) {\n return containerChildren;\n }\n // Recurse deeper (e.g., w:ins inside w:hyperlink)\n const found = findRunInContainers(containerChildren, runNode);\n if (found) return found;\n }\n }\n return undefined;\n}\n\n/**\n * Split a run at the boundaries defined by a ResolvedRunRange.\n *\n * This mutates the parent paragraph's children array:\n * - The original run is replaced with up to 3 runs (before, matched, after)\n * - All new runs clone the original's w:rPr formatting\n * - Zero-width children (page and column breaks, tabs, rendering hints,\n * drawings) stay at the character position they had: a break just before\n * the match lands in the before run, one just after it in the after run,\n * so a partial replacement never drops them\n *\n * @param paragraphNode - The w:p FXP node containing the run\n * @param range - The resolved range within the run to isolate\n * @returns The SplitResult with references to the new nodes\n */\nexport function splitRun(\n paragraphNode: FXPNode,\n range: ResolvedRunRange,\n): SplitResult {\n // If the range covers the full run, no splitting needed\n if (range.isFullRun) {\n return { matched: range.run.xmlRef };\n }\n\n const runNode = range.run.xmlRef;\n const parentChildren = findRunParentChildren(paragraphNode, runNode);\n\n if (!parentChildren) {\n throw new Error(\"Run not found in paragraph children or hyperlink children\");\n }\n\n const runIndex = parentChildren.indexOf(runNode);\n const parts = partitionContent(readRunContent(runNode), range.localStart, range.localEnd);\n\n const result: SplitResult = {\n matched: undefined!,\n };\n\n // Build the replacement runs\n const newRuns: FXPNode[] = [];\n\n if (hasContent(parts.before)) {\n const beforeRun = createRunFromContent(parts.before);\n result.before = beforeRun;\n newRuns.push(beforeRun);\n }\n\n const matchedRun = createRunFromContent(parts.matched);\n result.matched = matchedRun;\n newRuns.push(matchedRun);\n\n if (hasContent(parts.after)) {\n const afterRun = createRunFromContent(parts.after);\n result.after = afterRun;\n newRuns.push(afterRun);\n }\n\n // Replace the original run with the new runs in the parent\n parentChildren.splice(runIndex, 1, ...newRuns);\n\n return result;\n}\n\n/**\n * Split a run in two at a character offset, for an insertion between the\n * halves. A zero-width child sitting exactly at the offset goes with the\n * half named by `childrenAtOffset`: text inserted before an anchor that\n * follows a page break lands after the break (\"before\"), text inserted\n * after an anchor that precedes a break lands before it (\"after\").\n *\n * The original run is replaced in its parent by the halves that have any\n * content; the returned index is where the inserted node(s) should go.\n */\nexport function splitRunAtOffset(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n offset: number,\n childrenAtOffset: \"before\" | \"after\" = \"before\",\n): { parentChildren: FXPNode[]; index: number; before?: FXPNode; after?: FXPNode } {\n const parentChildren = findRunParentChildren(paragraphNode, runNode);\n if (!parentChildren) {\n throw new Error(\"Run not found in paragraph children or hyperlink children\");\n }\n const runIndex = parentChildren.indexOf(runNode);\n const parts = partitionContent(readRunContent(runNode), offset, offset, childrenAtOffset);\n\n const newRuns: FXPNode[] = [];\n const result: { parentChildren: FXPNode[]; index: number; before?: FXPNode; after?: FXPNode } = {\n parentChildren,\n index: runIndex,\n };\n if (hasContent(parts.before)) {\n result.before = createRunFromContent(parts.before);\n newRuns.push(result.before);\n result.index = runIndex + 1;\n }\n if (hasContent(parts.after)) {\n result.after = createRunFromContent(parts.after);\n newRuns.push(result.after);\n }\n parentChildren.splice(runIndex, 1, ...newRuns);\n return result;\n}\n\n/**\n * Split runs across multiple ResolvedRunRanges to isolate a text match.\n *\n * For a match that spans multiple runs, this:\n * 1. Splits the first run at the match start (if not start-aligned)\n * 2. Keeps middle runs intact (they're fully covered)\n * 3. Splits the last run at the match end (if not end-aligned)\n *\n * @returns Array of FXP run nodes that together contain exactly the matched text\n */\nexport function splitRunsForMatch(\n paragraphNode: FXPNode,\n ranges: ResolvedRunRange[],\n): FXPNode[] {\n if (ranges.length === 0) return [];\n\n if (ranges.length === 1) {\n const result = splitRun(paragraphNode, ranges[0]!);\n return [result.matched];\n }\n\n // Multi-run match: split from last to first to preserve indices\n const matchedRuns: FXPNode[] = [];\n\n // Process in reverse order to avoid index invalidation\n // But collect results in forward order\n const results: { index: number; split: SplitResult }[] = [];\n\n // Split last run first (if needed)\n const lastRange = ranges[ranges.length - 1]!;\n if (!lastRange.isEndAligned) {\n const split = splitRun(paragraphNode, lastRange);\n results.push({ index: ranges.length - 1, split });\n }\n\n // Split first run (if needed) — must be done after last to avoid index issues\n // Actually, since we're working on different runs, order doesn't matter\n // as long as we split from right to left in the children array\n const firstRange = ranges[0]!;\n if (!firstRange.isStartAligned) {\n const split = splitRun(paragraphNode, firstRange);\n results.push({ index: 0, split });\n }\n\n // Now collect all matched runs in order\n // After splitting, we need to re-find the runs\n for (let i = 0; i < ranges.length; i++) {\n const range = ranges[i]!;\n const result = results.find((r) => r.index === i);\n\n if (result) {\n matchedRuns.push(result.split.matched);\n } else {\n // Middle run or already-aligned run: use original xmlRef\n // Check the run still exists in its parent (splits may have shifted)\n const parentChildren = findRunParentChildren(paragraphNode, range.run.xmlRef);\n if (parentChildren && parentChildren.includes(range.run.xmlRef)) {\n matchedRuns.push(range.run.xmlRef);\n }\n }\n }\n\n return matchedRuns;\n}\n","/**\n * Revision tracking helpers for OOXML tracked changes.\n *\n * Provides:\n * - RevisionContext: holds the auto-incrementing ID counter, author, date\n * - scanMaxRevisionId: finds the highest w:id in existing revisions\n * - createElement helpers for w:ins, w:del, w:rPrChange, w:pPrChange\n * - convertRunToDeleted: renames w:t → w:delText inside a run\n */\n\nimport type { FXPNode, NormalizedDocument, PatchOptions } from \"../types.js\";\nimport {\n getChildren,\n getTagName,\n getAttr,\n setAttr,\n createElement,\n cloneNode,\n escapeXmlText,\n needsSpacePreserve,\n} from \"../xml/helpers.js\";\n\n// =============================================================================\n// RevisionContext\n// =============================================================================\n\n/**\n * Context object threaded through patch execution when trackRevisions is true.\n * Holds the auto-incrementing revision ID counter, author, and date.\n */\nexport interface RevisionContext {\n /** Next revision ID to assign. */\n nextId: number;\n /** Author for revision marks. */\n author: string;\n /** ISO 8601 date string for revision marks. */\n date: string;\n}\n\n/**\n * Create a RevisionContext from PatchOptions.\n * Scans the document for existing revision IDs to avoid collisions.\n */\nexport function createRevisionContext(\n doc: NormalizedDocument,\n options: PatchOptions,\n): RevisionContext {\n const maxId = scanMaxRevisionId(doc);\n return {\n nextId: maxId + 1,\n author: options.revisionAuthor ?? \"docxtor\",\n date: options.revisionDate ?? new Date().toISOString(),\n };\n}\n\n/**\n * Get the next revision ID and increment the counter.\n */\nexport function nextRevisionId(ctx: RevisionContext): string {\n const id = String(ctx.nextId);\n ctx.nextId++;\n return id;\n}\n\n// =============================================================================\n// Scan for existing revision IDs\n// =============================================================================\n\n/** Tag names that carry w:id for revision tracking. */\nconst REVISION_TAGS = new Set([\n \"w:ins\",\n \"w:del\",\n \"w:rPrChange\",\n \"w:pPrChange\",\n \"w:trPrChange\",\n \"w:tblPrChange\",\n \"w:sectPrChange\",\n \"w:cellIns\",\n \"w:cellDel\",\n \"w:cellMerge\",\n]);\n\n/**\n * Scan all XML parts in the document for the highest w:id value\n * on revision-related elements.\n *\n * Returns 0 if no existing revisions are found.\n */\nexport function scanMaxRevisionId(doc: NormalizedDocument): number {\n let maxId = 0;\n\n // Scan document.xml\n maxId = Math.max(maxId, scanNodesForMaxId(doc.xmlParts.document));\n\n // Scan headers\n for (const fxp of doc.xmlParts.headers.values()) {\n maxId = Math.max(maxId, scanNodesForMaxId(fxp));\n }\n\n // Scan footers\n for (const fxp of doc.xmlParts.footers.values()) {\n maxId = Math.max(maxId, scanNodesForMaxId(fxp));\n }\n\n // Scan the other revision-bearing parts so new ids never collide\n for (const key of [\"footnotes\", \"endnotes\", \"comments\"] as const) {\n const fxp = doc.xmlParts[key];\n if (fxp) maxId = Math.max(maxId, scanNodesForMaxId(fxp));\n }\n\n return maxId;\n}\n\n/**\n * Recursively scan an FXP node array for revision w:id values.\n */\nfunction scanNodesForMaxId(nodes: FXPNode[]): number {\n let maxId = 0;\n\n for (const node of nodes) {\n const tag = getTagName(node);\n if (tag && REVISION_TAGS.has(tag)) {\n const idStr = getAttr(node, \"w:id\");\n if (idStr) {\n const id = parseInt(idStr, 10);\n if (!isNaN(id) && id > maxId) {\n maxId = id;\n }\n }\n }\n\n // Recurse into children\n if (tag) {\n const children = getChildren(node);\n if (children.length > 0) {\n maxId = Math.max(maxId, scanNodesForMaxId(children));\n }\n }\n }\n\n return maxId;\n}\n\n// =============================================================================\n// Revision element creation helpers\n// =============================================================================\n\n/**\n * Create a w:ins element wrapping child nodes.\n */\nexport function createInsElement(\n ctx: RevisionContext,\n children: FXPNode[],\n): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:ins\", children, {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:del element wrapping child nodes.\n */\nexport function createDelElement(\n ctx: RevisionContext,\n children: FXPNode[],\n): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:del\", children, {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:rPrChange element containing the old w:rPr snapshot.\n */\nexport function createRPrChange(\n ctx: RevisionContext,\n oldRPr: FXPNode,\n): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:rPrChange\", [cloneNode(oldRPr)], {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:pPrChange element containing the old w:pPr snapshot.\n */\nexport function createPPrChange(\n ctx: RevisionContext,\n oldPPr: FXPNode,\n): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:pPrChange\", [cloneNode(oldPPr)], {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:ins element for table row properties (goes inside w:trPr).\n * This is an empty self-closing element with revision attributes.\n */\nexport function createTrPrIns(ctx: RevisionContext): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:ins\", [], {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n/**\n * Create a w:del element for table row properties (goes inside w:trPr).\n */\nexport function createTrPrDel(ctx: RevisionContext): FXPNode {\n const id = nextRevisionId(ctx);\n return createElement(\"w:del\", [], {\n \"w:id\": id,\n \"w:author\": ctx.author,\n \"w:date\": ctx.date,\n });\n}\n\n// =============================================================================\n// Run conversion helpers\n// =============================================================================\n\n/**\n * Convert a run's w:t elements to w:delText for use inside w:del.\n *\n * OOXML requires deleted text to use w:delText instead of w:t.\n * This mutates the run node in-place.\n */\nexport function convertRunToDeleted(runNode: FXPNode): void {\n const children = getChildren(runNode);\n for (let i = 0; i < children.length; i++) {\n const child = children[i]!;\n if (\"w:t\" in child) {\n // Replace the w:t with a w:delText that has the same children and attributes\n const tChildren = child[\"w:t\"] as FXPNode[];\n const attrs = child[\":@\"];\n const newNode: FXPNode = { \"w:delText\": tChildren };\n if (attrs) {\n newNode[\":@\"] = attrs;\n }\n children[i] = newNode;\n } else if (\"w:instrText\" in child) {\n // Field instructions inside a deleted run use w:delInstrText\n const newNode: FXPNode = { \"w:delInstrText\": child[\"w:instrText\"] as FXPNode[] };\n if (child[\":@\"]) newNode[\":@\"] = child[\":@\"];\n children[i] = newNode;\n }\n }\n}\n\n/**\n * Create a new w:r run node with the given text and optional formatting,\n * suitable for use inside w:ins. Handles newlines via w:br.\n */\nexport function createRevisionRun(\n text: string,\n rPrNode: FXPNode | undefined,\n): FXPNode {\n const children: FXPNode[] = [];\n\n // Clone formatting if present\n if (rPrNode) {\n children.push(cloneNode(rPrNode));\n }\n\n if (text.includes(\"\\n\")) {\n // Multi-line: interleave w:t and w:br\n const segments = text.split(\"\\n\");\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n if (seg.length > 0 || i === 0) {\n const wt: FXPNode = {\n \"w:t\": [{ \"#text\": escapeXmlText(seg) } as unknown as FXPNode],\n };\n if (needsSpacePreserve(seg)) {\n setAttr(wt, \"xml:space\", \"preserve\");\n }\n children.push(wt);\n }\n if (i < segments.length - 1) {\n children.push({ \"w:br\": [] as FXPNode[] });\n }\n }\n } else {\n // Single line\n const wt: FXPNode = {\n \"w:t\": [{ \"#text\": escapeXmlText(text) } as unknown as FXPNode],\n };\n if (needsSpacePreserve(text)) {\n setAttr(wt, \"xml:space\", \"preserve\");\n }\n children.push(wt);\n }\n\n return { \"w:r\": children };\n}\n","/**\n * Markdown formatting parser and stripper.\n *\n * Parses GFM inline formatting markers into structured segments\n * and provides a utility to strip markers for plain-text matching.\n *\n * Supported markers:\n * - **bold**\n * - _italic_\n * - ~~strikethrough~~\n * - <u>underline</u>\n * - Nesting: **_bold+italic_**, _**bold+italic**_\n *\n * Escapes: a backslash before `*`, `_`, `~`, `<` or `\\` writes that\n * character literally (`\\_` → `_`). Any other backslash is kept as-is.\n *\n * Underscore rules: a `_` is an italic marker only when it stands alone\n * (not next to another `_`) at a word boundary. Runs of underscores such as\n * signature blanks (`Name: ______`) and intra-word underscores\n * (`snake_case`) are literal text.\n *\n * Every entry point takes a `markers` flag; with `false` the text is\n * returned verbatim — no markers, no escapes.\n */\n\n/** Characters a backslash may escape when markers are enabled. */\nconst ESCAPABLE = new Set([\"*\", \"_\", \"~\", \"<\", \"\\\\\"]);\n\n/**\n * A segment of text with its inline formatting flags.\n */\nexport interface FormattedSegment {\n text: string;\n bold?: boolean;\n italic?: boolean;\n strikethrough?: boolean;\n underline?: boolean;\n}\n\n/**\n * Strip all GFM inline formatting markers from a string,\n * returning the plain text content.\n *\n * Used to normalize `find` / `anchor` fields in patches so they\n * match against the plain paragraph text.\n */\nexport function stripFormattingMarkers(text: string, markers: boolean = true): string {\n if (!markers) return text;\n // Process by walking the string and removing marker characters.\n // We use the same parser and concatenate the text segments.\n const segments = parseFormattedText(text);\n return segments.map((s) => s.text).join(\"\");\n}\n\n/**\n * Parse a GFM-formatted string into an array of segments, each\n * carrying the formatting flags that were active when the text\n * appeared.\n *\n * Handles:\n * - **bold**\n * - _italic_ (underscore only, to avoid conflicts with intra-word asterisks)\n * - ~~strikethrough~~\n * - Nesting in any order: **_text_**, _**text**_\n *\n * Plain text without markers returns a single segment with no flags.\n * With `markers: false` the input is returned as one verbatim segment.\n */\nexport function parseFormattedText(input: string, markers: boolean = true): FormattedSegment[] {\n if (!markers) return input.length > 0 ? [{ text: input }] : [];\n\n const segments: FormattedSegment[] = [];\n let pos = 0;\n let currentText = \"\";\n let bold = false;\n let italic = false;\n let strikethrough = false;\n let underline = false;\n\n function flush(): void {\n if (currentText.length > 0) {\n const seg: FormattedSegment = { text: currentText };\n if (bold) seg.bold = true;\n if (italic) seg.italic = true;\n if (strikethrough) seg.strikethrough = true;\n if (underline) seg.underline = true;\n segments.push(seg);\n currentText = \"\";\n }\n }\n\n while (pos < input.length) {\n // Backslash escape: the next character is literal\n if (input[pos] === \"\\\\\" && pos + 1 < input.length && ESCAPABLE.has(input[pos + 1]!)) {\n currentText += input[pos + 1];\n pos += 2;\n continue;\n }\n\n // Check for <u> (underline open)\n if (input.slice(pos, pos + 3) === \"<u>\") {\n flush();\n underline = true;\n pos += 3;\n continue;\n }\n\n // Check for </u> (underline close)\n if (input.slice(pos, pos + 4) === \"</u>\") {\n flush();\n underline = false;\n pos += 4;\n continue;\n }\n\n // Check for ~~ (strikethrough toggle)\n if (input[pos] === \"~\" && input[pos + 1] === \"~\") {\n flush();\n strikethrough = !strikethrough;\n pos += 2;\n continue;\n }\n\n // Check for ** (bold toggle)\n if (input[pos] === \"*\" && input[pos + 1] === \"*\") {\n flush();\n bold = !bold;\n pos += 2;\n continue;\n }\n\n // Check for _ (italic toggle)\n if (input[pos] === \"_\" && isItalicMarker(input, pos, italic)) {\n flush();\n italic = !italic;\n pos += 1;\n continue;\n }\n\n currentText += input[pos];\n pos++;\n }\n\n flush();\n\n // Merge adjacent segments with identical formatting\n return mergeSegments(segments);\n}\n\n/**\n * Decide whether the `_` at `pos` is an italic marker.\n *\n * - Never when it touches another `_` (underscore blanks, `__init__`).\n * - Opening: at the start of the string or after a non-word character, and\n * followed by something that is not whitespace.\n * - Closing (italic already open): preceded by something that is not\n * whitespace.\n * - Otherwise only at the end of the string or next to a non-word\n * character — the word-boundary rule that keeps `snake_case` literal.\n */\nfunction isItalicMarker(input: string, pos: number, italicOpen: boolean): boolean {\n const prevChar = pos > 0 ? input[pos - 1]! : \"\";\n const nextChar = pos + 1 < input.length ? input[pos + 1]! : \"\";\n if (prevChar === \"_\" || nextChar === \"_\") return false;\n\n if (italicOpen) {\n return prevChar !== \"\" && !/\\s/.test(prevChar);\n }\n const opensAtBoundary = pos === 0 || /\\W/.test(prevChar);\n if (opensAtBoundary) {\n return nextChar !== \"\" && !/\\s/.test(nextChar);\n }\n return pos === input.length - 1 || /\\W/.test(nextChar);\n}\n\n/**\n * Merge adjacent segments with identical formatting flags.\n */\nfunction mergeSegments(segments: FormattedSegment[]): FormattedSegment[] {\n if (segments.length <= 1) return segments;\n\n const merged: FormattedSegment[] = [segments[0]!];\n\n for (let i = 1; i < segments.length; i++) {\n const prev = merged[merged.length - 1]!;\n const curr = segments[i]!;\n\n if (\n !!prev.bold === !!curr.bold &&\n !!prev.italic === !!curr.italic &&\n !!prev.strikethrough === !!curr.strikethrough &&\n !!prev.underline === !!curr.underline\n ) {\n prev.text += curr.text;\n } else {\n merged.push(curr);\n }\n }\n\n return merged;\n}\n\n/**\n * Check whether a string contains any GFM formatting markers or escapes,\n * i.e. whether {@link parseFormattedText} would change it.\n */\nexport function hasFormattingMarkers(text: string): boolean {\n return stripFormattingMarkers(text) !== text;\n}\n","/**\n * Text operations: replace_text, insert_text, delete_text.\n *\n * These operate on a NormalizedParagraph by:\n * 1. Finding the target text in the normalized paragraph text\n * 2. Mapping to run(s) via the text-to-runs resolver\n * 3. Splitting runs if the match doesn't align with run boundaries\n * 4. Mutating the FXP w:t text nodes directly\n *\n * After execution, the FXP object reflects the edit, and the\n * paragraph should be re-normalized to update mappings.\n *\n * When a RevisionContext is provided, operations produce OOXML tracked\n * changes (w:ins, w:del) instead of direct mutations.\n */\n\nimport type {\n NormalizedParagraph,\n ReplaceTextPatch,\n InsertTextPatch,\n DeleteTextPatch,\n FXPNode,\n} from \"../types.js\";\nimport { findOccurrence, resolveRunRanges } from \"../mapping/text-to-runs.js\";\nimport {\n splitRun,\n splitRunsForMatch,\n splitRunAtOffset,\n findRunParentChildren,\n} from \"./run-splitter.js\";\nimport { readRunContent, writeRunContent } from \"./run-content.js\";\nimport {\n getChildren,\n setTextContent,\n removeChild,\n getRunText,\n findTextNode,\n getTagName,\n setAttr,\n escapeXmlText,\n needsSpacePreserve,\n findChild,\n cloneNode,\n createElement,\n getAttr,\n} from \"../xml/helpers.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport {\n createInsElement,\n createDelElement,\n convertRunToDeleted,\n createRevisionRun,\n nextRevisionId,\n} from \"./revision.js\";\nimport {\n stripFormattingMarkers,\n parseFormattedText,\n type FormattedSegment,\n} from \"./markdown-parser.js\";\n\n/**\n * Find the parent node that directly contains a run, and remove the run.\n * Handles runs that are direct children of w:p or nested inside\n * w:hyperlink, w:ins, or w:del containers.\n */\nfunction removeRunFromParagraph(paragraphNode: FXPNode, runNode: FXPNode): void {\n // Try direct removal first\n if (removeChild(paragraphNode, runNode)) return;\n\n // Try inside container elements (w:hyperlink, w:ins, w:del)\n const children = getChildren(paragraphNode);\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:hyperlink\" || tag === \"w:ins\" || tag === \"w:del\") {\n if (removeChild(child, runNode)) return;\n // Check nested containers\n const containerChildren = getChildren(child);\n for (const nested of containerChildren) {\n const nestedTag = getTagName(nested);\n if (nestedTag === \"w:hyperlink\" || nestedTag === \"w:ins\" || nestedTag === \"w:del\") {\n if (removeChild(nested, runNode)) return;\n }\n }\n }\n }\n}\n\n/**\n * Find the container element (w:ins, w:del, w:hyperlink) that directly holds\n * a run, or undefined if the run is a direct child of the paragraph.\n */\nfunction findRunContainer(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n): FXPNode | undefined {\n const children = getChildren(paragraphNode);\n if (children.includes(runNode)) return undefined;\n\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:hyperlink\" || tag === \"w:ins\" || tag === \"w:del\") {\n const containerChildren = getChildren(child);\n if (containerChildren.includes(runNode)) return child;\n for (const nested of containerChildren) {\n const nestedTag = getTagName(nested);\n if (nestedTag === \"w:hyperlink\" || nestedTag === \"w:ins\" || nestedTag === \"w:del\") {\n if (getChildren(nested).includes(runNode)) return nested;\n }\n }\n }\n }\n return undefined;\n}\n\n// =============================================================================\n// Replace text\n// =============================================================================\n\n/**\n * Execute a replace_text patch on a paragraph.\n *\n * Finds the target text, maps to runs, splits if needed, and replaces.\n * When revCtx is provided, wraps old text in w:del and new text in w:ins.\n */\nexport function executeReplaceText(\n paragraph: NormalizedParagraph,\n patch: ReplaceTextPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n const occurrence = patch.occurrence ?? 1;\n const plainFind = stripFormattingMarkers(patch.find, markers);\n const matchStart = findOccurrence(paragraph.text, plainFind, occurrence);\n\n if (matchStart === -1) {\n throw new Error(\n `Text \"${plainFind}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n const matchEnd = matchStart + plainFind.length;\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchEnd);\n\n if (ranges.length === 0) {\n throw new Error(\n `No runs found for text range [${matchStart}, ${matchEnd}] in paragraph \"${paragraph.id}\"`,\n );\n }\n\n if (revCtx) {\n executeReplaceTextRevision(paragraph, patch, ranges, revCtx, markers);\n } else {\n executeReplaceTextDirect(paragraph, patch, ranges, markers);\n }\n}\n\n/**\n * Direct (non-revision) replacement.\n *\n * If the replacement text contains GFM formatting markers (**bold**, _italic_,\n * ~~strike~~), it is parsed into segments and each segment becomes a separate\n * run with the appropriate w:rPr formatting, inheriting any existing formatting\n * from the matched run.\n */\nfunction executeReplaceTextDirect(\n paragraph: NormalizedParagraph,\n patch: ReplaceTextPatch,\n ranges: ReturnType<typeof resolveRunRanges>,\n markers: boolean,\n): void {\n const segments = parseFormattedText(patch.replace, markers);\n const hasFormatting = segments.some((s) => s.bold || s.italic || s.strikethrough || s.underline);\n const plainText = stripFormattingMarkers(patch.replace, markers);\n\n if (ranges.length === 1) {\n const range = ranges[0]!;\n const splitResult = splitRun(paragraph.xmlRef, range);\n\n if (!hasFormatting) {\n // Simple plain text replacement\n setRunText(splitResult.matched, plainText);\n } else {\n // Replace the matched run with formatted runs\n replaceRunWithFormattedRuns(paragraph.xmlRef, splitResult.matched, segments);\n }\n } else {\n const matchedRuns = splitRunsForMatch(paragraph.xmlRef, ranges);\n\n if (matchedRuns.length === 0) {\n throw new Error(\n `Failed to isolate matched runs in paragraph \"${paragraph.id}\"`,\n );\n }\n\n if (!hasFormatting) {\n setRunText(matchedRuns[0]!, plainText);\n } else {\n replaceRunWithFormattedRuns(paragraph.xmlRef, matchedRuns[0]!, segments);\n }\n\n // Remove the text of the other matched runs. A run that also holds a\n // zero-width child (a page break inside the match) keeps that child.\n for (let i = 1; i < matchedRuns.length; i++) {\n const content = readRunContent(matchedRuns[i]!);\n if (content.anchored.length > 0) {\n writeRunContent(matchedRuns[i]!, { ...content, text: \"\" });\n } else {\n removeRunFromParagraph(paragraph.xmlRef, matchedRuns[i]!);\n }\n }\n }\n}\n\n/**\n * Revision-tracked replacement:\n * 1. Split runs to isolate matched text\n * 2. Wrap matched runs in w:del (converting w:t → w:delText)\n * 3. Create new run(s) with replacement text wrapped in w:ins\n * 4. Insert w:ins immediately after w:del\n */\nfunction executeReplaceTextRevision(\n paragraph: NormalizedParagraph,\n patch: ReplaceTextPatch,\n ranges: ReturnType<typeof resolveRunRanges>,\n revCtx: RevisionContext,\n markers: boolean,\n): void {\n // Split runs to isolate the matched text\n let matchedRuns: FXPNode[];\n if (ranges.length === 1) {\n const range = ranges[0]!;\n const splitResult = splitRun(paragraph.xmlRef, range);\n matchedRuns = [splitResult.matched];\n } else {\n matchedRuns = splitRunsForMatch(paragraph.xmlRef, ranges);\n }\n\n if (matchedRuns.length === 0) {\n throw new Error(\n `Failed to isolate matched runs in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Get formatting from the first matched run for the replacement\n const rPrNode = findChild(matchedRuns[0]!, \"w:rPr\");\n\n // Create the replacement run(s); wrapped in w:ins below\n const segments = parseFormattedText(patch.replace, markers);\n const hasFormatting = segments.some((s) => s.bold || s.italic || s.strikethrough || s.underline);\n const plainText = stripFormattingMarkers(patch.replace, markers);\n\n let newRuns: FXPNode[] = [];\n if (segments.length > 0 && plainText.length > 0) {\n newRuns = hasFormatting\n ? createFormattedRuns(segments, rPrNode)\n : [createRevisionRun(plainText, rPrNode)];\n }\n // Mark the matched runs deleted (or drop them, where they were an\n // unaccepted insertion) and put the replacement where the match began.\n const place = detachMatchedRuns(paragraph.xmlRef, matchedRuns, revCtx);\n if (newRuns.length === 0) return;\n\n if (place.container) {\n // The match began inside an unaccepted insertion: the replacement must\n // land exactly where it was, not after the surrounding w:ins.\n insertIntoInsertion(paragraph.xmlRef, place.container, place.index, newRuns, revCtx);\n } else {\n place.parentChildren.splice(place.index, 0, createInsElement(revCtx, newRuns));\n }\n}\n\n/**\n * Where the content that replaces a match should go, after the match has\n * been detached: a position in a parent's children, or, when the match\n * began inside an unaccepted insertion that still holds other runs, a\n * position inside that `w:ins` container.\n */\ninterface DetachPlacement {\n parentChildren: FXPNode[];\n index: number;\n container?: FXPNode;\n}\n\n/**\n * Detach matched runs for a tracked edit, run by run, in whichever parent\n * each one lives.\n *\n * - A run inside an unaccepted `w:ins` is removed outright: it was never\n * part of the accepted document, so there is nothing to mark deleted. A\n * container left empty is removed too.\n * - Any other run is replaced by a `w:del` holding a copy of it; consecutive\n * runs in the same parent share one `w:del`.\n *\n * A match may span both kinds — text that starts inside another author's\n * insertion and continues in plain text — which is why the parent of the\n * first run cannot stand in for the others.\n */\nfunction detachMatchedRuns(\n paragraphNode: FXPNode,\n matchedRuns: FXPNode[],\n revCtx: RevisionContext,\n): DetachPlacement {\n let placement: DetachPlacement | undefined;\n const groups: { parent: FXPNode[]; index: number; runs: FXPNode[] }[] = [];\n\n for (const run of matchedRuns) {\n const parent = findRunParentChildren(paragraphNode, run);\n if (!parent) throw new Error(\"Matched run not found in paragraph\");\n const container = findRunContainer(paragraphNode, run);\n const insideIns = container != null && getTagName(container) === \"w:ins\";\n const index = parent.indexOf(run);\n parent.splice(index, 1);\n\n if (insideIns) {\n let containerKept = true;\n let containerParent: FXPNode[] | undefined;\n let containerIndex = -1;\n if (parent.length === 0) {\n containerParent = findRunParentChildren(paragraphNode, container);\n if (containerParent) {\n containerIndex = containerParent.indexOf(container);\n containerParent.splice(containerIndex, 1);\n containerKept = false;\n }\n }\n if (!placement) {\n placement = containerKept\n ? { parentChildren: parent, index, container }\n : { parentChildren: containerParent!, index: containerIndex };\n }\n continue;\n }\n\n const last = groups[groups.length - 1];\n if (last && last.parent === parent && last.index === index) {\n last.runs.push(run);\n } else {\n groups.push({ parent, index, runs: [run] });\n }\n }\n\n // Insert the w:del elements last group first, so earlier indices in the\n // same parent stay valid.\n for (let g = groups.length - 1; g >= 0; g--) {\n const group = groups[g]!;\n const deletedRuns = group.runs.map((run) => {\n const cloned = cloneNode(run);\n convertRunToDeleted(cloned);\n return cloned;\n });\n group.parent.splice(group.index, 0, createDelElement(revCtx, deletedRuns));\n }\n\n if (!placement) {\n // The match began in plain text: the replacement follows its w:del.\n const first = groups[0]!;\n placement = { parentChildren: first.parent, index: first.index + 1 };\n }\n return placement;\n}\n\n/**\n * Put `newRuns` at `index` among the children of an existing `w:ins`.\n *\n * - Same author: the runs join that insertion in place.\n * - Another author: the insertion is split around `index`; the new runs go\n * between the halves in their own `w:ins`, so attribution and order are\n * both kept. An empty half is dropped (and the whole container, if it\n * ends up empty).\n */\nfunction insertIntoInsertion(\n paragraphNode: FXPNode,\n container: FXPNode,\n index: number,\n newRuns: FXPNode[],\n revCtx: RevisionContext,\n): void {\n const containerChildren = getChildren(container);\n const containerParent = findRunParentChildren(paragraphNode, container);\n if (!containerParent) {\n throw new Error(\"Insertion container not found in paragraph\");\n }\n const containerIdx = containerParent.indexOf(container);\n\n if (newRuns.length === 0) {\n if (containerChildren.length === 0) containerParent.splice(containerIdx, 1);\n return;\n }\n\n if (getAttr(container, \"w:author\") === revCtx.author) {\n containerChildren.splice(index, 0, ...newRuns);\n return;\n }\n\n const tail = containerChildren.splice(index);\n const replacement: FXPNode[] = [];\n if (containerChildren.length > 0) replacement.push(container);\n replacement.push(createInsElement(revCtx, newRuns));\n if (tail.length > 0) {\n const second = createElement(\"w:ins\", tail, {\n \"w:id\": nextRevisionId(revCtx),\n \"w:author\": getAttr(container, \"w:author\") ?? \"\",\n \"w:date\": getAttr(container, \"w:date\") ?? \"\",\n });\n replacement.push(second);\n }\n containerParent.splice(containerIdx, 1, ...replacement);\n}\n\n// =============================================================================\n// Insert text\n// =============================================================================\n\n/**\n * Execute an insert_text patch on a paragraph.\n *\n * Strategy: find the insertion offset in the normalized text, then find\n * which run contains that offset. Insert the text directly into that run's\n * w:t node at the correct position.\n *\n * With revision tracking, the inserted text goes into a new run wrapped\n * in w:ins, and the target run is split if needed.\n */\nexport function executeInsertText(\n paragraph: NormalizedParagraph,\n patch: InsertTextPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n const occurrence = patch.occurrence ?? 1;\n const plainAnchor = stripFormattingMarkers(patch.anchor, markers);\n const anchorStart = findOccurrence(paragraph.text, plainAnchor, occurrence);\n\n if (anchorStart === -1) {\n throw new Error(\n `Anchor text \"${plainAnchor}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n // Determine the character offset where we want to insert\n const insertOffset =\n patch.position === \"before\"\n ? anchorStart\n : anchorStart + plainAnchor.length;\n\n if (revCtx) {\n executeInsertTextRevision(paragraph, patch, insertOffset, revCtx, markers);\n } else {\n executeInsertTextDirect(paragraph, patch, insertOffset, markers);\n }\n}\n\n/**\n * Direct (non-revision) insertion — modifies the run's text in-place,\n * or creates multiple formatted runs if the text contains GFM markers.\n */\nfunction executeInsertTextDirect(\n paragraph: NormalizedParagraph,\n patch: InsertTextPatch,\n insertOffset: number,\n markers: boolean,\n): void {\n let targetRun: typeof paragraph.runs[number] | undefined;\n\n for (const run of paragraph.runs) {\n if (insertOffset >= run.startOffset && insertOffset <= run.endOffset) {\n targetRun = run;\n break;\n }\n }\n\n if (!targetRun) {\n const lastRun = paragraph.runs[paragraph.runs.length - 1];\n if (!lastRun) {\n throw new Error(\n `No runs found in paragraph \"${paragraph.id}\" for insertion`,\n );\n }\n targetRun = lastRun;\n }\n\n const segments = parseFormattedText(patch.text, markers);\n const hasFormatting = segments.some((s) => s.bold || s.italic || s.strikethrough || s.underline);\n\n if (!hasFormatting) {\n // Simple plain text insertion\n const plainText = stripFormattingMarkers(patch.text, markers);\n const localInsertOffset = insertOffset - targetRun.startOffset;\n const currentText = getRunText(targetRun.xmlRef);\n const newText =\n currentText.slice(0, localInsertOffset) +\n plainText +\n currentText.slice(localInsertOffset);\n // A zero-width child at the insert point (a page break right after the\n // anchor) stays next to the anchor: before the new text when inserting\n // before the anchor, after it when inserting after the anchor.\n const side = sideOfChildrenAtInsert(patch);\n setRunText(targetRun.xmlRef, newText, (offset) =>\n offset < localInsertOffset || (offset === localInsertOffset && side === \"before\")\n ? offset\n : offset + plainText.length,\n );\n } else {\n // Formatted insertion: split the target run at the insert point and\n // insert formatted runs between the halves\n const localInsertOffset = insertOffset - targetRun.startOffset;\n const rPrNode = findChild(targetRun.xmlRef, \"w:rPr\");\n const formattedRuns = createFormattedRuns(segments, rPrNode);\n const split = splitRunAtOffset(\n paragraph.xmlRef,\n targetRun.xmlRef,\n localInsertOffset,\n sideOfChildrenAtInsert(patch),\n );\n split.parentChildren.splice(split.index, 0, ...formattedRuns);\n }\n}\n\n/**\n * Revision-tracked insertion:\n * 1. Find the target run containing the insertion offset\n * 2. Split the run at the insertion point if needed\n * 3. Create a new w:r wrapped in w:ins\n * 4. Insert between the split halves (or at boundary)\n */\nfunction executeInsertTextRevision(\n paragraph: NormalizedParagraph,\n patch: InsertTextPatch,\n insertOffset: number,\n revCtx: RevisionContext,\n markers: boolean,\n): void {\n let targetRun: typeof paragraph.runs[number] | undefined;\n\n for (const run of paragraph.runs) {\n if (insertOffset >= run.startOffset && insertOffset <= run.endOffset) {\n targetRun = run;\n break;\n }\n }\n\n if (!targetRun) {\n const lastRun = paragraph.runs[paragraph.runs.length - 1];\n if (!lastRun) {\n throw new Error(\n `No runs found in paragraph \"${paragraph.id}\" for insertion`,\n );\n }\n targetRun = lastRun;\n }\n\n const localInsertOffset = insertOffset - targetRun.startOffset;\n\n // Get formatting from the target run to clone onto the new run\n const rPrNode = findChild(targetRun.xmlRef, \"w:rPr\");\n\n // Create the new run(s) with inserted text wrapped in w:ins\n const segments = parseFormattedText(patch.text, markers);\n const hasFormatting = segments.some((s) => s.bold || s.italic || s.strikethrough || s.underline);\n\n let insRuns: FXPNode[];\n if (hasFormatting) {\n insRuns = createFormattedRuns(segments, rPrNode);\n } else {\n const plainText = stripFormattingMarkers(patch.text, markers);\n insRuns = [createRevisionRun(plainText, rPrNode)];\n }\n const insElement = createInsElement(revCtx, insRuns);\n\n // Find the parent children array containing the target run\n const parentChildren = findRunParentChildren(paragraph.xmlRef, targetRun.xmlRef);\n if (!parentChildren) {\n throw new Error(\"Target run not found in paragraph\");\n }\n\n const runIndex = parentChildren.indexOf(targetRun.xmlRef);\n\n // Determine if we need to split the run\n const atStart = localInsertOffset === 0;\n const atEnd = localInsertOffset === targetRun.text.length;\n\n if (atStart) {\n // Insert before the target run (no splitting needed)\n parentChildren.splice(runIndex, 0, insElement);\n } else if (atEnd) {\n // Insert after the target run (no splitting needed)\n parentChildren.splice(runIndex + 1, 0, insElement);\n } else {\n // Split the run at the insertion point; zero-width children keep their side\n const split = splitRunAtOffset(\n paragraph.xmlRef,\n targetRun.xmlRef,\n localInsertOffset,\n sideOfChildrenAtInsert(patch),\n );\n split.parentChildren.splice(split.index, 0, insElement);\n }\n}\n\n/**\n * Which side of inserted text a zero-width child at the insert point stays\n * on. Inserting after an anchor keeps a break that follows the anchor after\n * the new text; inserting before an anchor keeps a break that precedes the\n * anchor before it.\n */\nfunction sideOfChildrenAtInsert(patch: InsertTextPatch): \"before\" | \"after\" {\n return patch.position === \"after\" ? \"after\" : \"before\";\n}\n\n/**\n * Create a w:r run node with text and optional formatting (for splitting).\n *\n * Handles \\n in text by emitting w:br elements (matching the behavior\n * of getRunText which converts w:br → \\n). Without this, splitting a\n * run containing line breaks would produce literal \\n in w:t instead\n * of proper w:br elements.\n */\nfunction createRunWithTextAndFormatting(\n text: string,\n rPrNode: FXPNode | undefined,\n): FXPNode {\n const children: FXPNode[] = [];\n if (rPrNode) {\n children.push(cloneNode(rPrNode));\n }\n\n if (text.includes(\"\\n\")) {\n // Multi-line: interleave w:t and w:br\n const segments = text.split(\"\\n\");\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n if (seg.length > 0 || i === 0) {\n const textNode = { \"#text\": escapeXmlText(seg) } as unknown as FXPNode;\n const wtNode: FXPNode = { \"w:t\": [textNode] };\n if (needsSpacePreserve(seg)) {\n setAttr(wtNode, \"xml:space\", \"preserve\");\n }\n children.push(wtNode);\n }\n if (i < segments.length - 1) {\n children.push({ \"w:br\": [] as FXPNode[] });\n }\n }\n } else {\n // Single line\n const textNode = { \"#text\": escapeXmlText(text) } as unknown as FXPNode;\n const wtNode: FXPNode = { \"w:t\": [textNode] };\n if (needsSpacePreserve(text)) {\n setAttr(wtNode, \"xml:space\", \"preserve\");\n }\n children.push(wtNode);\n }\n\n return { \"w:r\": children };\n}\n\n// =============================================================================\n// Delete text\n// =============================================================================\n\n/**\n * Execute a delete_text patch on a paragraph.\n *\n * Without revisions: equivalent to replace_text with an empty replacement.\n * With revisions: wraps matched runs in w:del (text stays in XML but is marked as deleted).\n */\nexport function executeDeleteText(\n paragraph: NormalizedParagraph,\n patch: DeleteTextPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n if (revCtx) {\n executeDeleteTextRevision(paragraph, patch, revCtx, markers);\n } else {\n executeReplaceText(\n paragraph,\n {\n type: \"replace_text\",\n target: patch.target,\n find: patch.find,\n replace: \"\",\n occurrence: patch.occurrence,\n },\n undefined,\n markers,\n );\n }\n}\n\n/**\n * Revision-tracked deletion:\n * 1. Split runs to isolate the matched text\n * 2. Wrap matched runs in w:del, converting w:t → w:delText\n * 3. Text stays in XML but is marked as deleted\n */\nfunction executeDeleteTextRevision(\n paragraph: NormalizedParagraph,\n patch: DeleteTextPatch,\n revCtx: RevisionContext,\n markers: boolean,\n): void {\n const occurrence = patch.occurrence ?? 1;\n const plainFind = stripFormattingMarkers(patch.find, markers);\n const matchStart = findOccurrence(paragraph.text, plainFind, occurrence);\n\n if (matchStart === -1) {\n throw new Error(\n `Text \"${plainFind}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n const matchEnd = matchStart + plainFind.length;\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchEnd);\n\n if (ranges.length === 0) {\n throw new Error(\n `No runs found for text range [${matchStart}, ${matchEnd}] in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Split runs to isolate the matched text\n let matchedRuns: FXPNode[];\n if (ranges.length === 1) {\n const range = ranges[0]!;\n const splitResult = splitRun(paragraph.xmlRef, range);\n matchedRuns = [splitResult.matched];\n } else {\n matchedRuns = splitRunsForMatch(paragraph.xmlRef, ranges);\n }\n\n if (matchedRuns.length === 0) {\n throw new Error(\n `Failed to isolate matched runs in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Mark each matched run deleted in its own parent; runs that were an\n // unaccepted insertion are simply removed.\n detachMatchedRuns(paragraph.xmlRef, matchedRuns, revCtx);\n}\n\n// =============================================================================\n// Formatted run creation from GFM markers\n// =============================================================================\n\n/**\n * Build an array of w:r FXP nodes from parsed formatted segments.\n *\n * Each segment becomes a run. Formatting flags from the segment (bold, italic,\n * strikethrough) are layered on top of any existing rPr inherited from the\n * base run.\n */\nexport function createFormattedRuns(\n segments: FormattedSegment[],\n baseRPr: FXPNode | undefined,\n): FXPNode[] {\n const runs: FXPNode[] = [];\n\n for (const seg of segments) {\n if (seg.text.length === 0) continue;\n\n const run = createRunWithTextAndFormatting(seg.text, baseRPr);\n\n // Layer GFM formatting on top\n if (seg.bold || seg.italic || seg.strikethrough || seg.underline) {\n let rPr = findChild(run, \"w:rPr\");\n if (!rPr) {\n rPr = createElement(\"w:rPr\", []);\n const children = getChildren(run);\n children.unshift(rPr);\n }\n const rPrChildren = getChildren(rPr);\n\n if (seg.bold && !findChild(rPr, \"w:b\")) {\n rPrChildren.push(createElement(\"w:b\", []));\n }\n if (seg.italic && !findChild(rPr, \"w:i\")) {\n rPrChildren.push(createElement(\"w:i\", []));\n }\n if (seg.strikethrough && !findChild(rPr, \"w:strike\")) {\n rPrChildren.push(createElement(\"w:strike\", []));\n }\n if (seg.underline && !findChild(rPr, \"w:u\")) {\n rPrChildren.push(createElement(\"w:u\", [], { \"w:val\": \"single\" }));\n }\n }\n\n runs.push(run);\n }\n\n return runs;\n}\n\n/**\n * Replace a single run node in a paragraph with multiple formatted runs.\n *\n * Finds the run in the paragraph's children (or nested containers) and\n * splices in the new runs.\n */\nfunction replaceRunWithFormattedRuns(\n paragraphNode: FXPNode,\n runNode: FXPNode,\n segments: FormattedSegment[],\n): void {\n const content = readRunContent(runNode);\n const newRuns = createFormattedRuns(segments, content.rPr);\n\n if (newRuns.length === 0) {\n // All segments were empty — just clear the run text\n setRunText(runNode, \"\");\n return;\n }\n\n // Zero-width children before the old text go in front of the first new\n // run's text; the rest follow the last run, so a page break survives.\n const leading = content.anchored.filter((a) => a.offset <= 0).map((a) => a.node);\n const trailing = content.anchored.filter((a) => a.offset > 0).map((a) => a.node);\n if (leading.length > 0) {\n const first = getChildren(newRuns[0]!);\n const at = first.length > 0 && \"w:rPr\" in first[0]! ? 1 : 0;\n first.splice(at, 0, ...leading);\n }\n if (trailing.length > 0) {\n getChildren(newRuns[newRuns.length - 1]!).push(...trailing);\n }\n\n // Find the run in parent's children and replace\n const parentChildren = findRunParentChildren(paragraphNode, runNode);\n if (parentChildren) {\n const idx = parentChildren.indexOf(runNode);\n if (idx !== -1) {\n parentChildren.splice(idx, 1, ...newRuns);\n return;\n }\n }\n\n // Fallback: just set text on the existing run (shouldn't happen)\n const plainText = segments.map((s) => s.text).join(\"\");\n setRunText(runNode, plainText);\n}\n\n// =============================================================================\n// Internal: run text manipulation\n// =============================================================================\n\n/**\n * Set the text content of a w:r run node.\n *\n * A run that holds exactly one w:t and nothing else of note has that text\n * node updated in place. Otherwise the run is rebuilt from its content:\n * `\\n` becomes a soft `w:br`, an old soft break never survives next to the\n * new text, and zero-width children (page and column breaks, tabs,\n * rendering hints) are kept. `remap` says where each such child goes in the\n * new text, given its old offset; without it, a child that preceded the old\n * text stays in front and every other one follows the new text.\n */\nfunction setRunText(\n runNode: FXPNode,\n text: string,\n remap?: (offset: number) => number,\n): void {\n const content = readRunContent(runNode);\n const wtNode = findTextNode(runNode);\n const simple =\n wtNode !== undefined &&\n content.anchored.length === 0 &&\n !text.includes(\"\\n\") &&\n !content.text.includes(\"\\n\") &&\n getChildren(runNode).filter((c) => \"w:t\" in c).length === 1;\n\n if (simple) {\n const tChildren = (wtNode as Record<string, unknown>)[\"w:t\"];\n if (Array.isArray(tChildren)) {\n const textNode = tChildren.find(\n (c: Record<string, unknown>) => \"#text\" in c,\n );\n if (textNode) {\n setTextContent(textNode as FXPNode, text);\n } else {\n tChildren.push({ \"#text\": escapeXmlText(text) });\n }\n }\n if (needsSpacePreserve(text)) {\n setAttr(wtNode, \"xml:space\", \"preserve\");\n }\n return;\n }\n\n const place = remap ?? ((offset: number) => (offset <= 0 ? 0 : text.length));\n writeRunContent(runNode, {\n rPr: content.rPr,\n text,\n anchored: content.anchored.map((a) => ({ node: a.node, offset: place(a.offset) })),\n });\n}\n","/**\n * Table operations: add_row, delete_row, replace_cell_text.\n *\n * Table edits work by:\n * - add_row: clone an existing row (preserving cell styles), replace text\n * - delete_row: remove the w:tr node from the w:tbl\n * - replace_cell_text: delegate to text-ops on the paragraph inside the cell\n */\n\nimport type {\n NormalizedTable,\n NormalizedParagraph,\n AddTableRowPatch,\n DeleteTableRowPatch,\n ReplaceCellTextPatch,\n FXPNode,\n} from \"../types.js\";\nimport {\n cloneNode,\n findChildren,\n getChildren,\n insertAfter,\n removeChild,\n findChild,\n createElement,\n escapeXmlText,\n needsSpacePreserve,\n} from \"../xml/helpers.js\";\nimport { executeReplaceText } from \"./text-ops.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport { createTrPrIns, createTrPrDel } from \"./revision.js\";\n\n/**\n * Execute an add_table_row patch.\n *\n * Clones an existing row to preserve styling, then replaces cell text.\n *\n * @returns the new w:tr node (so the caller can re-stamp paragraph ids\n * that were duplicated by cloning)\n */\nexport function executeAddTableRow(\n table: NormalizedTable,\n patch: AddTableRowPatch,\n revCtx?: RevisionContext,\n): FXPNode {\n if (table.rows.length === 0) {\n throw new Error(`Table \"${table.id}\" has no rows to clone from`);\n }\n\n if (patch.afterRow < -1 || patch.afterRow >= table.rows.length) {\n throw new Error(\n `Invalid afterRow ${patch.afterRow} for table \"${table.id}\" with ${table.rows.length} rows`,\n );\n }\n\n // Pick a row to clone (the row we're inserting after, or the first row)\n const sourceRowIdx = patch.afterRow >= 0 ? patch.afterRow : 0;\n const sourceRow = table.rows[sourceRowIdx]!;\n\n // Verify cell count matches\n if (patch.cells.length !== sourceRow.cells.length) {\n throw new Error(\n `Cell count mismatch: patch has ${patch.cells.length} cells, ` +\n `but row has ${sourceRow.cells.length} cells`,\n );\n }\n\n // Clone the source row\n const newRowNode = cloneNode(sourceRow.xmlRef);\n\n // Replace text in each cell of the cloned row\n const tcNodes = findChildren(newRowNode, \"w:tc\");\n for (let i = 0; i < tcNodes.length; i++) {\n const tcNode = tcNodes[i]!;\n const cellText = patch.cells[i]!;\n\n // Find the first paragraph in the cell\n const pNode = findChild(tcNode, \"w:p\");\n if (pNode) {\n // Clear existing runs and add new text\n const pChildren = getChildren(pNode);\n\n // Remove all existing w:r runs (including those wrapped in w:ins from tracked changes)\n const toRemove = pChildren.filter(\n (c) => \"w:r\" in c || \"w:ins\" in c || \"w:del\" in c,\n );\n for (const node of toRemove) {\n const idx = pChildren.indexOf(node);\n if (idx !== -1) pChildren.splice(idx, 1);\n }\n\n // Add a new run with the cell text\n const wtNode: Record<string, unknown> = {\n \"w:t\": [{ \"#text\": escapeXmlText(cellText) } as any],\n };\n if (needsSpacePreserve(cellText)) {\n if (!wtNode[\":@\"]) wtNode[\":@\"] = {};\n (wtNode[\":@\"] as Record<string, string>)[\"@_xml:space\"] = \"preserve\";\n }\n const newRun = {\n \"w:r\": [wtNode as any],\n };\n pChildren.push(newRun);\n }\n }\n\n // Insert the new row into the table XML\n const tableChildren = getChildren(table.xmlRef);\n if (patch.afterRow === -1) {\n // Insert at the beginning, after tblPr and tblGrid\n const firstTr = tableChildren.find((c) => \"w:tr\" in c);\n if (firstTr) {\n const idx = tableChildren.indexOf(firstTr);\n tableChildren.splice(idx, 0, newRowNode);\n } else {\n tableChildren.push(newRowNode);\n }\n } else {\n insertAfter(table.xmlRef, sourceRow.xmlRef, newRowNode);\n }\n\n // If tracking revisions, add w:ins to the new row's w:trPr\n if (revCtx) {\n const rowChildren = getChildren(newRowNode);\n let trPr = rowChildren.find((c) => \"w:trPr\" in c);\n if (!trPr) {\n // Create w:trPr and insert at the beginning\n trPr = createElement(\"w:trPr\", []);\n rowChildren.unshift(trPr);\n }\n const trPrChildren = getChildren(trPr);\n trPrChildren.push(createTrPrIns(revCtx));\n }\n\n return newRowNode;\n}\n\n/**\n * Execute a delete_table_row patch.\n */\nexport function executeDeleteTableRow(\n table: NormalizedTable,\n patch: DeleteTableRowPatch,\n revCtx?: RevisionContext,\n): void {\n if (patch.row < 0 || patch.row >= table.rows.length) {\n throw new Error(\n `Invalid row index ${patch.row} for table \"${table.id}\" with ${table.rows.length} rows`,\n );\n }\n\n const row = table.rows[patch.row]!;\n\n if (revCtx) {\n // With revision tracking: keep the row, add w:del to w:trPr\n const rowChildren = getChildren(row.xmlRef);\n let trPr = rowChildren.find((c) => \"w:trPr\" in c);\n if (!trPr) {\n trPr = createElement(\"w:trPr\", []);\n rowChildren.unshift(trPr);\n }\n const trPrChildren = getChildren(trPr);\n trPrChildren.push(createTrPrDel(revCtx));\n } else {\n // Without revision tracking: remove the row from XML\n removeChild(table.xmlRef, row.xmlRef);\n }\n}\n\n/**\n * Execute a replace_cell_text patch.\n *\n * This delegates to the text replace operation on the targeted paragraph.\n */\nexport function executeReplaceCellText(\n paragraph: NormalizedParagraph,\n patch: ReplaceCellTextPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n executeReplaceText(paragraph, {\n type: \"replace_text\",\n target: patch.target,\n find: patch.find,\n replace: patch.replace,\n occurrence: patch.occurrence,\n }, revCtx, markers);\n}\n\n// =============================================================================\n// Build a w:tbl FXP node from scratch\n// =============================================================================\n\n/**\n * Build a complete w:tbl FXP node with TableGrid style and single borders.\n *\n * @param headers - Optional header row cells\n * @param rows - Data rows (each sub-array is one row of cell values)\n * @returns A w:tbl FXP node ready for insertion into w:body\n */\nexport function buildTableNode(\n headers: string[] | undefined,\n rows: string[][],\n): FXPNode {\n const colCount =\n headers?.length ?? (rows.length > 0 ? rows[0]!.length : 0);\n\n // w:tblPr — table properties with TableGrid style and borders\n const borderAttrs = { \"w:val\": \"single\", \"w:sz\": \"4\", \"w:space\": \"0\", \"w:color\": \"auto\" };\n const tblBorders = createElement(\"w:tblBorders\", [\n createElement(\"w:top\", [], borderAttrs),\n createElement(\"w:left\", [], borderAttrs),\n createElement(\"w:bottom\", [], borderAttrs),\n createElement(\"w:right\", [], borderAttrs),\n createElement(\"w:insideH\", [], borderAttrs),\n createElement(\"w:insideV\", [], borderAttrs),\n ]);\n\n const tblPr = createElement(\"w:tblPr\", [\n createElement(\"w:tblStyle\", [], { \"w:val\": \"TableGrid\" }),\n createElement(\"w:tblW\", [], { \"w:w\": \"0\", \"w:type\": \"auto\" }),\n tblBorders,\n createElement(\"w:tblLook\", [], {\n \"w:val\": \"04A0\",\n \"w:firstRow\": \"1\",\n \"w:lastRow\": \"0\",\n \"w:firstColumn\": \"1\",\n \"w:lastColumn\": \"0\",\n \"w:noHBand\": \"0\",\n \"w:noVBand\": \"1\",\n }),\n ]);\n\n // w:tblGrid — one gridCol per column\n const gridCols: FXPNode[] = [];\n for (let i = 0; i < colCount; i++) {\n gridCols.push(createElement(\"w:gridCol\", []));\n }\n const tblGrid = createElement(\"w:tblGrid\", gridCols);\n\n // Build rows\n const allRows: FXPNode[] = [];\n\n if (headers) {\n allRows.push(buildTableRowNode(headers));\n }\n\n for (const row of rows) {\n allRows.push(buildTableRowNode(row));\n }\n\n return createElement(\"w:tbl\", [tblPr, tblGrid, ...allRows]);\n}\n\n/**\n * Build a single w:tr node from cell text values.\n */\nfunction buildTableRowNode(cells: string[]): FXPNode {\n const tcNodes: FXPNode[] = [];\n for (const cellText of cells) {\n const wt: FXPNode = {\n \"w:t\": [{ \"#text\": escapeXmlText(cellText) } as any],\n };\n if (needsSpacePreserve(cellText)) {\n if (!wt[\":@\"]) wt[\":@\"] = {};\n (wt[\":@\"] as Record<string, string>)[\"@_xml:space\"] = \"preserve\";\n }\n const run: FXPNode = { \"w:r\": [wt] };\n const para: FXPNode = { \"w:p\": [run] };\n const tc = createElement(\"w:tc\", [\n createElement(\"w:tcPr\", [\n createElement(\"w:tcW\", [], { \"w:w\": \"0\", \"w:type\": \"auto\" }),\n ]),\n para,\n ]);\n tcNodes.push(tc);\n }\n return createElement(\"w:tr\", tcNodes);\n}\n","/**\n * Formatting operations: apply_formatting patch execution.\n *\n * Manipulates inline w:rPr (run properties) on targeted runs.\n * Never touches w:pPr, styles.xml, or any global definitions.\n *\n * The approach:\n * 1. If `find` is provided: locate the substring via findOccurrence + resolveRunRanges,\n * split runs to isolate the match, then apply formatting to isolated runs.\n * 2. If `find` is omitted: apply formatting to all runs in the paragraph.\n *\n * Formatting semantics:\n * - `true` / value → ensure the property is present (add/update w:rPr child)\n * - `false` / `null` → remove the property from w:rPr\n * - Property not mentioned → leave unchanged\n */\n\nimport type {\n NormalizedParagraph,\n ApplyFormattingPatch,\n FormattingUpdate,\n FXPNode,\n} from \"../types.js\";\nimport { findOccurrence, resolveRunRanges } from \"../mapping/text-to-runs.js\";\nimport { splitRunsForMatch } from \"./run-splitter.js\";\nimport {\n findChild,\n getChildren,\n createElement,\n setAttr,\n removeAttr,\n getAttrs,\n removeChild,\n cloneNode,\n} from \"../xml/helpers.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport { createRPrChange } from \"./revision.js\";\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Execute an apply_formatting patch on a paragraph.\n */\nexport function executeApplyFormatting(\n paragraph: NormalizedParagraph,\n patch: ApplyFormattingPatch,\n revCtx?: RevisionContext,\n): void {\n if (patch.find !== undefined && patch.find.length > 0) {\n applyFormattingToSubstring(paragraph, patch.find, patch.occurrence ?? 1, patch.formatting, revCtx);\n } else {\n applyFormattingToAllRuns(paragraph, patch.formatting, revCtx);\n }\n}\n\n// =============================================================================\n// Internal: target selection\n// =============================================================================\n\n/**\n * Apply formatting to a substring within a paragraph.\n * Isolates the matched runs via splitting, then applies formatting.\n */\nfunction applyFormattingToSubstring(\n paragraph: NormalizedParagraph,\n find: string,\n occurrence: number,\n formatting: FormattingUpdate,\n revCtx?: RevisionContext,\n): void {\n const matchStart = findOccurrence(paragraph.text, find, occurrence);\n if (matchStart === -1) {\n throw new Error(\n `Text \"${find}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n const matchEnd = matchStart + find.length;\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchEnd);\n\n if (ranges.length === 0) {\n throw new Error(\n `No runs found for text range [${matchStart}, ${matchEnd}] in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Split runs to isolate the matched text, then apply formatting to each\n const matchedRunNodes = splitRunsForMatch(paragraph.xmlRef, ranges);\n for (const runNode of matchedRunNodes) {\n applyFormattingToRunNode(runNode, formatting, revCtx);\n }\n}\n\n/**\n * Apply formatting to all runs in a paragraph.\n */\nfunction applyFormattingToAllRuns(\n paragraph: NormalizedParagraph,\n formatting: FormattingUpdate,\n revCtx?: RevisionContext,\n): void {\n for (const run of paragraph.runs) {\n applyFormattingToRunNode(run.xmlRef, formatting, revCtx);\n }\n}\n\n// =============================================================================\n// Internal: w:rPr manipulation\n// =============================================================================\n\n/**\n * Apply a FormattingUpdate to a single w:r run node's w:rPr.\n *\n * When revCtx is provided, clones the original w:rPr before modifying,\n * then adds w:rPrChange with the old snapshot.\n */\nfunction applyFormattingToRunNode(\n runNode: FXPNode,\n formatting: FormattingUpdate,\n revCtx?: RevisionContext,\n): void {\n // Only create w:rPr if we're actually adding properties\n const hasAdds = Object.entries(formatting).some(\n ([, v]) => v !== null && v !== false && v !== undefined,\n );\n\n let rPr = findChild(runNode, \"w:rPr\");\n\n // If we only have removes and there's no rPr, nothing to do\n if (!rPr && !hasAdds) return;\n\n // Snapshot the old rPr before modifications (for revision tracking)\n const oldRPr = revCtx && rPr ? cloneNode(rPr) : undefined;\n\n // Create rPr if needed (must be first child of w:r)\n if (!rPr) {\n rPr = createElement(\"w:rPr\");\n const children = getChildren(runNode);\n children.unshift(rPr);\n }\n\n // Apply each formatting property\n if (formatting.bold !== undefined) {\n applyBooleanProp(rPr, \"w:b\", formatting.bold);\n }\n if (formatting.italic !== undefined) {\n applyBooleanProp(rPr, \"w:i\", formatting.italic);\n }\n if (formatting.strikethrough !== undefined) {\n applyBooleanProp(rPr, \"w:strike\", formatting.strikethrough);\n }\n if (formatting.underline !== undefined) {\n applyAttrProp(rPr, \"w:u\", \"w:val\", formatting.underline);\n }\n if (formatting.fontSize !== undefined) {\n applyAttrProp(rPr, \"w:sz\", \"w:val\", formatting.fontSize !== null ? String(formatting.fontSize) : null);\n }\n if (formatting.fontFamily !== undefined) {\n applyFontFamily(rPr, formatting.fontFamily);\n }\n if (formatting.color !== undefined) {\n applyAttrProp(rPr, \"w:color\", \"w:val\", formatting.color);\n }\n if (formatting.highlight !== undefined) {\n applyAttrProp(rPr, \"w:highlight\", \"w:val\", formatting.highlight);\n }\n\n // Add w:rPrChange with the old snapshot (revision tracking)\n if (revCtx) {\n // Remove any existing w:rPrChange first\n const existingChange = findChild(rPr, \"w:rPrChange\");\n if (existingChange) {\n removeChild(rPr, existingChange);\n }\n\n const oldRPrForChange = oldRPr ?? createElement(\"w:rPr\");\n const rPrChange = createRPrChange(revCtx, oldRPrForChange);\n getChildren(rPr).push(rPrChange);\n }\n}\n\n/**\n * Set or remove a boolean property element (e.g., w:b, w:i, w:strike).\n * - `true` → ensure the element exists (no attributes needed)\n * - `false` / `null` → remove the element if present\n */\nfunction applyBooleanProp(\n rPr: FXPNode,\n tagName: string,\n value: boolean | null,\n): void {\n const existing = findChild(rPr, tagName);\n\n if (value === true) {\n if (!existing) {\n getChildren(rPr).push(createElement(tagName));\n }\n } else {\n // false or null → remove\n if (existing) {\n removeChild(rPr, existing);\n }\n }\n}\n\n/**\n * Set or remove a property element that carries its value in an attribute.\n * e.g., <w:u w:val=\"single\"/>, <w:sz w:val=\"24\"/>, <w:color w:val=\"FF0000\"/>\n *\n * - non-null string → create/update element with the attribute\n * - `null` → remove the element\n */\nfunction applyAttrProp(\n rPr: FXPNode,\n tagName: string,\n attrName: string,\n value: string | null,\n): void {\n const existing = findChild(rPr, tagName);\n\n if (value !== null) {\n if (existing) {\n setAttr(existing, attrName, value);\n } else {\n const el = createElement(tagName, [], { [attrName]: value });\n getChildren(rPr).push(el);\n }\n } else {\n // null → remove\n if (existing) {\n removeChild(rPr, existing);\n }\n }\n}\n\n/**\n * Set or remove font family.\n * In OOXML, font family is set via w:rFonts with multiple attributes:\n * <w:rFonts w:ascii=\"Arial\" w:hAnsi=\"Arial\"/>\n *\n * - non-null string → create/update w:rFonts with w:ascii and w:hAnsi\n * - `null` → remove only w:ascii and w:hAnsi attributes (preserving w:eastAsia,\n * w:cs, theme attributes, etc.). If no attributes remain, remove the element.\n */\nfunction applyFontFamily(rPr: FXPNode, value: string | null): void {\n const existing = findChild(rPr, \"w:rFonts\");\n\n if (value !== null) {\n if (existing) {\n setAttr(existing, \"w:ascii\", value);\n setAttr(existing, \"w:hAnsi\", value);\n } else {\n const el = createElement(\"w:rFonts\", [], {\n \"w:ascii\": value,\n \"w:hAnsi\": value,\n });\n getChildren(rPr).push(el);\n }\n } else {\n if (existing) {\n removeAttr(existing, \"w:ascii\");\n removeAttr(existing, \"w:hAnsi\");\n // Only remove the element entirely if no attributes remain\n if (Object.keys(getAttrs(existing)).length === 0) {\n removeChild(rPr, existing);\n }\n }\n }\n}\n","/**\n * Style operations: apply_style patch execution.\n *\n * Re-points paragraphs or runs to pre-existing styles in styles.xml.\n * Never creates, modifies, or deletes style definitions.\n *\n * Two modes:\n * 1. Paragraph style (no `find`): Sets w:pPr > w:pStyle on the paragraph.\n * The style must be type \"paragraph\" in styles.xml.\n *\n * 2. Character style (with `find`): Isolates matched runs via splitting,\n * then sets w:rPr > w:rStyle on each matched run.\n * The style must be type \"character\" in styles.xml.\n */\n\nimport type {\n NormalizedParagraph,\n NormalizedDocument,\n ApplyStylePatch,\n FXPNode,\n} from \"../types.js\";\nimport { findOccurrence, resolveRunRanges } from \"../mapping/text-to-runs.js\";\nimport { splitRunsForMatch } from \"./run-splitter.js\";\nimport {\n findChild,\n getChildren,\n createElement,\n setAttr,\n removeChild,\n cloneNode,\n} from \"../xml/helpers.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport { createPPrChange, createRPrChange } from \"./revision.js\";\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Execute an apply_style patch on a paragraph.\n *\n * Validates that the style exists and is the correct type before applying.\n */\nexport function executeApplyStyle(\n paragraph: NormalizedParagraph,\n patch: ApplyStylePatch,\n doc: NormalizedDocument,\n revCtx?: RevisionContext,\n): void {\n const styleInfo = doc.styles.find((s) => s.id === patch.style);\n if (!styleInfo) {\n throw new Error(\n `Style \"${patch.style}\" not found in document styles.xml`,\n );\n }\n\n if (patch.find !== undefined && patch.find.length > 0) {\n // Character style mode\n if (styleInfo.type !== \"character\") {\n throw new Error(\n `Style \"${patch.style}\" is type \"${styleInfo.type}\", but character style is required when \"find\" is specified`,\n );\n }\n applyCharacterStyle(paragraph, patch.find, patch.occurrence ?? 1, patch.style, revCtx);\n } else {\n // Paragraph style mode\n if (styleInfo.type !== \"paragraph\") {\n throw new Error(\n `Style \"${patch.style}\" is type \"${styleInfo.type}\", but paragraph style is required when \"find\" is omitted`,\n );\n }\n applyParagraphStyle(paragraph, patch.style, revCtx);\n }\n}\n\n// =============================================================================\n// Internal: paragraph style\n// =============================================================================\n\n/**\n * Apply a paragraph style by setting w:pPr > w:pStyle.\n *\n * If w:pPr doesn't exist, it's created as the first child of w:p.\n * If w:pStyle already exists, its w:val is updated.\n * If w:pStyle doesn't exist, it's created as the first child of w:pPr.\n *\n * When revCtx is provided, adds w:pPrChange with the old pPr snapshot.\n */\nfunction applyParagraphStyle(\n paragraph: NormalizedParagraph,\n styleId: string,\n revCtx?: RevisionContext,\n): void {\n const paraNode = paragraph.xmlRef;\n\n // Snapshot old pPr before modification (for revision tracking)\n let pPr = findChild(paraNode, \"w:pPr\");\n const oldPPr = revCtx && pPr ? cloneNode(pPr) : undefined;\n\n // Ensure w:pPr exists (must be first child of w:p)\n if (!pPr) {\n pPr = createElement(\"w:pPr\");\n const children = getChildren(paraNode);\n children.unshift(pPr);\n }\n\n // Set or update w:pStyle\n let pStyle = findChild(pPr, \"w:pStyle\");\n if (pStyle) {\n setAttr(pStyle, \"w:val\", styleId);\n } else {\n pStyle = createElement(\"w:pStyle\", [], { \"w:val\": styleId });\n // w:pStyle should be first child of w:pPr\n getChildren(pPr).unshift(pStyle);\n }\n\n // Add w:pPrChange with the old snapshot (revision tracking)\n if (revCtx) {\n // Remove any existing w:pPrChange first\n const existingChange = findChild(pPr, \"w:pPrChange\");\n if (existingChange) {\n removeChild(pPr, existingChange);\n }\n\n const oldPPrForChange = oldPPr ?? createElement(\"w:pPr\");\n const pPrChange = createPPrChange(revCtx, oldPPrForChange);\n getChildren(pPr).push(pPrChange);\n }\n}\n\n// =============================================================================\n// Internal: character style\n// =============================================================================\n\n/**\n * Apply a character style to a substring by setting w:rPr > w:rStyle\n * on each matched run.\n *\n * Uses the same find/split machinery as formatting patches.\n * When revCtx is provided, adds w:rPrChange with the old rPr snapshot.\n */\nfunction applyCharacterStyle(\n paragraph: NormalizedParagraph,\n find: string,\n occurrence: number,\n styleId: string,\n revCtx?: RevisionContext,\n): void {\n const matchStart = findOccurrence(paragraph.text, find, occurrence);\n if (matchStart === -1) {\n throw new Error(\n `Text \"${find}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n\n const matchEnd = matchStart + find.length;\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchEnd);\n\n if (ranges.length === 0) {\n throw new Error(\n `No runs found for text range [${matchStart}, ${matchEnd}] in paragraph \"${paragraph.id}\"`,\n );\n }\n\n // Split runs to isolate the matched text\n const matchedRunNodes = splitRunsForMatch(paragraph.xmlRef, ranges);\n\n // Apply w:rStyle to each matched run\n for (const runNode of matchedRunNodes) {\n applyRStyleToRunNode(runNode, styleId, revCtx);\n }\n}\n\n/**\n * Set w:rPr > w:rStyle on a single run node.\n *\n * Creates w:rPr if needed. If w:rStyle already exists, updates it.\n * Otherwise creates it as the first child of w:rPr.\n * When revCtx is provided, adds w:rPrChange with the old rPr snapshot.\n */\nfunction applyRStyleToRunNode(\n runNode: FXPNode,\n styleId: string,\n revCtx?: RevisionContext,\n): void {\n // Snapshot old rPr before modification (for revision tracking)\n let rPr = findChild(runNode, \"w:rPr\");\n const oldRPr = revCtx && rPr ? cloneNode(rPr) : undefined;\n\n // Ensure w:rPr exists (must be first child of w:r)\n if (!rPr) {\n rPr = createElement(\"w:rPr\");\n const children = getChildren(runNode);\n children.unshift(rPr);\n }\n\n // Remove any existing w:rStyle first\n const existingRStyle = findChild(rPr, \"w:rStyle\");\n if (existingRStyle) {\n removeChild(rPr, existingRStyle);\n }\n\n // Create new w:rStyle as first child of w:rPr\n const rStyle = createElement(\"w:rStyle\", [], { \"w:val\": styleId });\n getChildren(rPr).unshift(rStyle);\n\n // Add w:rPrChange with the old snapshot (revision tracking)\n if (revCtx) {\n // Remove any existing w:rPrChange first\n const existingChange = findChild(rPr, \"w:rPrChange\");\n if (existingChange) {\n removeChild(rPr, existingChange);\n }\n\n const oldRPrForChange = oldRPr ?? createElement(\"w:rPr\");\n const rPrChange = createRPrChange(revCtx, oldRPrForChange);\n getChildren(rPr).push(rPrChange);\n }\n}\n","/**\n * Field code placeholder support for OOXML.\n *\n * Supports {{FIELD}} syntax in text content. When encountered, emits\n * proper w:fldChar / w:instrText XML instead of plain text.\n *\n * Supported fields:\n * - {{PAGE}} → PAGE (current page number)\n * - {{NUMPAGES}} → NUMPAGES (total page count)\n * - {{DATE}} → DATE (current date)\n * - {{TIME}} → TIME (current time)\n * - {{FILENAME}} → FILENAME (document filename)\n */\n\nimport type { FXPNode } from \"../types.js\";\nimport {\n createElement,\n createRun,\n getChildren,\n setAttr,\n} from \"../xml/helpers.js\";\nimport type { FormattedSegment } from \"./markdown-parser.js\";\nimport { parseFormattedText } from \"./markdown-parser.js\";\nimport { createFormattedRuns } from \"./text-ops.js\";\n\n/** Supported OOXML field codes. */\nconst SUPPORTED_FIELDS = new Set([\n \"PAGE\",\n \"NUMPAGES\",\n \"DATE\",\n \"TIME\",\n \"FILENAME\",\n]);\n\n/** A parsed segment: either plain text or a field code. */\nexport type TextSegment =\n | { kind: \"text\"; text: string }\n | { kind: \"field\"; fieldCode: string };\n\n/**\n * Parse text containing {{FIELD}} placeholders into segments.\n *\n * Example: \"Page {{PAGE}} of {{NUMPAGES}}\" →\n * [{ kind: \"text\", text: \"Page \" },\n * { kind: \"field\", fieldCode: \"PAGE\" },\n * { kind: \"text\", text: \" of \" },\n * { kind: \"field\", fieldCode: \"NUMPAGES\" }]\n */\nexport function parseFieldPlaceholders(text: string): TextSegment[] {\n const segments: TextSegment[] = [];\n const regex = /\\{\\{(\\w+)\\}\\}/g;\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(text)) !== null) {\n const fieldName = match[1]!;\n\n // Only treat as field if it's a supported field code\n if (!SUPPORTED_FIELDS.has(fieldName)) {\n continue;\n }\n\n // Add preceding plain text\n if (match.index > lastIndex) {\n segments.push({ kind: \"text\", text: text.slice(lastIndex, match.index) });\n }\n\n segments.push({ kind: \"field\", fieldCode: fieldName });\n lastIndex = match.index + match[0].length;\n }\n\n // Add trailing text\n if (lastIndex < text.length) {\n segments.push({ kind: \"text\", text: text.slice(lastIndex) });\n }\n\n // If no fields were found, return a single text segment\n if (segments.length === 0) {\n segments.push({ kind: \"text\", text });\n }\n\n return segments;\n}\n\n/**\n * Check if text contains any field placeholders.\n */\nexport function hasFieldPlaceholders(text: string): boolean {\n const regex = /\\{\\{(\\w+)\\}\\}/g;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n if (SUPPORTED_FIELDS.has(match[1]!)) return true;\n }\n return false;\n}\n\n/**\n * Pick a character that does not occur in `text` to stand in for a field\n * while inline markers are parsed around it. U+FFFC (object replacement\n * character) is the first choice; if the text happens to contain it, a\n * private-use character that the text does not contain is used instead, so\n * literal text is never mistaken for a field.\n */\nfunction pickFieldPlaceholder(text: string): string {\n if (!text.includes(\"\\uFFFC\")) return \"\\uFFFC\";\n for (let cp = 0xe000; cp <= 0xf8ff; cp++) {\n const ch = String.fromCharCode(cp);\n if (!text.includes(ch)) return ch;\n }\n throw new Error(\"Cannot find a placeholder character for field codes\");\n}\n\n/**\n * Build FXP run nodes for a text string that may contain field placeholders\n * and/or inline markdown formatting.\n *\n * Formatting state is tracked across field boundaries so that markers like\n * `**{{PAGE}}**` correctly apply bold to the field runs rather than emitting\n * literal `**` characters.\n *\n * Algorithm:\n * 1. Parse field placeholders to get text/field segments.\n * 2. Replace every field with a placeholder character (one the text does not\n * contain) and run the GFM parser on the whole string, so markers open and\n * close across fields and the placeholder (a non-word character) keeps\n * `_{{PAGE}}_` a valid italic.\n * 3. Walk the formatted segments: text becomes runs with that formatting,\n * each placeholder becomes the field-code run sequence carrying the\n * formatting active at that point.\n *\n * @param text - Text possibly containing {{FIELD}} placeholders and/or formatting markers\n * @param rPrNode - Optional run properties to clone onto each run\n * @param markers - Parse inline formatting markers and escapes (default true);\n * with `false` the text is written verbatim\n * @returns Array of w:r FXP nodes\n */\nexport function buildFieldRuns(text: string, rPrNode?: FXPNode, markers: boolean = true): FXPNode[] {\n const segments = parseFieldPlaceholders(text);\n\n // Fast path: no field placeholders — just parse formatting directly\n if (segments.length === 1 && segments[0]!.kind === \"text\") {\n return emitTextRuns(parseFormattedText(segments[0]!.text, markers), rPrNode);\n }\n\n const fieldCodes = segments\n .filter((s): s is { kind: \"field\"; fieldCode: string } => s.kind === \"field\")\n .map((s) => s.fieldCode);\n const placeholder = pickFieldPlaceholder(text);\n const combined = segments\n .map((s) => (s.kind === \"text\" ? s.text : placeholder))\n .join(\"\");\n const formatted = parseFormattedText(combined, markers);\n\n const runs: FXPNode[] = [];\n let pending: FormattedSegment[] = [];\n let fieldIdx = 0;\n const flushPending = (): void => {\n if (pending.length > 0) {\n runs.push(...emitTextRuns(pending, rPrNode));\n pending = [];\n }\n };\n\n for (const seg of formatted) {\n const parts = seg.text.split(placeholder);\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!;\n if (part.length > 0) pending.push({ ...seg, text: part });\n if (i < parts.length - 1) {\n flushPending();\n const fieldRPr = buildFormattedRPr(rPrNode, {\n bold: !!seg.bold,\n italic: !!seg.italic,\n strikethrough: !!seg.strikethrough,\n underline: !!seg.underline,\n });\n runs.push(...buildFieldCodeRuns(fieldCodes[fieldIdx++]!, fieldRPr));\n }\n }\n }\n flushPending();\n\n return runs;\n}\n\n/**\n * Turn formatted segments into runs: one formatted run per segment when any\n * segment carries formatting, otherwise a single plain run.\n */\nfunction emitTextRuns(segments: FormattedSegment[], rPrNode?: FXPNode): FXPNode[] {\n const hasFormatting = segments.some(\n (s) => s.bold || s.italic || s.strikethrough || s.underline,\n );\n if (hasFormatting) {\n return createFormattedRuns(segments, rPrNode);\n }\n const plainText = segments.map((s) => s.text).join(\"\");\n return plainText.length > 0 ? [createRun(plainText, rPrNode)] : [];\n}\n\n/**\n * Build the w:fldChar / w:instrText run sequence for a field code.\n */\nfunction buildFieldCodeRuns(fieldCode: string, rPrNode?: FXPNode): FXPNode[] {\n const cloneRPr = (): FXPNode[] => {\n if (!rPrNode) return [];\n return [JSON.parse(JSON.stringify(rPrNode)) as FXPNode];\n };\n\n // Begin run\n const beginRun: FXPNode = {\n \"w:r\": [\n ...cloneRPr(),\n createElement(\"w:fldChar\", [], { \"w:fldCharType\": \"begin\" }),\n ],\n };\n\n // Instruction run\n const instrText = ` ${fieldCode} `;\n const instrTextNode: FXPNode = {\n \"w:instrText\": [{ \"#text\": instrText } as unknown as FXPNode],\n };\n setAttr(instrTextNode, \"xml:space\", \"preserve\");\n\n const instrRun: FXPNode = {\n \"w:r\": [...cloneRPr(), instrTextNode],\n };\n\n // Separate run\n const separateRun: FXPNode = {\n \"w:r\": [\n ...cloneRPr(),\n createElement(\"w:fldChar\", [], { \"w:fldCharType\": \"separate\" }),\n ],\n };\n\n // End run\n const endRun: FXPNode = {\n \"w:r\": [\n ...cloneRPr(),\n createElement(\"w:fldChar\", [], { \"w:fldCharType\": \"end\" }),\n ],\n };\n\n return [beginRun, instrRun, separateRun, endRun];\n}\n\n/**\n * Build a w:rPr node that merges the base rPrNode with GFM formatting flags.\n * Returns undefined if neither base nor flags produce any properties.\n */\nfunction buildFormattedRPr(\n baseRPr: FXPNode | undefined,\n fmt: { bold: boolean; italic: boolean; strikethrough: boolean; underline: boolean },\n): FXPNode | undefined {\n const needsFormatting = fmt.bold || fmt.italic || fmt.strikethrough || fmt.underline;\n if (!baseRPr && !needsFormatting) return undefined;\n\n // Clone the base rPr (or create a fresh one)\n const rPr: FXPNode = baseRPr\n ? (JSON.parse(JSON.stringify(baseRPr)) as FXPNode)\n : createElement(\"w:rPr\", []);\n\n if (!needsFormatting) return rPr;\n\n const children = getChildren(rPr);\n\n if (fmt.bold && !children.some((c) => \"w:b\" in c)) {\n children.push(createElement(\"w:b\", []));\n }\n if (fmt.italic && !children.some((c) => \"w:i\" in c)) {\n children.push(createElement(\"w:i\", []));\n }\n if (fmt.strikethrough && !children.some((c) => \"w:strike\" in c)) {\n children.push(createElement(\"w:strike\", []));\n }\n if (fmt.underline && !children.some((c) => \"w:u\" in c)) {\n children.push(createElement(\"w:u\", [], { \"w:val\": \"single\" }));\n }\n\n return rPr;\n}\n","/**\n * Header/footer structural operations: add and remove headers/footers.\n *\n * These ops create/remove XML parts, manage relationships in\n * document.xml.rels and [Content_Types].xml, and mutate sectPr\n * references in the document body.\n */\n\nimport type {\n NormalizedDocument,\n AddHeaderPatch,\n AddFooterPatch,\n RemoveHeaderPatch,\n RemoveFooterPatch,\n FXPNode,\n HeaderFooterVariant,\n} from \"../types.js\";\nimport {\n createElement,\n findChild,\n findChildren,\n getChildren,\n getTagName,\n getAttr,\n} from \"../xml/helpers.js\";\nimport { normalizeHeaderFooter } from \"../normalizer/header-footer.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport type { RevisionContext } from \"./revision.js\";\nimport { createInsElement } from \"./revision.js\";\n\n// =============================================================================\n// OOXML variant → w:type mapping\n// =============================================================================\n\n/** Map variant names to OOXML w:type attribute values. */\nconst VARIANT_TYPE_MAP: Record<HeaderFooterVariant, string> = {\n default: \"default\",\n first: \"first\",\n even: \"even\",\n};\n\n// =============================================================================\n// Add Header\n// =============================================================================\n\n/**\n * Execute an add_header patch: creates a new header XML part and wires\n * it into the document's last section.\n */\nexport function executeAddHeader(\n doc: NormalizedDocument,\n patch: AddHeaderPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n executeAddHeaderFooter(doc, \"header\", patch.variant, patch.paragraphs, revCtx, markers);\n}\n\n// =============================================================================\n// Add Footer\n// =============================================================================\n\n/**\n * Execute an add_footer patch: creates a new footer XML part and wires\n * it into the document's last section.\n */\nexport function executeAddFooter(\n doc: NormalizedDocument,\n patch: AddFooterPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n executeAddHeaderFooter(doc, \"footer\", patch.variant, patch.paragraphs, revCtx, markers);\n}\n\n// =============================================================================\n// Remove Header\n// =============================================================================\n\n/**\n * Execute a remove_header patch: removes a header from the document's\n * last section.\n */\nexport function executeRemoveHeader(\n doc: NormalizedDocument,\n patch: RemoveHeaderPatch,\n): void {\n executeRemoveHeaderFooter(doc, \"header\", patch.variant);\n}\n\n// =============================================================================\n// Remove Footer\n// =============================================================================\n\n/**\n * Execute a remove_footer patch: removes a footer from the document's\n * last section.\n */\nexport function executeRemoveFooter(\n doc: NormalizedDocument,\n patch: RemoveFooterPatch,\n): void {\n executeRemoveHeaderFooter(doc, \"footer\", patch.variant);\n}\n\n// =============================================================================\n// Shared implementation\n// =============================================================================\n\n/**\n * Add a header or footer to the document.\n */\nfunction executeAddHeaderFooter(\n doc: NormalizedDocument,\n type: \"header\" | \"footer\",\n variant: HeaderFooterVariant,\n paragraphs: string[],\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n // 1. Determine next available filename number\n const existingParts = type === \"header\" ? doc.xmlParts.headers : doc.xmlParts.footers;\n const nextNum = getNextPartNumber(existingParts, type);\n const filename = `${type}${nextNum}.xml`;\n const fullPath = `word/${filename}`;\n\n // 2. Build the header/footer XML FXP\n const rootTag = type === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const pNodes: FXPNode[] = [];\n\n for (const text of paragraphs) {\n const runs = buildFieldRuns(text, undefined, markers);\n\n if (revCtx) {\n // Wrap runs in w:ins for revision tracking\n const revRuns = runs.map((run) => {\n // For field code runs, wrap as-is; for plain runs, use as-is\n return run;\n });\n const insElement = createInsElement(revCtx, revRuns);\n pNodes.push({ \"w:p\": [insElement] });\n } else {\n pNodes.push({ \"w:p\": runs });\n }\n }\n\n const rootNode = createElement(rootTag, pNodes, {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n \"xmlns:r\": \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n });\n // Wrap attributes onto the tag using FXP convention (attrs on root via :@)\n const fxpPart: FXPNode[] = [rootNode];\n\n // 3. Generate a new rId\n const rId = getNextRelationshipId(doc);\n\n // 4. Add relationship to document.xml.rels\n addRelationship(doc, rId, filename, type);\n\n // 5. Add content type override\n addContentTypeOverride(doc, fullPath, type);\n\n // 6. Add header/footer reference to last sectPr\n addSectPrReference(doc, type, variant, rId);\n\n // 7. Store the new FXP part\n existingParts.set(fullPath, fxpPart);\n\n // 8. Normalize and add to doc.headers/doc.footers\n const hf = normalizeHeaderFooter(fxpPart, type, variant, rId);\n if (type === \"header\") {\n doc.headers.set(rId, hf);\n } else {\n doc.footers.set(rId, hf);\n }\n}\n\n/**\n * Remove a header or footer from the document.\n */\nfunction executeRemoveHeaderFooter(\n doc: NormalizedDocument,\n type: \"header\" | \"footer\",\n variant: HeaderFooterVariant,\n): void {\n // 1. Find the reference in sectPr\n const refTag = type === \"header\" ? \"w:headerReference\" : \"w:footerReference\";\n const sectPr = getLastSectPr(doc);\n if (!sectPr) throw new Error(\"No sectPr found in document\");\n\n const sectPrChildren = getChildren(sectPr);\n const refNode = sectPrChildren.find((child) => {\n const tag = getTagName(child);\n if (tag !== refTag) return false;\n const refType = getAttr(child, \"w:type\") ?? \"default\";\n return refType === VARIANT_TYPE_MAP[variant];\n });\n\n if (!refNode) {\n throw new Error(`No ${type} with variant \"${variant}\" found in sectPr`);\n }\n\n const rId = getAttr(refNode, \"r:id\");\n if (!rId) throw new Error(`${type} reference has no r:id`);\n\n // 2. Remove from sectPr\n const idx = sectPrChildren.indexOf(refNode);\n if (idx !== -1) sectPrChildren.splice(idx, 1);\n\n // 3. Find the target filename from rels\n const target = findRelationshipTarget(doc, rId);\n const fullPath = target ? `word/${target}` : undefined;\n\n // 4. Remove the relationship\n removeRelationship(doc, rId);\n\n // 5. Remove content type override\n if (fullPath) {\n removeContentTypeOverride(doc, fullPath);\n }\n\n // 6. Remove from xmlParts\n const parts = type === \"header\" ? doc.xmlParts.headers : doc.xmlParts.footers;\n if (fullPath) {\n parts.delete(fullPath);\n }\n\n // 7. Remove from normalized maps\n if (type === \"header\") {\n doc.headers.delete(rId);\n } else {\n doc.footers.delete(rId);\n }\n}\n\n// =============================================================================\n// Helpers: file numbering\n// =============================================================================\n\n/**\n * Find the next available number for a header/footer filename.\n */\nfunction getNextPartNumber(\n parts: Map<string, FXPNode[]>,\n type: \"header\" | \"footer\",\n): number {\n let maxNum = 0;\n const regex = new RegExp(`${type}(\\\\d+)\\\\.xml$`, \"i\");\n for (const path of parts.keys()) {\n const match = path.match(regex);\n if (match) {\n const num = parseInt(match[1]!, 10);\n if (num > maxNum) maxNum = num;\n }\n }\n return maxNum + 1;\n}\n\n// =============================================================================\n// Helpers: relationship management\n// =============================================================================\n\n/**\n * Get the next available relationship ID.\n */\nfunction getNextRelationshipId(doc: NormalizedDocument): string {\n const rels = doc.xmlParts.documentRels;\n if (!rels) return \"rId1\";\n\n let maxId = 0;\n const relsNode = rels.find((n) => getTagName(n) === \"Relationships\");\n if (relsNode) {\n const relNodes = findChildren(relsNode, \"Relationship\");\n for (const rel of relNodes) {\n const id = getAttr(rel, \"Id\");\n if (id) {\n const num = parseInt(id.replace(/^rId/, \"\"), 10);\n if (!isNaN(num) && num > maxId) maxId = num;\n }\n }\n }\n return `rId${maxId + 1}`;\n}\n\n/**\n * Add a Relationship node to document.xml.rels.\n */\nfunction addRelationship(\n doc: NormalizedDocument,\n rId: string,\n target: string,\n type: \"header\" | \"footer\",\n): void {\n if (!doc.xmlParts.documentRels) {\n // Initialize a minimal Relationships root on-demand so that\n // add_header / add_footer works on documents created via Docxtor.create()\n // (which doesn't include word/_rels/document.xml.rels).\n doc.xmlParts.documentRels = [\n createElement(\"Relationships\", [], {\n \"xmlns\": \"http://schemas.openxmlformats.org/package/2006/relationships\",\n }),\n ];\n }\n\n const relType = type === \"header\"\n ? \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header\"\n : \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer\";\n\n const relNode = createElement(\"Relationship\", [], {\n \"Id\": rId,\n \"Type\": relType,\n \"Target\": target,\n });\n\n const relsNode = doc.xmlParts.documentRels.find(\n (n) => getTagName(n) === \"Relationships\",\n );\n if (relsNode) {\n getChildren(relsNode).push(relNode);\n }\n}\n\n/**\n * Find the target filename for a relationship by rId.\n */\nfunction findRelationshipTarget(\n doc: NormalizedDocument,\n rId: string,\n): string | undefined {\n const rels = doc.xmlParts.documentRels;\n if (!rels) return undefined;\n\n const relsNode = rels.find((n) => getTagName(n) === \"Relationships\");\n if (!relsNode) return undefined;\n\n const relNodes = findChildren(relsNode, \"Relationship\");\n for (const rel of relNodes) {\n if (getAttr(rel, \"Id\") === rId) {\n return getAttr(rel, \"Target\");\n }\n }\n return undefined;\n}\n\n/**\n * Remove a Relationship node from document.xml.rels by rId.\n */\nfunction removeRelationship(doc: NormalizedDocument, rId: string): void {\n const rels = doc.xmlParts.documentRels;\n if (!rels) return;\n\n const relsNode = rels.find((n) => getTagName(n) === \"Relationships\");\n if (!relsNode) return;\n\n const children = getChildren(relsNode);\n const idx = children.findIndex(\n (child) => getTagName(child) === \"Relationship\" && getAttr(child, \"Id\") === rId,\n );\n if (idx !== -1) children.splice(idx, 1);\n}\n\n// =============================================================================\n// Helpers: content types management\n// =============================================================================\n\n/**\n * Add an Override entry to [Content_Types].xml.\n */\nfunction addContentTypeOverride(\n doc: NormalizedDocument,\n partName: string,\n type: \"header\" | \"footer\",\n): void {\n if (!doc.xmlParts.contentTypes) return;\n\n const contentType = type === \"header\"\n ? \"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml\"\n : \"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml\";\n\n const overrideNode = createElement(\"Override\", [], {\n \"PartName\": `/${partName}`,\n \"ContentType\": contentType,\n });\n\n const typesNode = doc.xmlParts.contentTypes.find(\n (n) => getTagName(n) === \"Types\",\n );\n if (typesNode) {\n getChildren(typesNode).push(overrideNode);\n }\n}\n\n/**\n * Remove an Override entry from [Content_Types].xml by part name.\n */\nfunction removeContentTypeOverride(\n doc: NormalizedDocument,\n partName: string,\n): void {\n if (!doc.xmlParts.contentTypes) return;\n\n const typesNode = doc.xmlParts.contentTypes.find(\n (n) => getTagName(n) === \"Types\",\n );\n if (!typesNode) return;\n\n const children = getChildren(typesNode);\n // PartName may be stored with or without leading /\n const idx = children.findIndex((child) => {\n if (getTagName(child) !== \"Override\") return false;\n const pn = getAttr(child, \"PartName\");\n return pn === `/${partName}` || pn === partName;\n });\n if (idx !== -1) children.splice(idx, 1);\n}\n\n// =============================================================================\n// Helpers: sectPr management\n// =============================================================================\n\n/**\n * Get the last w:sectPr in the document body.\n */\nfunction getLastSectPr(doc: NormalizedDocument): FXPNode | undefined {\n const docNode = doc.xmlParts.document.find(\n (n) => getTagName(n) === \"w:document\",\n );\n if (!docNode) return undefined;\n\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) return undefined;\n\n const bodyChildren = getChildren(bodyNode);\n // sectPr is typically the last child of w:body\n for (let i = bodyChildren.length - 1; i >= 0; i--) {\n if (getTagName(bodyChildren[i]!) === \"w:sectPr\") {\n return bodyChildren[i]!;\n }\n }\n return undefined;\n}\n\n/**\n * Add a header/footer reference to the last sectPr.\n * Creates sectPr if it doesn't exist.\n */\nfunction addSectPrReference(\n doc: NormalizedDocument,\n type: \"header\" | \"footer\",\n variant: HeaderFooterVariant,\n rId: string,\n): void {\n let sectPr = getLastSectPr(doc);\n\n if (!sectPr) {\n // Create a sectPr and append to body\n sectPr = createElement(\"w:sectPr\", []);\n const docNode = doc.xmlParts.document.find(\n (n) => getTagName(n) === \"w:document\",\n );\n if (!docNode) throw new Error(\"Cannot find w:document\");\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) throw new Error(\"Cannot find w:body\");\n getChildren(bodyNode).push(sectPr);\n }\n\n const refTag = type === \"header\" ? \"w:headerReference\" : \"w:footerReference\";\n const refNode = createElement(refTag, [], {\n \"w:type\": VARIANT_TYPE_MAP[variant],\n \"r:id\": rId,\n });\n\n // Insert header/footer references at the beginning of sectPr children\n // (before pgSz, pgMar, etc.)\n getChildren(sectPr).unshift(refNode);\n\n // If variant is \"first\", ensure w:titlePg is present in sectPr\n if (variant === \"first\") {\n const sectPrChildren = getChildren(sectPr);\n const hasTitlePg = sectPrChildren.some(\n (c) => getTagName(c) === \"w:titlePg\",\n );\n if (!hasTitlePg) {\n sectPrChildren.push(createElement(\"w:titlePg\", []));\n }\n }\n\n // If variant is \"even\", ensure w:evenAndOddHeaders is present in\n // word/settings.xml so Word honours the even-page header/footer reference.\n if (variant === \"even\") {\n ensureEvenAndOddHeaders(doc);\n }\n}\n\n// =============================================================================\n// Helpers: settings.xml management\n// =============================================================================\n\n/**\n * Ensure `<w:evenAndOddHeaders/>` is present in word/settings.xml.\n *\n * Without this element, Word ignores even-page header/footer references.\n * If settings.xml was not loaded (e.g., created via Docxtor.create()),\n * a minimal settings part is initialized on-demand.\n */\nfunction ensureEvenAndOddHeaders(doc: NormalizedDocument): void {\n if (!doc.xmlParts.settings) {\n // Create a minimal settings.xml FXP structure\n doc.xmlParts.settings = [\n createElement(\"w:settings\", [\n createElement(\"w:evenAndOddHeaders\", []),\n ], {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n }),\n ];\n }\n\n const settingsNode = doc.xmlParts.settings.find(\n (n) => getTagName(n) === \"w:settings\",\n );\n if (!settingsNode) return;\n\n const children = getChildren(settingsNode);\n const hasFlag = children.some((c) => getTagName(c) === \"w:evenAndOddHeaders\");\n if (!hasFlag) {\n children.push(createElement(\"w:evenAndOddHeaders\", []));\n }\n\n ensureSettingsRelationship(doc);\n ensureSettingsContentTypeOverride(doc);\n}\n\nfunction ensureSettingsRelationship(doc: NormalizedDocument): void {\n if (!doc.xmlParts.documentRels) {\n doc.xmlParts.documentRels = [\n createElement(\"Relationships\", [], {\n \"xmlns\": \"http://schemas.openxmlformats.org/package/2006/relationships\",\n }),\n ];\n }\n\n const relsNode = doc.xmlParts.documentRels.find(\n (n) => getTagName(n) === \"Relationships\",\n );\n if (!relsNode) return;\n\n const relType =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings\";\n const children = getChildren(relsNode);\n const hasSettingsRel = children.some((child) => {\n if (getTagName(child) !== \"Relationship\") return false;\n return getAttr(child, \"Type\") === relType && getAttr(child, \"Target\") === \"settings.xml\";\n });\n if (hasSettingsRel) return;\n\n children.push(createElement(\"Relationship\", [], {\n \"Id\": getNextRelationshipId(doc),\n \"Type\": relType,\n \"Target\": \"settings.xml\",\n }));\n}\n\nfunction ensureSettingsContentTypeOverride(doc: NormalizedDocument): void {\n if (!doc.xmlParts.contentTypes) {\n doc.xmlParts.contentTypes = [\n createElement(\"Types\", [], {\n \"xmlns\": \"http://schemas.openxmlformats.org/package/2006/content-types\",\n }),\n ];\n }\n\n const typesNode = doc.xmlParts.contentTypes.find(\n (n) => getTagName(n) === \"Types\",\n );\n if (!typesNode) return;\n\n const partName = \"/word/settings.xml\";\n const contentType =\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml\";\n const children = getChildren(typesNode);\n const hasSettingsOverride = children.some((child) => {\n if (getTagName(child) !== \"Override\") return false;\n return getAttr(child, \"PartName\") === partName;\n });\n if (hasSettingsOverride) return;\n\n children.push(createElement(\"Override\", [], {\n \"PartName\": partName,\n \"ContentType\": contentType,\n }));\n}\n","/**\n * Shared helpers for locating a paragraph's containers — both in the FXP\n * tree (the parent children array holding its `w:p`) and in the normalized\n * view (the `doc.body` / `hf.paragraphs` / `cell.paragraphs` array holding\n * its NormalizedParagraph).\n *\n * Used by list ops, paragraph ops, and the engine for insertions and\n * removals that must keep both views in sync.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n NormalizedPageBreak,\n FXPNode,\n} from \"../types.js\";\nimport { findChild, getChildren, getTagName } from \"../xml/helpers.js\";\nimport { normalizeParagraph } from \"../normalizer/paragraph.js\";\nimport { enrichListType } from \"../normalizer/document.js\";\n\n/** Any array that holds normalized paragraphs (body, hf.paragraphs, cell.paragraphs). */\nexport type NormalizedContainerArray = Array<\n NormalizedParagraph | NormalizedTable | NormalizedPageBreak\n>;\n\n/**\n * Get the w:body children array from the document FXP tree.\n */\nexport function getBodyChildren(doc: NormalizedDocument): FXPNode[] {\n const docNode = doc.xmlParts.document.find(\n (n) => getTagName(n) === \"w:document\",\n );\n if (!docNode) throw new Error(\"Cannot find w:document in FXP tree\");\n const bodyNode = findChild(docNode, \"w:body\");\n if (!bodyNode) throw new Error(\"Cannot find w:body in FXP tree\");\n return getChildren(bodyNode);\n}\n\n/**\n * Insert FXP nodes into the body before w:sectPr, or after a specific element.\n */\nexport function insertIntoBody(\n bodyChildren: FXPNode[],\n nodes: FXPNode[],\n afterXmlRef?: FXPNode,\n): void {\n if (afterXmlRef) {\n const idx = bodyChildren.indexOf(afterXmlRef);\n if (idx !== -1) {\n bodyChildren.splice(idx + 1, 0, ...nodes);\n return;\n }\n }\n const sectPrIndex = bodyChildren.findIndex(\n (n) => getTagName(n) === \"w:sectPr\",\n );\n if (sectPrIndex !== -1) {\n bodyChildren.splice(sectPrIndex, 0, ...nodes);\n } else {\n bodyChildren.push(...nodes);\n }\n}\n\n/**\n * Compute the next available body paragraph ID index.\n */\nexport function getNextPIdx(doc: NormalizedDocument): number {\n let max = -1;\n for (const el of doc.body) {\n if (el.kind === \"paragraph\") {\n const num = parseInt(el.id.replace(\"p\", \"\"), 10);\n if (!isNaN(num) && num > max) max = num;\n }\n }\n return max + 1;\n}\n\n/**\n * Find the FXP parent children array that directly contains a paragraph's xmlRef.\n * Searches w:body, w:tc (table cells, recursively), and header/footer roots.\n */\nexport function findParentChildren(\n doc: NormalizedDocument,\n xmlRef: FXPNode,\n): FXPNode[] | undefined {\n // Check body\n const bodyChildren = getBodyChildren(doc);\n if (bodyChildren.includes(xmlRef)) return bodyChildren;\n\n // Check table cells in body\n for (const child of bodyChildren) {\n if (getTagName(child) === \"w:tbl\") {\n const result = findInTable(child, xmlRef);\n if (result) return result;\n }\n }\n\n // Check headers/footers\n for (const hf of [...doc.headers.values(), ...doc.footers.values()]) {\n const rootTag = hf.type === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const rootNode = hf.xmlRef.find((n) => getTagName(n) === rootTag);\n if (rootNode) {\n const rootChildren = getChildren(rootNode);\n if (rootChildren.includes(xmlRef)) return rootChildren;\n for (const child of rootChildren) {\n if (getTagName(child) === \"w:tbl\") {\n const result = findInTable(child, xmlRef);\n if (result) return result;\n }\n }\n }\n }\n\n return undefined;\n}\n\nfunction findInTable(tblNode: FXPNode, target: FXPNode): FXPNode[] | undefined {\n for (const tr of getChildren(tblNode).filter((n) => getTagName(n) === \"w:tr\")) {\n for (const tc of getChildren(tr).filter((n) => getTagName(n) === \"w:tc\")) {\n const tcChildren = getChildren(tc);\n if (tcChildren.includes(target)) return tcChildren;\n // Check nested tables\n for (const child of tcChildren) {\n if (getTagName(child) === \"w:tbl\") {\n const result = findInTable(child, target);\n if (result) return result;\n }\n }\n }\n }\n return undefined;\n}\n\n/**\n * Find the normalized paragraph array that contains a given paragraph ID.\n * Returns { array, index } for doc.body, hf.paragraphs, or cell.paragraphs\n * (searching nested tables recursively).\n */\nexport function findNormalizedContainer(\n doc: NormalizedDocument,\n paraId: string,\n): { array: NormalizedContainerArray; index: number } | undefined {\n // Check doc.body\n const bodyIdx = doc.body.findIndex((el) => el.id === paraId);\n if (bodyIdx !== -1) return { array: doc.body, index: bodyIdx };\n\n // Check header/footer paragraphs\n for (const hf of [...doc.headers.values(), ...doc.footers.values()]) {\n const hfIdx = hf.paragraphs.findIndex((p) => p.id === paraId);\n if (hfIdx !== -1) return { array: hf.paragraphs, index: hfIdx };\n }\n\n // Check table cell paragraphs (body tables + hf tables), recursing into nested tables\n const allTables: NormalizedTable[] = [];\n for (const el of doc.body) {\n if (el.kind === \"table\") allTables.push(el);\n }\n for (const hf of [...doc.headers.values(), ...doc.footers.values()]) {\n allTables.push(...hf.tables);\n }\n\n for (const table of allTables) {\n const found = findInNormalizedTable(table, paraId);\n if (found) return found;\n }\n\n return undefined;\n}\n\nfunction findInNormalizedTable(\n table: NormalizedTable,\n paraId: string,\n): { array: NormalizedContainerArray; index: number } | undefined {\n for (const row of table.rows) {\n for (const cell of row.cells) {\n const cellIdx = cell.paragraphs.findIndex((p) => p.id === paraId);\n if (cellIdx !== -1) return { array: cell.paragraphs, index: cellIdx };\n for (const nested of cell.nestedTables) {\n const found = findInNormalizedTable(nested, paraId);\n if (found) return found;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Derive a new paragraph ID in the same namespace as the target paragraph.\n * E.g., target \"hdr.default.p2\" → \"hdr.default.p3\",\n * target \"tbl0.r1.c0.p0\" → \"tbl0.r1.c0.p1\",\n * target \"p5\" → \"p6\" (body namespace).\n *\n * Scans sibling paragraphs in the container to find the next available index.\n */\nexport function deriveNewParagraphId(\n targetId: string,\n containerArray: readonly { id: string }[],\n): string {\n // Extract prefix: everything up to and including the last \"p\" before the trailing number\n const match = targetId.match(/^(.*p)(\\d+)$/);\n if (!match) {\n // Fallback — shouldn't happen for valid paragraph IDs\n return `p${Date.now()}`;\n }\n const prefix = match[1]!; // e.g. \"hdr.default.p\", \"tbl0.r1.c0.p\", \"p\"\n\n // Find the max index among siblings with the same prefix\n let maxIdx = -1;\n for (const el of containerArray) {\n if (el.id.startsWith(prefix)) {\n const suffix = el.id.slice(prefix.length);\n const num = parseInt(suffix, 10);\n if (!isNaN(num) && num > maxIdx) maxIdx = num;\n }\n }\n\n return `${prefix}${maxIdx + 1}`;\n}\n\n/**\n * Re-normalize a paragraph in place, enriching listType.\n */\nexport function reNormalizeParagraph(doc: NormalizedDocument, para: NormalizedParagraph): void {\n const fresh = normalizeParagraph(para.xmlRef, para.id);\n para.text = fresh.text;\n para.runs = fresh.runs;\n para.style = fresh.style;\n para.listInfo = fresh.listInfo;\n para.revisions = fresh.revisions;\n para.paraId = fresh.paraId;\n para.hasPageBreak = fresh.hasPageBreak;\n para.pageBreakOffset = fresh.pageBreakOffset;\n if (doc.xmlParts.numbering) {\n enrichListType(para, doc.xmlParts.numbering);\n }\n}\n","/**\n * List operation executors for the 6 list patch types.\n *\n * Each executor mutates the FXP tree directly and updates the\n * NormalizedDocument accordingly.\n */\n\nimport type {\n NormalizedDocument,\n FXPNode,\n AddListPatch,\n AddListItemPatch,\n RemoveListItemPatch,\n ConvertToListPatch,\n RemoveListPatch,\n SetListPropertiesPatch,\n} from \"../types.js\";\nimport { createNumberingDefinition } from \"./numbering.js\";\nimport { enrichListType } from \"../normalizer/document.js\";\nimport { normalizeParagraph } from \"../normalizer/paragraph.js\";\nimport {\n createElement,\n findChild,\n getChildren,\n getTagName,\n setAttr,\n cloneNode,\n} from \"../xml/helpers.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport { findParagraphById, findElementById } from \"./validator.js\";\nimport { createInsElement, createPPrChange, convertRunToDeleted, createDelElement, type RevisionContext } from \"./revision.js\";\nimport {\n getBodyChildren,\n insertIntoBody,\n getNextPIdx,\n findParentChildren,\n findNormalizedContainer,\n deriveNewParagraphId,\n reNormalizeParagraph as reNormalize,\n} from \"./container-utils.js\";\nimport { stampNewParagraphs, findPartRootForId } from \"../normalizer/para-id.js\";\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/**\n * Build a w:p node with w:numPr for a list item.\n */\nfunction buildListParagraphNode(\n text: string,\n numId: string,\n level: number,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): FXPNode {\n const numPr = createElement(\"w:numPr\", [\n createElement(\"w:ilvl\", [], { \"w:val\": String(level) }),\n createElement(\"w:numId\", [], { \"w:val\": numId }),\n ]);\n const pPr = createElement(\"w:pPr\", [numPr]);\n\n const runs = buildFieldRuns(text, undefined, markers);\n let children: FXPNode[];\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n children = [pPr, insElement];\n } else {\n children = [pPr, ...runs];\n }\n\n return { \"w:p\": children };\n}\n\n// =============================================================================\n// Executors\n// =============================================================================\n\nexport function executeAddList(\n doc: NormalizedDocument,\n patch: AddListPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n const numId = patch.numId ?? createNumberingDefinition(doc, patch.listType);\n const defaultLevel = patch.level ?? 0;\n const nodes: FXPNode[] = [];\n\n for (const item of patch.items) {\n const text = typeof item === \"string\" ? item : item.text;\n const level = typeof item === \"string\" ? defaultLevel : (item.level ?? defaultLevel);\n nodes.push(buildListParagraphNode(text, numId, level, revCtx, markers));\n }\n\n if (patch.container) {\n // Insert into header/footer\n const map = patch.container.type === \"header\" ? doc.headers : doc.footers;\n for (const hf of map.values()) {\n if (hf.variant === patch.container.variant) {\n const rootTag = hf.type === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const rootNode = hf.xmlRef.find((n) => getTagName(n) === rootTag);\n if (rootNode) {\n const rootChildren = getChildren(rootNode);\n stampNewParagraphs(doc, nodes, rootNode);\n rootChildren.push(...nodes);\n const prefix = patch.container.type === \"header\" ? \"hdr\" : \"ftr\";\n let nextPIdx = hf.paragraphs.length;\n for (const node of nodes) {\n const normalized = normalizeParagraph(\n node,\n `${prefix}.${patch.container.variant}.p${nextPIdx++}`,\n );\n if (doc.xmlParts.numbering) enrichListType(normalized, doc.xmlParts.numbering);\n hf.paragraphs.push(normalized);\n }\n }\n return;\n }\n }\n return;\n }\n\n const bodyChildren = getBodyChildren(doc);\n let afterXmlRef: FXPNode | undefined;\n let afterElementId: string | undefined;\n if (patch.afterId) {\n const target = findElementById(patch.afterId, doc);\n if (target) {\n afterXmlRef = target.xmlRef;\n afterElementId = target.id;\n }\n }\n\n stampNewParagraphs(doc, nodes, findPartRootForId(doc, \"body\"));\n insertIntoBody(bodyChildren, nodes, afterXmlRef);\n\n // Normalize and add to doc.body\n let nextPIdx = getNextPIdx(doc);\n const insertIdx = afterElementId !== undefined\n ? doc.body.findIndex((el) => el.id === afterElementId)\n : -1;\n\n for (let i = 0; i < nodes.length; i++) {\n const id = `p${nextPIdx++}`;\n const normalized = normalizeParagraph(nodes[i]!, id);\n if (doc.xmlParts.numbering) enrichListType(normalized, doc.xmlParts.numbering);\n if (insertIdx !== -1) {\n doc.body.splice(insertIdx + 1 + i, 0, normalized);\n } else {\n doc.body.push(normalized);\n }\n }\n}\n\nexport function executeAddListItem(\n doc: NormalizedDocument,\n patch: AddListItemPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): void {\n const targetPara = findParagraphById(patch.target, doc)!;\n const numId = targetPara.listInfo!.numId;\n const level = patch.level ?? targetPara.listInfo!.level;\n\n const newNode = buildListParagraphNode(patch.text, numId, level, revCtx, markers);\n stampNewParagraphs(doc, [newNode], findPartRootForId(doc, targetPara.id));\n\n // Find the parent FXP container\n const parentChildren = findParentChildren(doc, targetPara.xmlRef);\n if (!parentChildren) throw new Error(\"Cannot find parent container for target paragraph\");\n\n const targetIdx = parentChildren.indexOf(targetPara.xmlRef);\n if (targetIdx === -1) throw new Error(\"Target xmlRef not found in parent\");\n\n if (patch.position === \"after\") {\n parentChildren.splice(targetIdx + 1, 0, newNode);\n } else {\n parentChildren.splice(targetIdx, 0, newNode);\n }\n\n // Normalize and insert into the correct normalized container\n const container = findNormalizedContainer(doc, targetPara.id);\n const containerArray = container ? container.array : doc.body;\n const id = deriveNewParagraphId(targetPara.id, containerArray);\n const normalized = normalizeParagraph(newNode, id);\n if (doc.xmlParts.numbering) enrichListType(normalized, doc.xmlParts.numbering);\n\n if (container) {\n if (patch.position === \"after\") {\n container.array.splice(container.index + 1, 0, normalized);\n } else {\n container.array.splice(container.index, 0, normalized);\n }\n } else {\n doc.body.push(normalized);\n }\n}\n\nexport function executeRemoveListItem(\n doc: NormalizedDocument,\n patch: RemoveListItemPatch,\n revCtx?: RevisionContext,\n): void {\n const targetPara = findParagraphById(patch.target, doc)!;\n\n if (revCtx) {\n // Mark runs as deleted instead of removing\n const children = getChildren(targetPara.xmlRef);\n // Store old pPr for pPrChange\n const oldPPr = findChild(targetPara.xmlRef, \"w:pPr\");\n if (oldPPr) {\n const pPrChange = createPPrChange(revCtx, oldPPr);\n const pPrChildren = getChildren(oldPPr);\n pPrChildren.push(pPrChange);\n }\n // Wrap runs in w:del — handle both direct w:r and w:r nested inside w:ins\n for (let i = children.length - 1; i >= 0; i--) {\n const child = children[i]!;\n const tag = getTagName(child);\n if (tag === \"w:r\") {\n // Clone the run and convert w:t → w:delText for valid OOXML\n const cloned = cloneNode(child);\n convertRunToDeleted(cloned);\n const delElement = createDelElement(revCtx, [cloned]);\n children[i] = delElement;\n } else if (tag === \"w:ins\") {\n // Inserted but not accepted text: removing the w:ins entirely is the\n // correct OOXML semantics (the content was never part of the accepted\n // document, so wrapping it in w:del would corrupt the file).\n children.splice(i, 1);\n }\n }\n reNormalize(doc, targetPara);\n return;\n }\n\n // Physical removal\n const parentChildren = findParentChildren(doc, targetPara.xmlRef);\n if (parentChildren) {\n const idx = parentChildren.indexOf(targetPara.xmlRef);\n if (idx !== -1) parentChildren.splice(idx, 1);\n }\n\n // Remove from the correct normalized container (body, hf.paragraphs, or cell.paragraphs)\n const container = findNormalizedContainer(doc, targetPara.id);\n if (container) {\n container.array.splice(container.index, 1);\n }\n}\n\nexport function executeConvertToList(\n doc: NormalizedDocument,\n patch: ConvertToListPatch,\n revCtx?: RevisionContext,\n): void {\n const numId = createNumberingDefinition(doc, patch.listType);\n const level = patch.level ?? 0;\n\n for (const target of patch.targets) {\n const para = findParagraphById(target, doc)!;\n const pNode = para.xmlRef;\n\n // Find or create w:pPr\n let pPr = findChild(pNode, \"w:pPr\");\n const pChildren = getChildren(pNode);\n\n if (revCtx && pPr) {\n const pPrChange = createPPrChange(revCtx, pPr);\n getChildren(pPr).push(pPrChange);\n }\n\n if (!pPr) {\n pPr = createElement(\"w:pPr\", []);\n pChildren.unshift(pPr);\n }\n\n const pPrChildren = getChildren(pPr);\n\n // Remove existing w:numPr if present\n const existingIdx = pPrChildren.findIndex((c) => getTagName(c) === \"w:numPr\");\n if (existingIdx !== -1) pPrChildren.splice(existingIdx, 1);\n\n // Add new w:numPr\n const numPr = createElement(\"w:numPr\", [\n createElement(\"w:ilvl\", [], { \"w:val\": String(level) }),\n createElement(\"w:numId\", [], { \"w:val\": numId }),\n ]);\n pPrChildren.unshift(numPr);\n\n reNormalize(doc, para);\n }\n}\n\nexport function executeRemoveList(\n doc: NormalizedDocument,\n patch: RemoveListPatch,\n revCtx?: RevisionContext,\n): void {\n for (const target of patch.targets) {\n const para = findParagraphById(target, doc)!;\n const pPr = findChild(para.xmlRef, \"w:pPr\");\n if (!pPr) continue;\n\n if (revCtx) {\n const pPrChange = createPPrChange(revCtx, pPr);\n getChildren(pPr).push(pPrChange);\n }\n\n const pPrChildren = getChildren(pPr);\n const numPrIdx = pPrChildren.findIndex((c) => getTagName(c) === \"w:numPr\");\n if (numPrIdx !== -1) pPrChildren.splice(numPrIdx, 1);\n\n reNormalize(doc, para);\n }\n}\n\nexport function executeSetListProperties(\n doc: NormalizedDocument,\n patch: SetListPropertiesPatch,\n revCtx?: RevisionContext,\n): void {\n const para = findParagraphById(patch.target, doc)!;\n const pPr = findChild(para.xmlRef, \"w:pPr\");\n if (!pPr) throw new Error(\"No w:pPr found on target paragraph\");\n\n if (revCtx) {\n const pPrChange = createPPrChange(revCtx, pPr);\n getChildren(pPr).push(pPrChange);\n }\n\n const numPr = findChild(pPr, \"w:numPr\");\n if (!numPr) throw new Error(\"No w:numPr found on target paragraph\");\n\n if (patch.level !== undefined) {\n const ilvl = findChild(numPr, \"w:ilvl\");\n if (ilvl) {\n setAttr(ilvl, \"w:val\", String(patch.level));\n } else {\n getChildren(numPr).unshift(\n createElement(\"w:ilvl\", [], { \"w:val\": String(patch.level) }),\n );\n }\n }\n\n if (patch.listType !== undefined) {\n const newNumId = createNumberingDefinition(doc, patch.listType);\n const numIdNode = findChild(numPr, \"w:numId\");\n if (numIdNode) {\n setAttr(numIdNode, \"w:val\", newNumId);\n } else {\n getChildren(numPr).push(\n createElement(\"w:numId\", [], { \"w:val\": newNumId }),\n );\n }\n }\n\n reNormalize(doc, para);\n}\n","/**\n * Whole-paragraph operations: insert_paragraph and delete_paragraph.\n *\n * insert_paragraph creates one or more sibling `w:p` nodes before or after a\n * target paragraph (in the body, a table cell, or a header/footer), optionally\n * inheriting the target's paragraph properties and run formatting. With\n * revision tracking, runs are wrapped in `w:ins` and the paragraph mark itself\n * carries `w:pPr/w:rPr/w:ins` so that accepting the change does not leave a\n * stray empty paragraph behind.\n *\n * delete_paragraph removes a paragraph outright, or with revision tracking\n * marks every run deleted and puts `w:del` on the paragraph mark — Word's\n * convention for a deleted paragraph.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n InsertParagraphPatch,\n DeleteParagraphPatch,\n FXPNode,\n} from \"../types.js\";\nimport {\n cloneNode,\n createElement,\n findChild,\n getAttr,\n getChildren,\n getTagName,\n} from \"../xml/helpers.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport { normalizeParagraph } from \"../normalizer/paragraph.js\";\nimport { enrichListType } from \"../normalizer/document.js\";\nimport {\n createInsElement,\n createDelElement,\n convertRunToDeleted,\n nextRevisionId,\n type RevisionContext,\n} from \"./revision.js\";\nimport {\n findParentChildren,\n findNormalizedContainer,\n deriveNewParagraphId,\n reNormalizeParagraph,\n} from \"./container-utils.js\";\nimport { stampNewParagraphs, findPartRootForId } from \"../normalizer/para-id.js\";\n\n// =============================================================================\n// Style inheritance\n// =============================================================================\n\n/** w:pPr children that must not be copied onto a new paragraph. */\nconst PPR_EXCLUDE = new Set([\"w:sectPr\", \"w:pPrChange\", \"w:rPr\"]);\n/** Paragraph-mark w:rPr children that are revision state, not formatting. */\nconst PARA_MARK_REVISION = new Set([\"w:ins\", \"w:del\", \"w:moveFrom\", \"w:moveTo\", \"w:rPrChange\"]);\n\n/**\n * Clone the target's w:pPr for a new sibling paragraph: paragraph style,\n * numbering, indentation, spacing, and the paragraph-mark run properties —\n * minus section breaks and revision bookkeeping.\n */\nfunction buildInheritedPPr(targetP: FXPNode): FXPNode | undefined {\n const pPr = findChild(targetP, \"w:pPr\");\n if (!pPr) return undefined;\n\n const children: FXPNode[] = [];\n let markRPr: FXPNode | undefined;\n\n for (const child of getChildren(pPr)) {\n const tag = getTagName(child);\n if (!tag) continue;\n if (tag === \"w:rPr\") {\n const kept = getChildren(child)\n .filter((c) => {\n const t = getTagName(c);\n return t !== undefined && !PARA_MARK_REVISION.has(t);\n })\n .map(cloneNode);\n if (kept.length > 0) markRPr = createElement(\"w:rPr\", kept);\n continue;\n }\n if (PPR_EXCLUDE.has(tag)) continue;\n children.push(cloneNode(child));\n }\n\n // w:rPr is the last element of CT_PPr apart from sectPr/pPrChange\n if (markRPr) children.push(markRPr);\n if (children.length === 0) return undefined;\n return createElement(\"w:pPr\", children);\n}\n\n/**\n * Run formatting for the new text: the target's last run's w:rPr (what Word\n * would give you if you pressed Enter at the end of the paragraph and typed).\n */\nfunction buildInheritedRunRPr(target: NormalizedParagraph): FXPNode | undefined {\n const lastRun = target.runs[target.runs.length - 1];\n if (!lastRun) return undefined;\n const rPr = findChild(lastRun.xmlRef, \"w:rPr\");\n if (!rPr) return undefined;\n const kept = getChildren(rPr)\n .filter((c) => getTagName(c) !== \"w:rPrChange\")\n .map(cloneNode);\n if (kept.length === 0) return undefined;\n return createElement(\"w:rPr\", kept);\n}\n\n// =============================================================================\n// Paragraph mark revisions\n// =============================================================================\n\n/**\n * Add a `w:ins` or `w:del` marker to a paragraph's mark (`w:pPr/w:rPr`).\n * Creates w:pPr / w:rPr as needed, keeping schema order.\n */\nexport function addParagraphMarkRevision(\n pNode: FXPNode,\n kind: \"ins\" | \"del\",\n revCtx: RevisionContext,\n): void {\n let pPr = findChild(pNode, \"w:pPr\");\n if (!pPr) {\n pPr = createElement(\"w:pPr\", []);\n getChildren(pNode).unshift(pPr);\n }\n const pPrChildren = getChildren(pPr);\n\n let rPr = findChild(pPr, \"w:rPr\");\n if (!rPr) {\n rPr = createElement(\"w:rPr\", []);\n const tailIdx = pPrChildren.findIndex((c) => {\n const t = getTagName(c);\n return t === \"w:sectPr\" || t === \"w:pPrChange\";\n });\n if (tailIdx === -1) pPrChildren.push(rPr);\n else pPrChildren.splice(tailIdx, 0, rPr);\n }\n\n const marker = createElement(`w:${kind}`, [], {\n \"w:id\": nextRevisionId(revCtx),\n \"w:author\": revCtx.author,\n \"w:date\": revCtx.date,\n });\n // w:ins / w:del are the first children of CT_ParaRPr\n getChildren(rPr).unshift(marker);\n}\n\n/** Find a paragraph-mark revision marker (`w:pPr/w:rPr/w:ins|w:del`). */\nfunction findParagraphMarkMarker(pNode: FXPNode, kind: \"ins\" | \"del\"): FXPNode | undefined {\n const pPr = findChild(pNode, \"w:pPr\");\n if (!pPr) return undefined;\n const rPr = findChild(pPr, \"w:rPr\");\n if (!rPr) return undefined;\n return findChild(rPr, `w:${kind}`);\n}\n\n// =============================================================================\n// insert_paragraph\n// =============================================================================\n\n/**\n * Build the new w:p nodes for an insert_paragraph patch.\n */\nfunction buildParagraphNodes(\n target: NormalizedParagraph,\n patch: InsertParagraphPatch,\n revCtx: RevisionContext | undefined,\n markers: boolean,\n): FXPNode[] {\n const inherit = patch.inheritStyle ?? true;\n const pPrTemplate = inherit ? buildInheritedPPr(target.xmlRef) : undefined;\n const runRPr = inherit ? buildInheritedRunRPr(target) : undefined;\n\n const lines = patch.text.split(\"\\n\");\n const nodes: FXPNode[] = [];\n\n for (const line of lines) {\n const children: FXPNode[] = [];\n if (pPrTemplate) children.push(cloneNode(pPrTemplate));\n\n const runs = buildFieldRuns(line, runRPr, markers);\n if (revCtx) {\n if (runs.length > 0) children.push(createInsElement(revCtx, runs));\n } else {\n children.push(...runs);\n }\n\n const pNode: FXPNode = { \"w:p\": children };\n if (revCtx) addParagraphMarkRevision(pNode, \"ins\", revCtx);\n nodes.push(pNode);\n }\n\n return nodes;\n}\n\n/**\n * Execute an insert_paragraph patch.\n *\n * The target is resolved by the caller (positional or `para:` id); the new\n * paragraphs are inserted as siblings in the same container — body, table\n * cell, or header/footer — and added to the normalized view with the next\n * free positional ids so they are addressable later in the same batch.\n *\n * @returns the normalized paragraphs that were created\n */\nexport function executeInsertParagraph(\n doc: NormalizedDocument,\n target: NormalizedParagraph,\n patch: InsertParagraphPatch,\n revCtx?: RevisionContext,\n markers: boolean = true,\n): NormalizedParagraph[] {\n const parentChildren = findParentChildren(doc, target.xmlRef);\n if (!parentChildren) {\n throw new Error(`Cannot find parent container for paragraph \"${target.id}\"`);\n }\n const targetIdx = parentChildren.indexOf(target.xmlRef);\n if (targetIdx === -1) {\n throw new Error(`Paragraph \"${target.id}\" not found in its parent`);\n }\n\n const nodes = buildParagraphNodes(target, patch, revCtx, markers);\n stampNewParagraphs(doc, nodes, findPartRootForId(doc, target.id));\n\n // XML insertion\n const xmlInsertAt = patch.position === \"after\" ? targetIdx + 1 : targetIdx;\n parentChildren.splice(xmlInsertAt, 0, ...nodes);\n\n // Normalized insertion\n const container = findNormalizedContainer(doc, target.id);\n if (!container) {\n throw new Error(`Paragraph \"${target.id}\" not found in normalized document`);\n }\n const normInsertAt = patch.position === \"after\" ? container.index + 1 : container.index;\n\n const created: NormalizedParagraph[] = [];\n for (let i = 0; i < nodes.length; i++) {\n const id = deriveNewParagraphId(target.id, container.array);\n const normalized = normalizeParagraph(nodes[i]!, id);\n if (doc.xmlParts.numbering) enrichListType(normalized, doc.xmlParts.numbering);\n container.array.splice(normInsertAt + i, 0, normalized);\n created.push(normalized);\n }\n\n return created;\n}\n\n// =============================================================================\n// delete_paragraph\n// =============================================================================\n\n/** Inline containers whose runs should be marked deleted recursively. */\nconst INLINE_CONTAINERS = new Set([\n \"w:hyperlink\",\n \"w:smartTag\",\n \"w:customXml\",\n \"w:dir\",\n \"w:bdo\",\n \"w:moveTo\",\n \"w:sdtContent\",\n \"w:fldSimple\",\n]);\n\n/**\n * Mark every run among `children` as deleted, in place.\n *\n * - Consecutive `w:r` siblings are grouped into one `w:del`.\n * - `w:ins` by the same author is removed outright (it was never part of the\n * accepted document); `w:ins` by another author gets `w:del` nested inside.\n * - Existing `w:del` / `w:moveFrom` content is left alone.\n * - Inline containers (hyperlinks, smart tags, content controls) are recursed.\n */\nexport function markRunsDeleted(children: FXPNode[], revCtx: RevisionContext): void {\n let i = 0;\n while (i < children.length) {\n const child = children[i]!;\n const tag = getTagName(child);\n\n if (tag === \"w:r\") {\n // Gather a group of consecutive runs\n let j = i;\n while (j < children.length && getTagName(children[j]!) === \"w:r\") j++;\n const group = children.slice(i, j);\n for (const run of group) convertRunToDeleted(run);\n children.splice(i, j - i, createDelElement(revCtx, group));\n i++;\n continue;\n }\n\n if (tag === \"w:ins\") {\n if (getAttr(child, \"w:author\") === revCtx.author) {\n children.splice(i, 1);\n continue;\n }\n markRunsDeleted(getChildren(child), revCtx);\n i++;\n continue;\n }\n\n if (tag === \"w:sdt\") {\n const content = findChild(child, \"w:sdtContent\");\n if (content) markRunsDeleted(getChildren(content), revCtx);\n i++;\n continue;\n }\n\n if (tag && INLINE_CONTAINERS.has(tag)) {\n markRunsDeleted(getChildren(child), revCtx);\n }\n // w:del, w:moveFrom, w:pPr, bookmarks, proofErr, comment ranges: untouched\n i++;\n }\n}\n\n/**\n * True if every run in the paragraph lives inside a `w:ins` by `author` and\n * the paragraph mark is also an insertion by `author` — i.e. the paragraph is\n * wholly our own unaccepted insertion and can simply be removed.\n */\nfunction isWhollyOwnInsertion(pNode: FXPNode, author: string): boolean {\n const mark = findParagraphMarkMarker(pNode, \"ins\");\n if (!mark || getAttr(mark, \"w:author\") !== author) return false;\n\n const check = (children: FXPNode[]): boolean => {\n for (const child of children) {\n const tag = getTagName(child);\n if (tag === \"w:r\") return false;\n if (tag === \"w:ins\") {\n if (getAttr(child, \"w:author\") !== author) return false;\n continue;\n }\n if (tag === \"w:del\" || tag === \"w:moveFrom\") return false;\n if (tag === \"w:sdt\") {\n const content = findChild(child, \"w:sdtContent\");\n if (content && !check(getChildren(content))) return false;\n continue;\n }\n if (tag && INLINE_CONTAINERS.has(tag)) {\n if (!check(getChildren(child))) return false;\n }\n }\n return true;\n };\n return check(getChildren(pNode));\n}\n\n/**\n * Physically remove a paragraph from the XML and the normalized view.\n *\n * Every story (table cell, header, footer, body) must keep at least one\n * block element, so the last paragraph of a container is emptied instead\n * of removed. For the body, a table counts as a block element too.\n */\nfunction removeParagraph(doc: NormalizedDocument, para: NormalizedParagraph): void {\n const parentChildren = findParentChildren(doc, para.xmlRef);\n if (!parentChildren) {\n throw new Error(`Cannot find parent container for paragraph \"${para.id}\"`);\n }\n const container = findNormalizedContainer(doc, para.id);\n if (!container) {\n throw new Error(`Paragraph \"${para.id}\" not found in normalized document`);\n }\n\n const isBody = container.array === doc.body;\n const blockCount = parentChildren.filter((c) => {\n const t = getTagName(c);\n return t === \"w:p\" || (isBody && t === \"w:tbl\");\n }).length;\n\n if (blockCount === 1) {\n // Keep the story valid: strip content, keep w:pPr\n const pChildren = getChildren(para.xmlRef);\n const pPr = findChild(para.xmlRef, \"w:pPr\");\n pChildren.length = 0;\n if (pPr) pChildren.push(pPr);\n reNormalizeParagraph(doc, para);\n return;\n }\n\n const idx = parentChildren.indexOf(para.xmlRef);\n if (idx !== -1) parentChildren.splice(idx, 1);\n container.array.splice(container.index, 1);\n}\n\n/**\n * Execute a delete_paragraph patch.\n *\n * With revision tracking: all runs are wrapped in `w:del` (w:t → w:delText),\n * and the paragraph mark gets `w:pPr/w:rPr/w:del`. Runs already inside a\n * `w:ins` by the same author are removed rather than double-marked; if the\n * whole paragraph is the author's own unaccepted insertion, it is removed.\n *\n * Without tracking: the `w:p` is removed.\n */\nexport function executeDeleteParagraph(\n doc: NormalizedDocument,\n target: NormalizedParagraph,\n _patch: DeleteParagraphPatch,\n revCtx?: RevisionContext,\n): void {\n if (!revCtx) {\n removeParagraph(doc, target);\n return;\n }\n\n if (isWhollyOwnInsertion(target.xmlRef, revCtx.author)) {\n removeParagraph(doc, target);\n return;\n }\n\n markRunsDeleted(getChildren(target.xmlRef), revCtx);\n\n // Don't double-mark a mark that is already deleted\n if (!findParagraphMarkMarker(target.xmlRef, \"del\")) {\n addParagraphMarkRevision(target.xmlRef, \"del\", revCtx);\n }\n\n reNormalizeParagraph(doc, target);\n}\n","/**\n * Helpers for OPC package plumbing: relationships and content types.\n *\n * Used when a part is created on demand (comments.xml, core.xml) or removed\n * (custom.xml). Works on either relationship part — the package-level\n * `_rels/.rels` or the main document's `word/_rels/document.xml.rels`.\n */\n\nimport type { FXPNode } from \"../types.js\";\nimport { createElement, findChildren, getAttr, getChildren, getTagName } from \"../xml/helpers.js\";\n\nconst RELS_NAMESPACE = \"http://schemas.openxmlformats.org/package/2006/relationships\";\nconst CONTENT_TYPES_NAMESPACE = \"http://schemas.openxmlformats.org/package/2006/content-types\";\n\n/** Create an empty Relationships part. */\nexport function createRelsPart(): FXPNode[] {\n return [\n { \"?xml\": [{ \"#text\": \"\" } as unknown as FXPNode], \":@\": { \"@_version\": \"1.0\", \"@_encoding\": \"UTF-8\", \"@_standalone\": \"yes\" } } as FXPNode,\n createElement(\"Relationships\", [], { xmlns: RELS_NAMESPACE }),\n ];\n}\n\n/** Create an empty [Content_Types] part with the standard defaults. */\nexport function createContentTypesPart(): FXPNode[] {\n return [\n createElement(\"Types\", [\n createElement(\"Default\", [], { Extension: \"rels\", ContentType: \"application/vnd.openxmlformats-package.relationships+xml\" }),\n createElement(\"Default\", [], { Extension: \"xml\", ContentType: \"application/xml\" }),\n ], { xmlns: CONTENT_TYPES_NAMESPACE }),\n ];\n}\n\nfunction getRelsRoot(rels: FXPNode[]): FXPNode | undefined {\n return rels.find((n) => getTagName(n) === \"Relationships\");\n}\n\nfunction getTypesRoot(contentTypes: FXPNode[]): FXPNode | undefined {\n return contentTypes.find((n) => getTagName(n) === \"Types\");\n}\n\n/** Next unused `rIdN` in a relationships part. */\nexport function getNextRelationshipId(rels: FXPNode[]): string {\n let maxId = 0;\n const root = getRelsRoot(rels);\n if (root) {\n for (const rel of findChildren(root, \"Relationship\")) {\n const id = getAttr(rel, \"Id\");\n if (id) {\n const num = parseInt(id.replace(/^rId/, \"\"), 10);\n if (!isNaN(num) && num > maxId) maxId = num;\n }\n }\n }\n return `rId${maxId + 1}`;\n}\n\n/** Find a relationship by type, returning its target. */\nexport function findRelationshipByType(\n rels: FXPNode[] | undefined,\n type: string,\n): { id: string; target: string } | undefined {\n if (!rels) return undefined;\n const root = getRelsRoot(rels);\n if (!root) return undefined;\n for (const rel of findChildren(root, \"Relationship\")) {\n if (getAttr(rel, \"Type\") === type) {\n return { id: getAttr(rel, \"Id\") ?? \"\", target: getAttr(rel, \"Target\") ?? \"\" };\n }\n }\n return undefined;\n}\n\n/**\n * Ensure a relationship of the given type and target exists. Returns its Id.\n */\nexport function ensureRelationship(\n rels: FXPNode[],\n type: string,\n target: string,\n): string {\n const root = getRelsRoot(rels);\n if (!root) throw new Error(\"Relationships root not found\");\n for (const rel of findChildren(root, \"Relationship\")) {\n if (getAttr(rel, \"Type\") === type && getAttr(rel, \"Target\") === target) {\n return getAttr(rel, \"Id\") ?? \"\";\n }\n }\n const id = getNextRelationshipId(rels);\n getChildren(root).push(createElement(\"Relationship\", [], { Id: id, Type: type, Target: target }));\n return id;\n}\n\n/** Remove every relationship of the given type. Returns the removed targets. */\nexport function removeRelationshipsByType(\n rels: FXPNode[] | undefined,\n type: string,\n): string[] {\n if (!rels) return [];\n const root = getRelsRoot(rels);\n if (!root) return [];\n const children = getChildren(root);\n const removed: string[] = [];\n for (let i = children.length - 1; i >= 0; i--) {\n const child = children[i]!;\n if (getTagName(child) === \"Relationship\" && getAttr(child, \"Type\") === type) {\n removed.push(getAttr(child, \"Target\") ?? \"\");\n children.splice(i, 1);\n }\n }\n return removed;\n}\n\n/** Ensure an Override for `partName` (leading slash) exists in [Content_Types].xml. */\nexport function ensureContentTypeOverride(\n contentTypes: FXPNode[],\n partName: string,\n contentType: string,\n): void {\n const root = getTypesRoot(contentTypes);\n if (!root) return;\n const normalized = partName.startsWith(\"/\") ? partName : `/${partName}`;\n const exists = findChildren(root, \"Override\").some(\n (o) => getAttr(o, \"PartName\") === normalized,\n );\n if (!exists) {\n getChildren(root).push(createElement(\"Override\", [], { PartName: normalized, ContentType: contentType }));\n }\n}\n\n/** Remove the Override for `partName` (with or without leading slash). */\nexport function removeContentTypeOverride(\n contentTypes: FXPNode[] | undefined,\n partName: string,\n): void {\n if (!contentTypes) return;\n const root = getTypesRoot(contentTypes);\n if (!root) return;\n const children = getChildren(root);\n const normalized = partName.startsWith(\"/\") ? partName : `/${partName}`;\n const idx = children.findIndex(\n (c) => getTagName(c) === \"Override\" && getAttr(c, \"PartName\") === normalized,\n );\n if (idx !== -1) children.splice(idx, 1);\n}\n","/**\n * add_comment: attach a Word comment to a paragraph or a substring of it.\n *\n * Creates `word/comments.xml` on demand (with its relationship and content\n * type), appends a `w:comment`, and anchors it in the paragraph with\n * `w:commentRangeStart` / `w:commentRangeEnd` plus a `w:commentReference` run.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n AddCommentPatch,\n FXPNode,\n} from \"../types.js\";\nimport {\n createElement,\n findChild,\n findChildren,\n getAttr,\n getChildren,\n getTagName,\n} from \"../xml/helpers.js\";\nimport { findOccurrence, resolveRunRanges } from \"../mapping/text-to-runs.js\";\nimport { splitRunsForMatch, findRunParentChildren } from \"./run-splitter.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport { stripFormattingMarkers } from \"./markdown-parser.js\";\nimport {\n createRelsPart,\n createContentTypesPart,\n ensureRelationship,\n ensureContentTypeOverride,\n} from \"./part-utils.js\";\n\nconst COMMENTS_REL_TYPE =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments\";\nconst COMMENTS_CONTENT_TYPE =\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml\";\n\nexport interface CommentOptions {\n /** Author to use when the patch does not name one. */\n defaultAuthor: string;\n /** ISO 8601 date for the comment. */\n date: string;\n /** Parse inline formatting markers in `find` and `text`. Default: true. */\n markers?: boolean;\n}\n\n/**\n * Ensure word/comments.xml exists (as a parsed part) and is wired into the\n * package. Returns the `w:comments` root node.\n */\nexport function ensureCommentsPart(doc: NormalizedDocument): FXPNode {\n if (!doc.xmlParts.comments) {\n doc.xmlParts.comments = [\n { \"?xml\": [{ \"#text\": \"\" } as unknown as FXPNode], \":@\": { \"@_version\": \"1.0\", \"@_encoding\": \"UTF-8\", \"@_standalone\": \"yes\" } } as FXPNode,\n createElement(\"w:comments\", [], {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n \"xmlns:r\": \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n }),\n ];\n\n if (!doc.xmlParts.documentRels) doc.xmlParts.documentRels = createRelsPart();\n ensureRelationship(doc.xmlParts.documentRels, COMMENTS_REL_TYPE, \"comments.xml\");\n\n if (!doc.xmlParts.contentTypes) doc.xmlParts.contentTypes = createContentTypesPart();\n ensureContentTypeOverride(doc.xmlParts.contentTypes, \"/word/comments.xml\", COMMENTS_CONTENT_TYPE);\n }\n\n const root = doc.xmlParts.comments.find((n) => getTagName(n) === \"w:comments\");\n if (!root) throw new Error(\"word/comments.xml has no w:comments root\");\n return root;\n}\n\n/** Next unused comment id (max existing + 1, starting at 0). */\nfunction nextCommentId(commentsRoot: FXPNode): string {\n let max = -1;\n for (const c of findChildren(commentsRoot, \"w:comment\")) {\n const id = parseInt(getAttr(c, \"w:id\") ?? \"\", 10);\n if (!isNaN(id) && id > max) max = id;\n }\n return String(max + 1);\n}\n\n/** Initials from an author name: first letter of each word, up to three. */\nexport function deriveInitials(author: string): string {\n const words = author.split(/\\s+/).filter((w) => w.length > 0);\n const initials = words.map((w) => w[0]!.toUpperCase()).join(\"\");\n return initials.slice(0, 3) || \"A\";\n}\n\n/**\n * Build the `w:comment` element with its body paragraphs.\n */\nfunction buildCommentElement(\n id: string,\n author: string,\n initials: string,\n date: string,\n text: string,\n doc: NormalizedDocument,\n markers: boolean,\n): FXPNode {\n const hasCommentTextStyle = doc.styles.some((s) => s.id === \"CommentText\");\n const hasCommentRefStyle = doc.styles.some((s) => s.id === \"CommentReference\");\n\n const paragraphs = text.split(\"\\n\").map((line, idx) => {\n const children: FXPNode[] = [];\n if (hasCommentTextStyle) {\n children.push(createElement(\"w:pPr\", [createElement(\"w:pStyle\", [], { \"w:val\": \"CommentText\" })]));\n }\n if (idx === 0) {\n const refRun: FXPNode[] = [];\n if (hasCommentRefStyle) {\n refRun.push(createElement(\"w:rPr\", [createElement(\"w:rStyle\", [], { \"w:val\": \"CommentReference\" })]));\n }\n refRun.push(createElement(\"w:annotationRef\", []));\n children.push({ \"w:r\": refRun });\n }\n children.push(...buildFieldRuns(line, undefined, markers));\n return { \"w:p\": children } as FXPNode;\n });\n\n return createElement(\"w:comment\", paragraphs, {\n \"w:id\": id,\n \"w:author\": author,\n \"w:date\": date,\n \"w:initials\": initials,\n });\n}\n\n/** The `w:r` holding the comment reference mark. */\nfunction buildReferenceRun(id: string, doc: NormalizedDocument): FXPNode {\n const children: FXPNode[] = [];\n if (doc.styles.some((s) => s.id === \"CommentReference\")) {\n children.push(createElement(\"w:rPr\", [createElement(\"w:rStyle\", [], { \"w:val\": \"CommentReference\" })]));\n }\n children.push(createElement(\"w:commentReference\", [], { \"w:id\": id }));\n return { \"w:r\": children };\n}\n\n/**\n * Execute an add_comment patch on a paragraph.\n */\nexport function executeAddComment(\n doc: NormalizedDocument,\n paragraph: NormalizedParagraph,\n patch: AddCommentPatch,\n options: CommentOptions,\n): void {\n const author = patch.author && patch.author.length > 0 ? patch.author : options.defaultAuthor;\n const initials = patch.initials ?? deriveInitials(author);\n\n // Resolve the anchor before touching the package, so a failed patch has\n // no side effects (no empty comments part, relationship or content type).\n let matchedRuns: FXPNode[] | undefined;\n if (patch.find !== undefined && patch.find.length > 0) {\n const occurrence = patch.occurrence ?? 1;\n const plainFind = stripFormattingMarkers(patch.find, options.markers ?? true);\n const matchStart = findOccurrence(paragraph.text, plainFind, occurrence);\n if (matchStart === -1) {\n throw new Error(\n `Text \"${plainFind}\" not found in paragraph \"${paragraph.id}\" (occurrence ${occurrence})`,\n );\n }\n const ranges = resolveRunRanges(paragraph.runs, matchStart, matchStart + plainFind.length);\n if (ranges.length === 0) {\n throw new Error(`No runs found for \"${plainFind}\" in paragraph \"${paragraph.id}\"`);\n }\n matchedRuns = splitRunsForMatch(paragraph.xmlRef, ranges);\n if (matchedRuns.length === 0) {\n throw new Error(`Failed to isolate matched runs in paragraph \"${paragraph.id}\"`);\n }\n }\n\n const commentsRoot = ensureCommentsPart(doc);\n const id = nextCommentId(commentsRoot);\n\n const rangeStart = createElement(\"w:commentRangeStart\", [], { \"w:id\": id });\n const rangeEnd = createElement(\"w:commentRangeEnd\", [], { \"w:id\": id });\n const refRun = buildReferenceRun(id, doc);\n\n if (matchedRuns) {\n const firstRun = matchedRuns[0]!;\n const lastRun = matchedRuns[matchedRuns.length - 1]!;\n\n const firstParent = findRunParentChildren(paragraph.xmlRef, firstRun);\n if (!firstParent) throw new Error(\"Matched run not found in paragraph\");\n firstParent.splice(firstParent.indexOf(firstRun), 0, rangeStart);\n\n const lastParent = findRunParentChildren(paragraph.xmlRef, lastRun);\n if (!lastParent) throw new Error(\"Matched run not found in paragraph\");\n lastParent.splice(lastParent.indexOf(lastRun) + 1, 0, rangeEnd, refRun);\n } else {\n const pChildren = getChildren(paragraph.xmlRef);\n const pPr = findChild(paragraph.xmlRef, \"w:pPr\");\n const startIdx = pPr ? pChildren.indexOf(pPr) + 1 : 0;\n pChildren.splice(startIdx, 0, rangeStart);\n pChildren.push(rangeEnd, refRun);\n }\n\n getChildren(commentsRoot).push(\n buildCommentElement(id, author, initials, options.date, patch.text, doc, options.markers ?? true),\n );\n}\n","/**\n * Patch engine: executes patches sequentially against a NormalizedDocument.\n *\n * After each patch:\n * 1. The FXP object is mutated directly (via xmlRef pointers)\n * 2. The affected paragraph is re-normalized to update text + run mappings\n *\n * This is the \"Option A: re-normalize after each patch\" approach — simple\n * and correct. Can be optimized to incremental updates later.\n *\n * With `atomic: true`, the whole batch runs against a deep clone of the\n * document; the clone replaces the document only if every patch succeeds.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedTable,\n Patch,\n PatchResult,\n PatchOptions,\n AppendTextPatch,\n AddTablePatch,\n DocxXmlParts,\n FXPNode,\n} from \"../types.js\";\nimport {\n validatePatch,\n findParagraphById,\n findTableById,\n findElementById,\n} from \"./validator.js\";\nimport {\n executeReplaceText,\n executeInsertText,\n executeDeleteText,\n} from \"./text-ops.js\";\nimport {\n executeAddTableRow,\n executeDeleteTableRow,\n executeReplaceCellText,\n buildTableNode,\n} from \"./table-ops.js\";\nimport { executeApplyFormatting } from \"./format-ops.js\";\nimport { executeApplyStyle } from \"./style-ops.js\";\nimport { normalizeParagraph } from \"../normalizer/paragraph.js\";\nimport { normalizeTable } from \"../normalizer/table.js\";\nimport { enrichListType, reNormalizeDocument } from \"../normalizer/document.js\";\nimport { createRevisionContext, type RevisionContext } from \"./revision.js\";\nimport {\n findChildren,\n getChildren,\n getTagName,\n createElement,\n} from \"../xml/helpers.js\";\nimport { createInsElement, createTrPrIns } from \"./revision.js\";\nimport {\n executeAddHeader,\n executeAddFooter,\n executeRemoveHeader,\n executeRemoveFooter,\n} from \"./header-footer-ops.js\";\nimport { buildFieldRuns } from \"./field-codes.js\";\nimport {\n executeAddList,\n executeAddListItem,\n executeRemoveListItem,\n executeConvertToList,\n executeRemoveList,\n executeSetListProperties,\n} from \"./list-ops.js\";\nimport { createNumberingDefinition } from \"./numbering.js\";\nimport { parseTextSegments } from \"./text-segments.js\";\nimport {\n getBodyChildren,\n insertIntoBody,\n reNormalizeParagraph,\n} from \"./container-utils.js\";\nimport { executeInsertParagraph, executeDeleteParagraph } from \"./paragraph-ops.js\";\nimport { executeAddComment } from \"./comment-ops.js\";\nimport { stampNewParagraphs, findPartRootForId } from \"../normalizer/para-id.js\";\n\n/**\n * Apply a list of patches to a NormalizedDocument.\n *\n * Patches are applied sequentially in order. After each patch,\n * the affected elements are re-normalized so subsequent patches\n * operate on updated text/mappings.\n *\n * @param options - Optional patch options (e.g., revision tracking, atomic)\n * @returns Array of PatchResult for each patch\n */\nexport function applyPatches(\n doc: NormalizedDocument,\n patches: Patch[],\n options?: PatchOptions,\n): PatchResult[] {\n if (options?.atomic) {\n return applyPatchesAtomic(doc, patches, options);\n }\n return applyPatchesSequential(doc, patches, options);\n}\n\nfunction applyPatchesSequential(\n doc: NormalizedDocument,\n patches: Patch[],\n options?: PatchOptions,\n): PatchResult[] {\n const results: PatchResult[] = [];\n\n // Create revision context if tracking revisions\n const revCtx = options?.trackRevisions\n ? createRevisionContext(doc, options)\n : undefined;\n\n for (const patch of patches) {\n const result = applySinglePatch(doc, patch, revCtx, options);\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * All-or-nothing: apply every patch to a deep clone; swap the clone in only\n * if every patch succeeded. The results describe every patch either way.\n */\nfunction applyPatchesAtomic(\n doc: NormalizedDocument,\n patches: Patch[],\n options: PatchOptions,\n): PatchResult[] {\n const clone = cloneDocument(doc);\n const results = applyPatchesSequential(clone, patches, { ...options, atomic: false });\n\n if (results.every((r) => r.success)) {\n doc.body = clone.body;\n doc.headers = clone.headers;\n doc.footers = clone.footers;\n doc.xmlParts = clone.xmlParts;\n }\n\n return results;\n}\n\n/**\n * Deep-clone a document's XML parts and rebuild the normalized view over\n * the clone, so no xmlRef points back into the original.\n */\nexport function cloneDocument(doc: NormalizedDocument): NormalizedDocument {\n const xmlParts = {} as DocxXmlParts;\n for (const [key, value] of Object.entries(doc.xmlParts)) {\n if (value === undefined) continue;\n if (value instanceof Map) {\n const copy = new Map<string, FXPNode[]>();\n for (const [k, v] of value) copy.set(k, JSON.parse(JSON.stringify(v)) as FXPNode[]);\n xmlParts[key] = copy;\n } else {\n xmlParts[key] = JSON.parse(JSON.stringify(value)) as FXPNode[];\n }\n }\n\n const clone: NormalizedDocument = {\n body: [],\n headers: new Map(),\n footers: new Map(),\n xmlParts,\n styles: doc.styles,\n loadOptions: doc.loadOptions,\n };\n reNormalizeDocument(clone);\n return clone;\n}\n\n/**\n * Whether inline formatting markers apply to a patch: the patch's own\n * `markers` field wins, then `PatchOptions.inlineMarkers`, then `true`.\n */\nfunction markersFor(patch: Patch, options?: PatchOptions): boolean {\n const own = (patch as { markers?: boolean }).markers;\n return own ?? options?.inlineMarkers ?? true;\n}\n\n/**\n * Apply a single patch and re-normalize affected elements.\n */\nfunction applySinglePatch(\n doc: NormalizedDocument,\n patch: Patch,\n revCtx?: RevisionContext,\n options?: PatchOptions,\n): PatchResult {\n // Validate\n const validation = validatePatch(patch, doc);\n if (!validation.valid) {\n return { success: false, patch, error: validation.error };\n }\n\n const markers = markersFor(patch, options);\n\n try {\n switch (patch.type) {\n case \"replace_text\": {\n const para = findParagraphById(patch.target, doc)!;\n executeReplaceText(para, patch, revCtx, markers);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"insert_text\": {\n const para = findParagraphById(patch.target, doc)!;\n executeInsertText(para, patch, revCtx, markers);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"delete_text\": {\n const para = findParagraphById(patch.target, doc)!;\n executeDeleteText(para, patch, revCtx, markers);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"insert_paragraph\": {\n const para = findParagraphById(patch.target, doc)!;\n executeInsertParagraph(doc, para, patch, revCtx, markers);\n break;\n }\n case \"delete_paragraph\": {\n const para = findParagraphById(patch.target, doc)!;\n executeDeleteParagraph(doc, para, patch, revCtx);\n break;\n }\n case \"add_comment\": {\n const para = findParagraphById(patch.target, doc)!;\n executeAddComment(doc, para, patch, {\n defaultAuthor: options?.revisionAuthor ?? \"docxtor\",\n date: options?.revisionDate ?? new Date().toISOString(),\n markers,\n });\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"replace_cell_text\": {\n const para = findParagraphById(patch.target, doc)!;\n executeReplaceCellText(para, patch, revCtx, markers);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"add_table_row\": {\n const table = findTableById(patch.target, doc)!;\n const newRow = executeAddTableRow(table, patch, revCtx);\n // The cloned row carries the source row's paraIds — give it fresh ones\n stampNewParagraphs(doc, [newRow], findPartRootForId(doc, table.id), true);\n reNormalizeTable(doc, table);\n break;\n }\n case \"delete_table_row\": {\n const table = findTableById(patch.target, doc)!;\n executeDeleteTableRow(table, patch, revCtx);\n reNormalizeTable(doc, table);\n break;\n }\n case \"apply_formatting\": {\n const para = findParagraphById(patch.target, doc)!;\n executeApplyFormatting(para, patch, revCtx);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"apply_style\": {\n const para = findParagraphById(patch.target, doc)!;\n executeApplyStyle(para, patch, doc, revCtx);\n reNormalizeParagraph(doc, para);\n break;\n }\n case \"append_text\": {\n executeAppendText(doc, patch, revCtx, markers);\n break;\n }\n case \"add_table\": {\n executeAddTablePatch(doc, patch, revCtx);\n break;\n }\n case \"add_header\": {\n executeAddHeader(doc, patch, revCtx, markers);\n break;\n }\n case \"add_footer\": {\n executeAddFooter(doc, patch, revCtx, markers);\n break;\n }\n case \"remove_header\": {\n executeRemoveHeader(doc, patch);\n break;\n }\n case \"remove_footer\": {\n executeRemoveFooter(doc, patch);\n break;\n }\n case \"add_list\": {\n executeAddList(doc, patch, revCtx, markers);\n break;\n }\n case \"add_list_item\": {\n executeAddListItem(doc, patch, revCtx, markers);\n break;\n }\n case \"remove_list_item\": {\n executeRemoveListItem(doc, patch, revCtx);\n break;\n }\n case \"convert_to_list\": {\n executeConvertToList(doc, patch, revCtx);\n break;\n }\n case \"remove_list\": {\n executeRemoveList(doc, patch, revCtx);\n break;\n }\n case \"set_list_properties\": {\n executeSetListProperties(doc, patch, revCtx);\n break;\n }\n }\n\n return { success: true, patch };\n } catch (err) {\n return {\n success: false,\n patch,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Re-normalize a table after a structural change (row add/delete).\n * This replaces the table's rows/cells with fresh normalized data.\n */\nfunction reNormalizeTable(\n doc: NormalizedDocument,\n table: NormalizedTable,\n): void {\n const fresh = normalizeTable(table.xmlRef, table.id);\n\n // Update in-place\n table.rows = fresh.rows;\n table.style = fresh.style;\n\n if (doc.xmlParts.numbering) {\n for (const row of table.rows) {\n for (const cell of row.cells) {\n for (const para of cell.paragraphs) {\n enrichListType(para, doc.xmlParts.numbering);\n }\n }\n }\n }\n}\n\n// =============================================================================\n// Append text (document-level operation)\n// =============================================================================\n\n/**\n * Compute the next available paragraph and table IDs.\n */\nfunction getNextIds(doc: NormalizedDocument): {\n nextPIdx: number;\n nextTblIdx: number;\n} {\n let maxPIdx = -1;\n let maxTblIdx = -1;\n for (const el of doc.body) {\n if (el.kind === \"paragraph\") {\n const num = parseInt(el.id.replace(\"p\", \"\"), 10);\n if (!isNaN(num) && num > maxPIdx) maxPIdx = num;\n } else if (el.kind === \"table\") {\n const num = parseInt(el.id.replace(\"tbl\", \"\"), 10);\n if (!isNaN(num) && num > maxTblIdx) maxTblIdx = num;\n }\n }\n return { nextPIdx: maxPIdx + 1, nextTblIdx: maxTblIdx + 1 };\n}\n\n// =============================================================================\n// Revision tracking for new tables\n// =============================================================================\n\n/**\n * Mark all rows in a table node as inserted revisions.\n * Adds w:ins to w:trPr for each w:tr in the table.\n */\nfunction markTableRowsAsInserted(\n tableNode: FXPNode,\n revCtx: RevisionContext,\n): void {\n const trNodes = findChildren(tableNode, \"w:tr\");\n for (const trNode of trNodes) {\n const rowChildren = getChildren(trNode);\n let trPr = rowChildren.find((c) => \"w:trPr\" in c);\n if (!trPr) {\n trPr = createElement(\"w:trPr\", []);\n rowChildren.unshift(trPr);\n }\n const trPrChildren = getChildren(trPr);\n trPrChildren.push(createTrPrIns(revCtx));\n }\n}\n\n// =============================================================================\n// Execute append_text\n// =============================================================================\n\n/**\n * Execute an append_text patch: creates new w:p and/or w:tbl elements at the\n * end of the document body (or a header/footer if container is specified).\n *\n * Supports markdown table syntax — pipe-delimited rows with a separator line\n * are automatically converted to OOXML tables.\n * Supports {{FIELD}} placeholders (e.g., {{PAGE}}, {{NUMPAGES}}).\n *\n * Inserts before w:sectPr if present so section properties stay last.\n * With revision tracking, new paragraph runs are wrapped in w:ins.\n */\nfunction executeAppendText(\n doc: NormalizedDocument,\n patch: AppendTextPatch,\n revCtx: RevisionContext | undefined,\n markers: boolean,\n): void {\n if (patch.container) {\n // Target a header/footer container\n executeAppendTextToContainer(doc, patch, revCtx, markers);\n return;\n }\n\n const bodyChildren = getBodyChildren(doc);\n const segments = parseTextSegments(patch.text);\n const newNodes: FXPNode[] = [];\n\n // Track list context for numId reuse across table segments\n let lastListNumId: string | undefined;\n let lastListType: \"bullet\" | \"ordered\" | undefined;\n\n for (const segment of segments) {\n if (segment.kind === \"text\") {\n // A non-blank text segment resets list context\n if (segment.lines.some((l) => l.trim().length > 0)) {\n lastListNumId = undefined;\n lastListType = undefined;\n }\n for (const line of segment.lines) {\n const runs = buildFieldRuns(line, undefined, markers);\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n newNodes.push({ \"w:p\": [insElement] });\n } else {\n newNodes.push({ \"w:p\": runs });\n }\n }\n } else if (segment.kind === \"table\") {\n const tblNode = buildTableNode(segment.headers, segment.rows);\n if (revCtx) {\n markTableRowsAsInserted(tblNode, revCtx);\n }\n newNodes.push(tblNode);\n // Don't reset list context — lists continue across tables\n } else {\n // List segment\n let numId: string;\n if (lastListNumId && lastListType === segment.listType) {\n numId = lastListNumId;\n } else {\n numId = createNumberingDefinition(doc, segment.listType);\n }\n lastListNumId = numId;\n lastListType = segment.listType;\n\n for (const item of segment.items) {\n const numPr = createElement(\"w:numPr\", [\n createElement(\"w:ilvl\", [], { \"w:val\": String(item.level) }),\n createElement(\"w:numId\", [], { \"w:val\": numId }),\n ]);\n const pPr = createElement(\"w:pPr\", [numPr]);\n const runs = buildFieldRuns(item.text, undefined, markers);\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n newNodes.push({ \"w:p\": [pPr, insElement] });\n } else {\n newNodes.push({ \"w:p\": [pPr, ...runs] });\n }\n }\n }\n }\n\n stampNewParagraphs(doc, newNodes, findPartRootForId(doc, \"body\"));\n\n // Insert all new nodes before w:sectPr\n insertIntoBody(bodyChildren, newNodes);\n\n // Normalize and add to doc.body\n let { nextPIdx, nextTblIdx } = getNextIds(doc);\n for (const node of newNodes) {\n if (\"w:tbl\" in node) {\n const id = `tbl${nextTblIdx++}`;\n const normalized = normalizeTable(node, id);\n doc.body.push(normalized);\n } else {\n const id = `p${nextPIdx++}`;\n const normalized = normalizeParagraph(node, id);\n if (doc.xmlParts.numbering) {\n enrichListType(normalized, doc.xmlParts.numbering);\n }\n doc.body.push(normalized);\n }\n }\n}\n\n// =============================================================================\n// Execute add_table\n// =============================================================================\n\n/**\n * Execute an add_table patch: creates a new w:tbl element and inserts it\n * into the document body.\n */\nfunction executeAddTablePatch(\n doc: NormalizedDocument,\n patch: AddTablePatch,\n revCtx?: RevisionContext,\n): void {\n if (patch.container) {\n if (patch.afterId) {\n throw new Error(`\"afterId\" is not supported when \"container\" is set`);\n }\n executeAddTableToContainer(doc, patch, revCtx);\n return;\n }\n\n const bodyChildren = getBodyChildren(doc);\n const tableNode = buildTableNode(patch.headers, patch.rows);\n\n if (revCtx) {\n markTableRowsAsInserted(tableNode, revCtx);\n }\n\n stampNewParagraphs(doc, [tableNode], findPartRootForId(doc, \"body\"));\n\n // Find the afterId target's xmlRef if specified\n let afterXmlRef: FXPNode | undefined;\n let afterElementId: string | undefined;\n if (patch.afterId) {\n const target = findElementById(patch.afterId, doc);\n if (target) {\n afterXmlRef = target.xmlRef;\n afterElementId = target.id;\n }\n }\n\n insertIntoBody(bodyChildren, [tableNode], afterXmlRef);\n\n // Normalize and add to doc.body\n const { nextTblIdx } = getNextIds(doc);\n const id = `tbl${nextTblIdx}`;\n const normalized = normalizeTable(tableNode, id);\n\n // Insert into body array at correct position (after the target element)\n if (afterElementId !== undefined) {\n const targetIdx = doc.body.findIndex((el) => el.id === afterElementId);\n if (targetIdx !== -1) {\n doc.body.splice(targetIdx + 1, 0, normalized);\n return;\n }\n }\n doc.body.push(normalized);\n}\n\n// =============================================================================\n// Container helpers (header/footer targeting for append_text and add_table)\n// =============================================================================\n\n/**\n * Find a header/footer's root FXP node by type and variant.\n */\nfunction findContainerRoot(\n doc: NormalizedDocument,\n containerType: \"header\" | \"footer\",\n variant: string,\n): { rootNode: FXPNode; hf: import(\"../types.js\").HeaderFooter } | undefined {\n const map = containerType === \"header\" ? doc.headers : doc.footers;\n for (const hf of map.values()) {\n if (hf.variant === variant) {\n const rootTag = containerType === \"header\" ? \"w:hdr\" : \"w:ftr\";\n const rootNode = hf.xmlRef.find((n) => getTagName(n) === rootTag);\n if (rootNode) return { rootNode, hf };\n }\n }\n return undefined;\n}\n\n/**\n * Append text to a header/footer container.\n */\nfunction executeAppendTextToContainer(\n doc: NormalizedDocument,\n patch: AppendTextPatch,\n revCtx: RevisionContext | undefined,\n markers: boolean,\n): void {\n const container = patch.container!;\n const result = findContainerRoot(doc, container.type, container.variant);\n if (!result) {\n throw new Error(\n `${container.type} with variant \"${container.variant}\" not found`,\n );\n }\n\n const { rootNode, hf } = result;\n const rootChildren = getChildren(rootNode);\n const prefix = container.type === \"header\" ? \"hdr\" : \"ftr\";\n\n const segments = parseTextSegments(patch.text);\n let nextPIdx = hf.paragraphs.length;\n\n // Track list context for numId reuse across segments (same as body path)\n let lastListNumId: string | undefined;\n let lastListType: \"bullet\" | \"ordered\" | undefined;\n\n for (const segment of segments) {\n if (segment.kind === \"text\") {\n // A non-blank text segment resets list context\n if (segment.lines.some((l) => l.trim().length > 0)) {\n lastListNumId = undefined;\n lastListType = undefined;\n }\n for (const line of segment.lines) {\n let pNode: FXPNode;\n const runs = buildFieldRuns(line, undefined, markers);\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n pNode = { \"w:p\": [insElement] };\n } else {\n pNode = { \"w:p\": runs };\n }\n stampNewParagraphs(doc, [pNode], rootNode);\n rootChildren.push(pNode);\n const normalized = normalizeParagraph(\n pNode,\n `${prefix}.${container.variant}.p${nextPIdx++}`,\n );\n hf.paragraphs.push(normalized);\n }\n } else if (segment.kind === \"table\") {\n // Table segment — add to container XML\n const tblNode = buildTableNode(segment.headers, segment.rows);\n if (revCtx) {\n markTableRowsAsInserted(tblNode, revCtx);\n }\n stampNewParagraphs(doc, [tblNode], rootNode);\n rootChildren.push(tblNode);\n\n // Normalize and track\n const nextTblIdx = hf.tables.length;\n const tblId = `${prefix}.${container.variant}.tbl${nextTblIdx}`;\n const normalizedTbl = normalizeTable(tblNode, tblId);\n hf.tables.push(normalizedTbl);\n } else {\n // List segment in container — reuse numId if same list type\n let numId: string;\n if (lastListNumId && lastListType === segment.listType) {\n numId = lastListNumId;\n } else {\n numId = createNumberingDefinition(doc, segment.listType);\n }\n lastListNumId = numId;\n lastListType = segment.listType;\n for (const item of segment.items) {\n const numPr = createElement(\"w:numPr\", [\n createElement(\"w:ilvl\", [], { \"w:val\": String(item.level) }),\n createElement(\"w:numId\", [], { \"w:val\": numId }),\n ]);\n const pPr = createElement(\"w:pPr\", [numPr]);\n const runs = buildFieldRuns(item.text, undefined, markers);\n let pNode: FXPNode;\n if (revCtx) {\n const insElement = createInsElement(revCtx, runs);\n pNode = { \"w:p\": [pPr, insElement] };\n } else {\n pNode = { \"w:p\": [pPr, ...runs] };\n }\n stampNewParagraphs(doc, [pNode], rootNode);\n rootChildren.push(pNode);\n const normalized = normalizeParagraph(\n pNode,\n `${prefix}.${container.variant}.p${nextPIdx++}`,\n );\n if (doc.xmlParts.numbering) {\n enrichListType(normalized, doc.xmlParts.numbering);\n }\n hf.paragraphs.push(normalized);\n }\n }\n }\n}\n\n/**\n * Execute add_table targeting a header/footer container.\n */\nfunction executeAddTableToContainer(\n doc: NormalizedDocument,\n patch: AddTablePatch,\n revCtx?: RevisionContext,\n): void {\n const container = patch.container!;\n const result = findContainerRoot(doc, container.type, container.variant);\n if (!result) {\n throw new Error(\n `${container.type} with variant \"${container.variant}\" not found`,\n );\n }\n\n const { rootNode, hf } = result;\n const rootChildren = getChildren(rootNode);\n const tableNode = buildTableNode(patch.headers, patch.rows);\n\n if (revCtx) {\n markTableRowsAsInserted(tableNode, revCtx);\n }\n\n stampNewParagraphs(doc, [tableNode], rootNode);\n rootChildren.push(tableNode);\n\n // Normalize and track\n const prefix = container.type === \"header\" ? \"hdr\" : \"ftr\";\n const nextTblIdx = hf.tables.length;\n const id = `${prefix}.${container.variant}.tbl${nextTblIdx}`;\n const normalized = normalizeTable(tableNode, id);\n hf.tables.push(normalized);\n}\n","/**\n * LLM projection formats: convert a NormalizedDocument into\n * simplified representations that are easy for LLMs to understand\n * and generate patches against.\n */\n\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n RevisionMark,\n RunFormatting,\n RunFilter,\n RunMapping,\n StyleInfo,\n ProjectionOptions,\n} from \"../types.js\";\nimport { getLevelInfo } from \"../patches/numbering.js\";\n\n// =============================================================================\n// JSON Projection\n// =============================================================================\n\nexport interface JsonProjectionRunFormatting extends RunFormatting {\n /** The run's text content. */\n text: string;\n /** Start character offset in paragraph text (inclusive). */\n startOffset: number;\n /** End character offset in paragraph text (exclusive). */\n endOffset: number;\n}\n\ninterface JsonProjectionParagraph {\n id: string;\n /** Native `w14:paraId`, when the paragraph has one. */\n paraId?: string;\n text: string;\n style?: string;\n listLevel?: number;\n listType?: string;\n numId?: string;\n formatting?: JsonProjectionRunFormatting[];\n revisions?: RevisionMark[];\n hasPageBreak?: boolean;\n pageBreakOffset?: number;\n}\n\ninterface JsonProjectionTableCell {\n id: string;\n /** Native `w14:paraId` of the cell's first paragraph, when it has one. */\n paraId?: string;\n text: string;\n paragraphs?: JsonProjectionParagraph[];\n}\n\ninterface JsonProjectionTable {\n id: string;\n style?: string;\n rows: JsonProjectionTableCell[][];\n nestedTables?: JsonProjectionTable[];\n}\n\ninterface JsonProjectionHeaderFooter {\n type: \"header\" | \"footer\";\n variant: string;\n paragraphs: JsonProjectionParagraph[];\n tables?: JsonProjectionTable[];\n}\n\nexport interface JsonProjectionPageBreak {\n id: string;\n type: \"page_break\";\n}\n\nexport interface JsonProjection {\n body: (JsonProjectionParagraph | JsonProjectionTable | JsonProjectionPageBreak)[];\n headers?: JsonProjectionHeaderFooter[];\n footers?: JsonProjectionHeaderFooter[];\n styles?: StyleInfo[];\n}\n\n/**\n * Project a NormalizedDocument to a JSON structure suitable for LLMs.\n */\nexport function projectToJson(\n doc: NormalizedDocument,\n options: ProjectionOptions = {},\n): JsonProjection {\n const runFilter = options.runFilter;\n const includeFormatting = (options.includeFormatting ?? false) || runFilter !== undefined;\n const includeHeadersFooters = options.includeHeadersFooters ?? true;\n const includeTables = options.includeTables ?? true;\n const includeRevisions = options.includeRevisions ?? false;\n const includePageBreaks = options.includePageBreaks ?? false;\n\n const body: (JsonProjectionParagraph | JsonProjectionTable | JsonProjectionPageBreak)[] = [];\n\n for (const element of doc.body) {\n if (element.kind === \"paragraph\") {\n body.push(projectParagraphToJson(element, includeFormatting, includeRevisions, includePageBreaks, runFilter));\n } else if (element.kind === \"table\" && includeTables) {\n body.push(projectTableToJson(element, includeFormatting, includeRevisions, runFilter));\n } else if (element.kind === \"page_break\" && includePageBreaks) {\n body.push({ id: element.id, type: \"page_break\" });\n }\n }\n\n const result: JsonProjection = { body };\n\n // Include available styles if present\n if (doc.styles.length > 0) {\n result.styles = doc.styles;\n }\n\n if (includeHeadersFooters) {\n if (doc.headers.size > 0) {\n result.headers = [];\n for (const header of doc.headers.values()) {\n const hfEntry: JsonProjectionHeaderFooter = {\n type: \"header\",\n variant: header.variant,\n paragraphs: header.paragraphs.map((p) =>\n projectParagraphToJson(p, includeFormatting, includeRevisions, false, runFilter),\n ),\n };\n if (includeTables && header.tables.length > 0) {\n hfEntry.tables = header.tables.map((t) =>\n projectTableToJson(t, includeFormatting, includeRevisions, runFilter),\n );\n }\n result.headers.push(hfEntry);\n }\n }\n if (doc.footers.size > 0) {\n result.footers = [];\n for (const footer of doc.footers.values()) {\n const hfEntry: JsonProjectionHeaderFooter = {\n type: \"footer\",\n variant: footer.variant,\n paragraphs: footer.paragraphs.map((p) =>\n projectParagraphToJson(p, includeFormatting, includeRevisions, false, runFilter),\n ),\n };\n if (includeTables && footer.tables.length > 0) {\n hfEntry.tables = footer.tables.map((t) =>\n projectTableToJson(t, includeFormatting, includeRevisions, runFilter),\n );\n }\n result.footers.push(hfEntry);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Whether a run's formatting satisfies every property of a RunFilter.\n * String comparisons are case-insensitive so colour hex casing doesn't matter.\n */\nexport function runMatchesFilter(formatting: RunFormatting, filter: RunFilter): boolean {\n const eqStr = (a: string | undefined, b: string | undefined): boolean =>\n a !== undefined && b !== undefined && a.toUpperCase() === b.toUpperCase();\n\n if (filter.bold !== undefined && (formatting.bold ?? false) !== filter.bold) return false;\n if (filter.italic !== undefined && (formatting.italic ?? false) !== filter.italic) return false;\n if (filter.strikethrough !== undefined && (formatting.strikethrough ?? false) !== filter.strikethrough) return false;\n if (filter.underline !== undefined && !eqStr(formatting.underline, filter.underline)) return false;\n if (filter.color !== undefined && !eqStr(formatting.color, filter.color)) return false;\n if (filter.highlight !== undefined && !eqStr(formatting.highlight, filter.highlight)) return false;\n return true;\n}\n\nfunction projectParagraphToJson(\n para: NormalizedParagraph,\n includeFormatting: boolean,\n includeRevisions: boolean = false,\n includePageBreaks: boolean = false,\n runFilter?: RunFilter,\n): JsonProjectionParagraph {\n const result: JsonProjectionParagraph = {\n id: para.id,\n text: para.text,\n };\n\n if (para.paraId) {\n result.paraId = para.paraId;\n }\n\n if (para.style) {\n result.style = para.style;\n }\n\n if (para.listInfo) {\n result.listLevel = para.listInfo.level;\n if (para.listInfo.listType) {\n result.listType = para.listInfo.listType;\n }\n result.numId = para.listInfo.numId;\n }\n\n if (includeFormatting && para.runs.length > 0) {\n const fmtRuns: JsonProjectionRunFormatting[] = [];\n for (const r of para.runs) {\n if (runFilter) {\n if (!runMatchesFilter(r.formatting, runFilter)) continue;\n } else {\n // Only include runs that have at least one formatting property set\n const hasAny =\n r.formatting.bold ||\n r.formatting.italic ||\n r.formatting.underline ||\n r.formatting.strikethrough ||\n r.formatting.fontSize ||\n r.formatting.fontFamily ||\n r.formatting.color ||\n r.formatting.highlight;\n if (!hasAny) continue;\n }\n\n const fmt: JsonProjectionRunFormatting = {\n text: r.text,\n startOffset: r.startOffset,\n endOffset: r.endOffset,\n };\n if (r.formatting.bold) fmt.bold = true;\n if (r.formatting.italic) fmt.italic = true;\n if (r.formatting.underline) fmt.underline = r.formatting.underline;\n if (r.formatting.strikethrough) fmt.strikethrough = true;\n if (r.formatting.fontSize) fmt.fontSize = r.formatting.fontSize;\n if (r.formatting.fontFamily) fmt.fontFamily = r.formatting.fontFamily;\n if (r.formatting.color) fmt.color = r.formatting.color;\n if (r.formatting.themeColor) fmt.themeColor = r.formatting.themeColor;\n if (r.formatting.highlight) fmt.highlight = r.formatting.highlight;\n fmtRuns.push(fmt);\n }\n if (fmtRuns.length > 0) {\n result.formatting = fmtRuns;\n }\n }\n\n if (includeRevisions && para.revisions && para.revisions.length > 0) {\n result.revisions = para.revisions;\n }\n\n if (includePageBreaks && para.hasPageBreak) {\n result.hasPageBreak = true;\n if (para.pageBreakOffset !== undefined) {\n result.pageBreakOffset = para.pageBreakOffset;\n }\n }\n\n return result;\n}\n\nfunction projectTableToJson(\n table: NormalizedTable,\n includeFormatting: boolean = false,\n includeRevisions: boolean = false,\n runFilter?: RunFilter,\n): JsonProjectionTable {\n const result: JsonProjectionTable = {\n id: table.id,\n style: table.style,\n rows: table.rows.map((row) =>\n row.cells.map((cell) => {\n const cellResult: JsonProjectionTableCell = {\n id: cell.paragraphs[0]?.id ?? `${table.id}.r${row.index}.c${cell.colIndex}.p0`,\n text: cell.paragraphs.map((p) => p.text).join(\"\\n\"),\n };\n if (cell.paragraphs[0]?.paraId) {\n cellResult.paraId = cell.paragraphs[0].paraId;\n }\n // Include individual paragraph details for multi-paragraph cells,\n // or when filtering runs (so matching runs in cells are reported)\n if (cell.paragraphs.length > 1 || runFilter !== undefined) {\n cellResult.paragraphs = cell.paragraphs.map((p) =>\n projectParagraphToJson(p, includeFormatting, includeRevisions, false, runFilter),\n );\n }\n return cellResult;\n }),\n ),\n };\n\n // Include nested tables if any exist\n const allNested: JsonProjectionTable[] = [];\n for (const row of table.rows) {\n for (const cell of row.cells) {\n for (const nested of cell.nestedTables) {\n allNested.push(projectTableToJson(nested, includeFormatting, includeRevisions, runFilter));\n }\n }\n }\n if (allNested.length > 0) {\n result.nestedTables = allNested;\n }\n\n return result;\n}\n\n// =============================================================================\n// List prefix rendering\n// =============================================================================\n\n/**\n * Format a number according to a w:numFmt value.\n */\nfunction formatNumber(n: number, fmt: string): string {\n switch (fmt) {\n case \"decimal\":\n return String(n);\n case \"lowerLetter\": {\n // 1→a, 2→b, ..., 26→z, 27→aa\n let result = \"\";\n let v = n;\n while (v > 0) {\n v--;\n result = String.fromCharCode(97 + (v % 26)) + result;\n v = Math.floor(v / 26);\n }\n return result;\n }\n case \"upperLetter\": {\n let result = \"\";\n let v = n;\n while (v > 0) {\n v--;\n result = String.fromCharCode(65 + (v % 26)) + result;\n v = Math.floor(v / 26);\n }\n return result;\n }\n case \"lowerRoman\":\n return toRoman(n).toLowerCase();\n case \"upperRoman\":\n return toRoman(n);\n default:\n return String(n);\n }\n}\n\nfunction toRoman(n: number): string {\n const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];\n const syms = [\"M\", \"CM\", \"D\", \"CD\", \"C\", \"XC\", \"L\", \"XL\", \"X\", \"IX\", \"V\", \"IV\", \"I\"];\n let result = \"\";\n let v = n;\n for (let i = 0; i < vals.length; i++) {\n while (v >= vals[i]!) {\n result += syms[i];\n v -= vals[i]!;\n }\n }\n return result;\n}\n\n/** Default ordered formats per level (cycling). */\nconst DEFAULT_ORDERED_FMTS = [\"decimal\", \"lowerLetter\", \"lowerRoman\"];\n\n/**\n * Build list prefix string for a paragraph, updating counter state.\n * Returns empty string for non-list paragraphs.\n */\nfunction buildListPrefix(\n para: NormalizedParagraph,\n counters: Map<string, number>,\n numberingFxp?: import(\"../types.js\").FXPNode[],\n): string {\n if (!para.listInfo) return \"\";\n\n const { numId, level, listType } = para.listInfo;\n const indent = \" \".repeat(level);\n\n // Clear all deeper-level counters for this numId\n for (const key of counters.keys()) {\n const [kid, klvl] = key.split(\":\");\n if (kid === numId && Number(klvl) > level) {\n counters.delete(key);\n }\n }\n\n // Increment this level's counter\n const counterKey = `${numId}:${level}`;\n const count = (counters.get(counterKey) ?? 0) + 1;\n counters.set(counterKey, count);\n\n if (listType === \"bullet\" || !listType) {\n return `${indent}- `;\n }\n\n if (listType === \"multilevel\" && numberingFxp) {\n // Resolve compound prefix from w:lvlText pattern\n const info = getLevelInfo(numberingFxp, numId, level);\n if (info) {\n // Replace %N placeholders with counter values\n let prefix = info.pattern;\n const placeholders = prefix.match(/%(\\d+)/g);\n if (placeholders) {\n for (const ph of placeholders) {\n const lvlIdx = parseInt(ph.slice(1), 10) - 1;\n const lvlCount = counters.get(`${numId}:${lvlIdx}`) ?? 1;\n const fmt = info.ancestorFormats.get(lvlIdx) ?? \"decimal\";\n prefix = prefix.replaceAll(ph, formatNumber(lvlCount, fmt));\n }\n }\n return `${indent}${prefix} `;\n }\n }\n\n // Ordered (independent counters) — resolve format from numbering.xml or use defaults\n let fmt = DEFAULT_ORDERED_FMTS[level % DEFAULT_ORDERED_FMTS.length] ?? \"decimal\";\n if (numberingFxp) {\n const info = getLevelInfo(numberingFxp, numId, level);\n if (info) {\n fmt = info.format;\n }\n }\n return `${indent}${formatNumber(count, fmt)}. `;\n}\n\n// =============================================================================\n// Markdown Projection\n// =============================================================================\n\n/**\n * Project a NormalizedDocument to a markdown-like string for LLMs.\n *\n * Format:\n * ```\n * [p0] This is a paragraph\n * [p1] (Heading1) This is a heading\n *\n * [tbl0] Table\n * | [tbl0.r0.c0.p0] Header A | [tbl0.r0.c1.p0] Header B |\n * | [tbl0.r1.c0.p0] Cell 1 | [tbl0.r1.c1.p0] Cell 2 |\n *\n * --- Header (default) ---\n * [hdr.default.p0] Header text\n *\n * --- Footer (default) ---\n * [ftr.default.p0] Footer text\n * ```\n */\nexport function projectToMarkdown(\n doc: NormalizedDocument,\n options: ProjectionOptions = {},\n): string {\n const includeHeadersFooters = options.includeHeadersFooters ?? true;\n const includeTables = options.includeTables ?? true;\n const includeRevisions = options.includeRevisions ?? false;\n const includePageBreaks = options.includePageBreaks ?? false;\n const includeFormatting = options.includeFormatting ?? false;\n const lines: string[] = [];\n\n // List counter state: \"numId:level\" → current count\n const listCounters = new Map<string, number>();\n const numberingFxp = doc.xmlParts.numbering;\n\n for (const element of doc.body) {\n if (element.kind === \"paragraph\") {\n const listPrefix = buildListPrefix(element, listCounters, numberingFxp);\n lines.push(formatParagraphMarkdown(element, includeRevisions, includePageBreaks, includeFormatting, listPrefix));\n } else if (element.kind === \"table\" && includeTables) {\n lines.push(\"\"); // blank line before table\n lines.push(...formatTableMarkdown(element, includeRevisions, includeFormatting));\n lines.push(\"\"); // blank line after table\n } else if (element.kind === \"page_break\" && includePageBreaks) {\n lines.push(\"\");\n lines.push(`[${element.id}] --- PAGE BREAK ---`);\n lines.push(\"\");\n }\n }\n\n if (includeHeadersFooters) {\n for (const header of doc.headers.values()) {\n lines.push(\"\");\n lines.push(`--- Header (${header.variant}) ---`);\n const hdrCounters = new Map<string, number>();\n for (const para of header.paragraphs) {\n const listPrefix = buildListPrefix(para, hdrCounters, numberingFxp);\n lines.push(formatParagraphMarkdown(para, includeRevisions, false, includeFormatting, listPrefix));\n }\n if (includeTables) {\n for (const table of header.tables) {\n lines.push(\"\");\n lines.push(...formatTableMarkdown(table, includeRevisions));\n }\n }\n }\n\n for (const footer of doc.footers.values()) {\n lines.push(\"\");\n lines.push(`--- Footer (${footer.variant}) ---`);\n const ftrCounters = new Map<string, number>();\n for (const para of footer.paragraphs) {\n const listPrefix = buildListPrefix(para, ftrCounters, numberingFxp);\n lines.push(formatParagraphMarkdown(para, includeRevisions, false, includeFormatting, listPrefix));\n }\n if (includeTables) {\n for (const table of footer.tables) {\n lines.push(\"\");\n lines.push(...formatTableMarkdown(table, includeRevisions));\n }\n }\n }\n }\n\n // Include available styles\n if (doc.styles.length > 0) {\n lines.push(\"\");\n lines.push(\"--- Available Styles ---\");\n for (const style of doc.styles) {\n lines.push(`[${style.type}] ${style.id} — ${style.name}`);\n }\n }\n\n return lines.join(\"\\n\").trim();\n}\n\nfunction formatParagraphMarkdown(\n para: NormalizedParagraph,\n includeRevisions: boolean = false,\n includePageBreaks: boolean = false,\n includeFormatting: boolean = false,\n listPrefix: string = \"\",\n): string {\n const styleHint = para.style ? ` (${para.style})` : \"\";\n\n let text = includeFormatting\n ? annotateTextWithFormatting(para.text, para.runs)\n : para.text;\n const hasRevisions = includeRevisions && para.revisions && para.revisions.length > 0;\n\n if (hasRevisions) {\n if (includeFormatting) {\n // Map revision offsets through formatting annotation shift\n const mappedRevisions = para.revisions!.map((rev) => ({\n ...rev,\n startOffset: mapOffsetToFormattedText(rev.startOffset, para.runs),\n endOffset: mapOffsetToFormattedText(rev.endOffset, para.runs),\n }));\n text = annotateTextWithRevisions(text, mappedRevisions);\n } else {\n text = annotateTextWithRevisions(para.text, para.revisions!);\n }\n }\n\n // Insert [PAGE BREAK] marker at the correct position in the text.\n if (includePageBreaks && para.hasPageBreak) {\n let offset = para.pageBreakOffset ?? 0;\n\n if (includeFormatting) {\n offset = mapOffsetToFormattedText(offset, para.runs);\n }\n\n if (hasRevisions) {\n if (includeFormatting) {\n // Revisions have already been mapped through formatting, so use mapped revisions\n const mappedRevisions = para.revisions!.map((rev) => ({\n ...rev,\n startOffset: mapOffsetToFormattedText(rev.startOffset, para.runs),\n endOffset: mapOffsetToFormattedText(rev.endOffset, para.runs),\n }));\n offset = mapOffsetToAnnotatedText(offset, mappedRevisions);\n } else {\n offset = mapOffsetToAnnotatedText(offset, para.revisions!);\n }\n }\n\n const before = text.slice(0, offset);\n const after = text.slice(offset);\n if (before.length === 0) {\n text = `[PAGE BREAK] ${after}`;\n } else if (after.length === 0) {\n text = `${before} [PAGE BREAK]`;\n } else {\n text = `${before} [PAGE BREAK] ${after}`;\n }\n }\n\n const listHint = para.listInfo ? ` {list:${para.listInfo.numId} L${para.listInfo.level}}` : \"\";\n return `[${formatIdToken(para)}]${styleHint}${listHint} ${listPrefix}${text}`;\n}\n\n/**\n * The id token shown in markdown brackets: `p12`, or `p12 ¶4F2A1C7B` when\n * the paragraph carries a native paraId.\n */\nfunction formatIdToken(para: NormalizedParagraph): string {\n return para.paraId ? `${para.id} ¶${para.paraId}` : para.id;\n}\n\n/**\n * Annotate paragraph text with GFM inline formatting markers.\n *\n * Walks the runs and wraps text spans with:\n * - **bold**\n * - _italic_\n * - ~~strikethrough~~\n * - <u>underline</u>\n * - Combined: **_bold+italic_**\n *\n * Each run is wrapped independently. Runs with no representable\n * formatting are emitted as plain text.\n */\nfunction annotateTextWithFormatting(\n text: string,\n runs: RunMapping[],\n): string {\n if (runs.length === 0) return text;\n\n const parts: string[] = [];\n let lastEnd = 0;\n\n for (const run of runs) {\n // Fill any gap between runs with plain text\n if (run.startOffset > lastEnd) {\n parts.push(text.slice(lastEnd, run.startOffset));\n }\n\n const runText = text.slice(run.startOffset, run.endOffset);\n const { bold, italic, strikethrough, underline } = run.formatting;\n\n if (!bold && !italic && !strikethrough && !underline) {\n parts.push(runText);\n } else {\n let wrapped = runText;\n // Inner wrapping first, then outer\n if (underline) wrapped = `<u>${wrapped}</u>`;\n if (italic) wrapped = `_${wrapped}_`;\n if (bold) wrapped = `**${wrapped}**`;\n if (strikethrough) wrapped = `~~${wrapped}~~`;\n parts.push(wrapped);\n }\n\n lastEnd = run.endOffset;\n }\n\n // Trailing text after last run\n if (lastEnd < text.length) {\n parts.push(text.slice(lastEnd));\n }\n\n return parts.join(\"\");\n}\n\n/**\n * Map an offset in the original paragraph text to the corresponding\n * position in the formatting-annotated string.\n *\n * Formatting markers (**, _, ~~) add characters that shift positions.\n * This walks runs in document order and accumulates the shift.\n */\nfunction mapOffsetToFormattedText(\n originalOffset: number,\n runs: RunMapping[],\n): number {\n let shift = 0;\n\n for (const run of runs) {\n const { bold, italic, strikethrough, underline } = run.formatting;\n if (!bold && !italic && !strikethrough && !underline) continue;\n\n // Calculate marker lengths for this run\n let prefixLen = 0;\n let suffixLen = 0;\n if (strikethrough) { prefixLen += 2; suffixLen += 2; }\n if (bold) { prefixLen += 2; suffixLen += 2; }\n if (italic) { prefixLen += 1; suffixLen += 1; }\n if (underline) { prefixLen += 3; suffixLen += 4; } // <u> and </u>\n\n if (originalOffset >= run.startOffset) {\n shift += prefixLen;\n }\n if (originalOffset >= run.endOffset) {\n shift += suffixLen;\n }\n }\n\n return originalOffset + shift;\n}\n\n/**\n * Map an offset in the original paragraph text to the corresponding\n * position in the revision-annotated string.\n *\n * Revision markers ({+...+} for inserts, {-...-} for deletes) add\n * characters that shift later positions. This walks revisions in\n * document order (ascending startOffset) and accumulates the shift.\n */\nfunction mapOffsetToAnnotatedText(\n originalOffset: number,\n revisions: RevisionMark[],\n): number {\n // Sort by startOffset ascending — we accumulate shift left-to-right\n const sorted = [...revisions].sort((a, b) => a.startOffset - b.startOffset);\n let shift = 0;\n\n for (const rev of sorted) {\n if (rev.type === \"insert\") {\n // {+...+} wraps text at [startOffset, endOffset).\n // The 2-char prefix \"{+\" is inserted at startOffset.\n // If the break is past (or at) startOffset, it shifts by +2 for the prefix.\n // The 2-char suffix \"+}\" is inserted at endOffset.\n // If the break is past (or at) endOffset, it shifts by another +2.\n if (originalOffset >= rev.startOffset) shift += 2; // \"{+\"\n if (originalOffset >= rev.endOffset) shift += 2; // \"+}\"\n } else if (rev.type === \"delete\") {\n // {-text-} is inserted at startOffset (zero-width in original).\n // Total insertion = text.length + 4 chars (\"{-\" + text + \"-}\").\n // If the break is at or past the delete position, shift by the full marker length.\n if (originalOffset >= rev.startOffset) {\n shift += rev.text.length + 4;\n }\n }\n }\n\n return originalOffset + shift;\n}\n\n/**\n * Annotate paragraph text with revision markers.\n *\n * Inserts are shown as `{+text+}` and deletes as `{-text-}`.\n * Revisions are applied from right-to-left to avoid offset shifting.\n */\nfunction annotateTextWithRevisions(\n text: string,\n revisions: RevisionMark[],\n): string {\n // Sort by startOffset descending so we can splice from right-to-left\n const sorted = [...revisions].sort((a, b) => b.startOffset - a.startOffset);\n\n let result = text;\n for (const rev of sorted) {\n if (rev.type === \"insert\") {\n // The inserted text is already in .text at [startOffset, endOffset)\n // Wrap it with {+...+}\n const before = result.slice(0, rev.startOffset);\n const inserted = result.slice(rev.startOffset, rev.endOffset);\n const after = result.slice(rev.endOffset);\n result = `${before}{+${inserted}+}${after}`;\n } else if (rev.type === \"delete\") {\n // Deleted text is NOT in .text (startOffset === endOffset is a zero-width position)\n // Insert the deleted text marker at that position\n const before = result.slice(0, rev.startOffset);\n const after = result.slice(rev.startOffset);\n result = `${before}{-${rev.text}-}${after}`;\n }\n }\n\n return result;\n}\n\nfunction formatTableMarkdown(\n table: NormalizedTable,\n includeRevisions: boolean = false,\n includeFormatting: boolean = false,\n): string[] {\n const lines: string[] = [];\n\n for (const row of table.rows) {\n const cells = row.cells.map((cell) => {\n let text: string;\n if (includeFormatting) {\n text = cell.paragraphs\n .map((p) => {\n let t = annotateTextWithFormatting(p.text, p.runs);\n if (includeRevisions && p.revisions && p.revisions.length > 0) {\n const mappedRevisions = p.revisions.map((rev) => ({\n ...rev,\n startOffset: mapOffsetToFormattedText(rev.startOffset, p.runs),\n endOffset: mapOffsetToFormattedText(rev.endOffset, p.runs),\n }));\n t = annotateTextWithRevisions(t, mappedRevisions);\n }\n return t;\n })\n .join(\" \");\n } else if (includeRevisions) {\n const annotated = cell.paragraphs.map((p) => {\n if (p.revisions && p.revisions.length > 0) {\n return annotateTextWithRevisions(p.text, p.revisions);\n }\n return p.text;\n });\n text = annotated.join(\" \");\n } else {\n text = cell.paragraphs.map((p) => p.text).join(\" \");\n }\n const first = cell.paragraphs[0];\n const id = first ? formatIdToken(first) : `${table.id}.r${row.index}.c${cell.colIndex}.p0`;\n return `[${id}] ${text}`;\n });\n lines.push(`| ${cells.join(\" | \")} |`);\n }\n\n return lines;\n}\n","/**\n * LLM projection: generates simplified document representations\n * for LLM consumption.\n *\n * The projection strips away XML complexity and presents the document\n * as a flat list of paragraphs with IDs and text content. The LLM\n * can then emit patches referencing these IDs.\n */\n\nimport type {\n NormalizedDocument,\n ProjectionFormat,\n ProjectionOptions,\n} from \"../types.js\";\nimport { projectToJson, projectToMarkdown } from \"./formats.js\";\nimport type { JsonProjection, JsonProjectionPageBreak, JsonProjectionRunFormatting } from \"./formats.js\";\n\n/**\n * Project a NormalizedDocument to an LLM-friendly format.\n *\n * @param doc - The normalized document\n * @param format - \"json\" or \"markdown\"\n * @param options - Projection options\n * @returns The projected representation (object for JSON, string for markdown)\n */\nexport function project(\n doc: NormalizedDocument,\n format: ProjectionFormat = \"json\",\n options: ProjectionOptions = {},\n): JsonProjection | string {\n switch (format) {\n case \"json\":\n return projectToJson(doc, options);\n case \"markdown\":\n return projectToMarkdown(doc, options);\n default:\n throw new Error(`Unknown projection format: ${format}`);\n }\n}\n\nexport { projectToJson, projectToMarkdown };\nexport type { JsonProjection, JsonProjectionPageBreak, JsonProjectionRunFormatting };\n","/**\n * Accept or reject every tracked change in a document.\n *\n * Walks document.xml, headers, footers, footnotes, endnotes and comments,\n * resolving each revision element according to the mode:\n *\n * | Element | Accept | Reject |\n * |--------------------------------------|--------------------------------|-----------------------------------|\n * | `w:ins` (runs) | unwrap | remove |\n * | `w:del` (runs) | remove | unwrap, w:delText → w:t |\n * | `w:moveTo` / `w:moveFrom` | as ins / as del | as ins / as del |\n * | `w:*PrChange` | drop | restore old properties |\n * | row `w:trPr/w:ins` / `w:del` | drop marker / remove row | remove row / drop marker |\n * | cell `w:cellIns` / `w:cellDel` | drop marker / remove cell | remove cell / drop marker |\n * | paragraph mark `w:ins` | drop marker | merge paragraph into the next |\n * | paragraph mark `w:del` | merge paragraph into the next | drop marker |\n *\n * With an `authors` filter only revisions by those authors are touched; with\n * an `ids` filter only revisions with those `w:id`s.\n * The caller must re-normalize the document afterwards (positional ids may\n * change; `paraId`s do not).\n */\n\nimport type { NormalizedDocument, FXPNode, RevisionFilterOptions } from \"../types.js\";\nimport {\n cloneNode,\n findChild,\n getAttr,\n getChildren,\n getTagName,\n needsSpacePreserve,\n setAttr,\n unescapeXmlText,\n} from \"../xml/helpers.js\";\n\ntype Mode = \"accept\" | \"reject\";\n\ninterface WalkContext {\n mode: Mode;\n authors?: Set<string>;\n ids?: Set<string>;\n count: number;\n /**\n * Ids of `w:moveFromRangeStart` / `w:moveToRangeStart` markers that match\n * the author filter, collected per part. Range *end* markers carry only an\n * id, so they are matched through this set.\n */\n moveRangeIds: Set<string>;\n}\n\n/** Property-change elements and the siblings to keep when restoring the snapshot. */\nconst PROP_CHANGE_KEEP: Record<string, Set<string>> = {\n \"w:rPrChange\": new Set([\"w:ins\", \"w:del\", \"w:moveFrom\", \"w:moveTo\"]),\n \"w:pPrChange\": new Set([\"w:rPr\", \"w:sectPr\"]),\n \"w:trPrChange\": new Set([\"w:ins\", \"w:del\"]),\n \"w:tcPrChange\": new Set([\"w:cellIns\", \"w:cellDel\", \"w:cellMerge\"]),\n \"w:tblPrChange\": new Set(),\n \"w:tblPrExChange\": new Set(),\n \"w:tblGridChange\": new Set(),\n \"w:sectPrChange\": new Set(),\n};\n\n/** Parents whose `w:ins` / `w:del` children are markers owned by w:p / w:tr, not run containers. */\nconst MARKER_PARENTS = new Set([\"w:rPr\", \"w:trPr\", \"w:tcPr\"]);\n\nconst MOVE_RANGE_START_TAGS = new Set([\"w:moveFromRangeStart\", \"w:moveToRangeStart\"]);\nconst MOVE_RANGE_END_TAGS = new Set([\"w:moveFromRangeEnd\", \"w:moveToRangeEnd\"]);\n\n/** Elements that may sit between two paragraphs without separating them. */\nconst TRANSPARENT_SIBLINGS = new Set([\n \"w:bookmarkStart\",\n \"w:bookmarkEnd\",\n \"w:proofErr\",\n \"w:permStart\",\n \"w:permEnd\",\n \"w:commentRangeStart\",\n \"w:commentRangeEnd\",\n \"w:customXmlInsRangeStart\",\n \"w:customXmlInsRangeEnd\",\n \"w:customXmlDelRangeStart\",\n \"w:customXmlDelRangeEnd\",\n]);\n\n// =============================================================================\n// Public API\n// =============================================================================\n\nexport function acceptAllRevisions(\n doc: NormalizedDocument,\n options?: RevisionFilterOptions,\n): { accepted: number } {\n const ctx = makeContext(\"accept\", options);\n walkAllParts(doc, ctx);\n return { accepted: ctx.count };\n}\n\nexport function rejectAllRevisions(\n doc: NormalizedDocument,\n options?: RevisionFilterOptions,\n): { rejected: number } {\n const ctx = makeContext(\"reject\", options);\n walkAllParts(doc, ctx);\n return { rejected: ctx.count };\n}\n\nfunction makeContext(mode: Mode, options?: RevisionFilterOptions): WalkContext {\n return {\n mode,\n authors: options?.authors ? new Set(options.authors) : undefined,\n ids: options?.ids ? new Set(options.ids.map(String)) : undefined,\n count: 0,\n moveRangeIds: new Set(),\n };\n}\n\nfunction walkAllParts(doc: NormalizedDocument, ctx: WalkContext): void {\n const parts: FXPNode[][] = [\n doc.xmlParts.document,\n ...doc.xmlParts.headers.values(),\n ...doc.xmlParts.footers.values(),\n ];\n for (const key of [\"footnotes\", \"endnotes\", \"comments\"] as const) {\n const part = doc.xmlParts[key];\n if (part) parts.push(part);\n }\n for (const part of parts) {\n if (ctx.ids) expandMoveIds(part, ctx.ids);\n ctx.moveRangeIds = collectMatchingMoveRangeIds(part, ctx);\n processChildren(part, undefined, ctx);\n }\n}\n\nfunction matches(node: FXPNode, ctx: WalkContext): boolean {\n if (ctx.ids) {\n const id = getAttr(node, \"w:id\");\n if (id === undefined || !ctx.ids.has(id)) return false;\n }\n if (!ctx.authors) return true;\n const author = getAttr(node, \"w:author\");\n return author !== undefined && ctx.authors.has(author);\n}\n\n/**\n * A move is several elements with different ids: `w:moveFromRangeStart` / `w:moveToRangeStart`\n * markers (with matching `…RangeEnd` markers) around `w:moveFrom` / `w:moveTo` content. When an\n * `ids` filter names any part of a move, add the rest, including the other side of the move (the\n * \"from\" and \"to\" ranges share a `w:name`), so the move is resolved as a unit rather than leaving range\n * markers without content, content without its range, or one side of a move without the other.\n */\nfunction expandMoveIds(nodes: FXPNode[], ids: Set<string>): void {\n const open: { from: string[]; to: string[] } = { from: [], to: [] };\n const groups: string[][] = []; // each: [range id, ...content ids inside it]\n const groupOf = new Map<string, string[]>();\n const byName = new Map<string, string[]>();\n const visit = (list: FXPNode[]): void => {\n for (const node of list) {\n const tag = getTagName(node);\n if (!tag) continue;\n const id = getAttr(node, \"w:id\");\n if (id !== undefined) {\n if (tag === \"w:moveFromRangeStart\" || tag === \"w:moveToRangeStart\") {\n const name = getAttr(node, \"w:name\");\n const g = (name !== undefined && byName.get(name)) || [];\n if (!g.length) groups.push(g);\n if (name !== undefined) byName.set(name, g);\n g.push(id);\n groupOf.set(id, g);\n (tag === \"w:moveFromRangeStart\" ? open.from : open.to).push(id);\n } else if (tag === \"w:moveFromRangeEnd\" || tag === \"w:moveToRangeEnd\") {\n const stack = tag === \"w:moveFromRangeEnd\" ? open.from : open.to;\n const at = stack.lastIndexOf(id);\n if (at !== -1) stack.splice(at, 1);\n } else if (tag === \"w:moveFrom\" || tag === \"w:moveTo\") {\n for (const rangeId of tag === \"w:moveFrom\" ? open.from : open.to) groupOf.get(rangeId)!.push(id);\n }\n }\n const children = getChildren(node);\n if (children.length > 0) visit(children);\n }\n };\n visit(nodes);\n for (const g of groups) if (g.some((id) => ids.has(id))) for (const id of g) ids.add(id);\n}\n\n/**\n * Pre-pass: ids of move range start markers that match the author filter,\n * so the author-less end markers can be removed together with them.\n */\nfunction collectMatchingMoveRangeIds(nodes: FXPNode[], ctx: WalkContext): Set<string> {\n const ids = new Set<string>();\n const visit = (list: FXPNode[]): void => {\n for (const node of list) {\n const tag = getTagName(node);\n if (!tag) continue;\n if (MOVE_RANGE_START_TAGS.has(tag) && matches(node, ctx)) {\n const id = getAttr(node, \"w:id\");\n if (id !== undefined) ids.add(id);\n }\n const children = getChildren(node);\n if (children.length > 0) visit(children);\n }\n };\n visit(nodes);\n return ids;\n}\n\n// =============================================================================\n// Walker\n// =============================================================================\n\n/**\n * Process a children array in place. Elements may be removed, unwrapped, or\n * merged, so the loop manages its own index.\n */\nfunction processChildren(\n children: FXPNode[],\n parentTag: string | undefined,\n ctx: WalkContext,\n): void {\n let i = 0;\n while (i < children.length) {\n const child = children[i]!;\n const tag = getTagName(child);\n if (!tag) {\n i++;\n continue;\n }\n\n const inMarkerParent = parentTag !== undefined && MARKER_PARENTS.has(parentTag);\n\n // --- Run-level insertions -------------------------------------------\n if ((tag === \"w:ins\" || tag === \"w:moveTo\") && !inMarkerParent) {\n if (matches(child, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n children.splice(i, 1, ...getChildren(child)); // unwrap; reprocess from i\n } else {\n children.splice(i, 1);\n }\n continue;\n }\n processChildren(getChildren(child), tag, ctx);\n i++;\n continue;\n }\n\n // --- Run-level deletions --------------------------------------------\n if ((tag === \"w:del\" || tag === \"w:moveFrom\") && !inMarkerParent) {\n if (matches(child, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n children.splice(i, 1);\n } else {\n const inner = getChildren(child);\n for (const node of inner) restoreDeletedContent(node);\n children.splice(i, 1, ...inner);\n }\n continue;\n }\n processChildren(getChildren(child), tag, ctx);\n i++;\n continue;\n }\n\n // --- Move range markers ---------------------------------------------\n // Start markers carry w:author; end markers only an id, matched through\n // the ids collected from their start markers.\n if (MOVE_RANGE_START_TAGS.has(tag) || MOVE_RANGE_END_TAGS.has(tag)) {\n const id = getAttr(child, \"w:id\");\n const remove = MOVE_RANGE_START_TAGS.has(tag)\n ? matches(child, ctx)\n : id !== undefined && ctx.moveRangeIds.has(id);\n if (remove) {\n children.splice(i, 1);\n continue;\n }\n i++;\n continue;\n }\n\n // --- Property changes -----------------------------------------------\n if (tag in PROP_CHANGE_KEEP) {\n if (matches(child, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n children.splice(i, 1);\n } else {\n restorePropertyChange(children, child, tag);\n }\n i = 0; // siblings may have been replaced\n continue;\n }\n i++;\n continue;\n }\n\n // --- Paragraphs: content first, then the paragraph mark ------------\n if (tag === \"w:p\") {\n processChildren(getChildren(child), tag, ctx);\n if (handleParagraphMark(children, i, child, ctx)) continue; // merged away\n i++;\n continue;\n }\n\n // --- Table rows -----------------------------------------------------\n if (tag === \"w:tr\") {\n if (handleRowMarkers(children, i, child, ctx)) continue; // row removed\n processChildren(getChildren(child), tag, ctx);\n i++;\n continue;\n }\n\n // --- Table cells ----------------------------------------------------\n if (tag === \"w:tc\") {\n if (handleCellMarkers(children, i, child, ctx)) continue; // cell removed\n processChildren(getChildren(child), tag, ctx);\n i++;\n continue;\n }\n\n // --- Everything else: recurse ---------------------------------------\n const grandChildren = getChildren(child);\n if (grandChildren.length > 0) processChildren(grandChildren, tag, ctx);\n i++;\n }\n}\n\n// =============================================================================\n// Deleted content restoration (reject)\n// =============================================================================\n\n/**\n * Turn deleted run content back into live content: `w:delText` → `w:t`,\n * `w:delInstrText` → `w:instrText`. Recurses through inline containers.\n */\nfunction restoreDeletedContent(node: FXPNode): void {\n const tag = getTagName(node);\n if (!tag) return;\n\n if (tag === \"w:r\") {\n const children = getChildren(node);\n for (let i = 0; i < children.length; i++) {\n const c = children[i]!;\n if (\"w:delText\" in c) {\n const replacement: FXPNode = { \"w:t\": c[\"w:delText\"] as FXPNode[] };\n if (c[\":@\"]) replacement[\":@\"] = c[\":@\"];\n const text = textOf(replacement[\"w:t\"] as FXPNode[]);\n if (needsSpacePreserve(text)) setAttr(replacement, \"xml:space\", \"preserve\");\n children[i] = replacement;\n } else if (\"w:delInstrText\" in c) {\n const replacement: FXPNode = { \"w:instrText\": c[\"w:delInstrText\"] as FXPNode[] };\n if (c[\":@\"]) replacement[\":@\"] = c[\":@\"];\n children[i] = replacement;\n }\n }\n return;\n }\n\n for (const child of getChildren(node)) restoreDeletedContent(child);\n}\n\nfunction textOf(nodes: FXPNode[]): string {\n let text = \"\";\n for (const n of nodes) {\n if (\"#text\" in n) text += unescapeXmlText(String(n[\"#text\"]));\n }\n return text;\n}\n\n// =============================================================================\n// Property change restoration (reject)\n// =============================================================================\n\n/**\n * Replace the contents of a property container (`w:rPr`, `w:pPr`, ...) with\n * the snapshot stored inside its `*Change` child, keeping the siblings that\n * are not part of the snapshot (revision markers, paragraph-mark rPr, sectPr).\n */\nfunction restorePropertyChange(\n containerChildren: FXPNode[],\n changeNode: FXPNode,\n changeTag: string,\n): void {\n const keep = PROP_CHANGE_KEEP[changeTag] ?? new Set<string>();\n const snapshot = getChildren(changeNode).find((c) => getTagName(c) !== undefined);\n const snapshotChildren = snapshot ? getChildren(snapshot).map(cloneNode) : [];\n\n const kept = containerChildren.filter((c) => {\n const t = getTagName(c);\n return t !== undefined && keep.has(t) && c !== changeNode;\n });\n\n containerChildren.length = 0;\n if (changeTag === \"w:rPrChange\") {\n // Paragraph-mark markers come first in CT_ParaRPr\n containerChildren.push(...kept, ...snapshotChildren);\n } else {\n containerChildren.push(...snapshotChildren, ...kept);\n }\n}\n\n// =============================================================================\n// Paragraph marks\n// =============================================================================\n\n/**\n * Resolve `w:pPr/w:rPr/w:ins|w:del` on a paragraph.\n * Returns true if the paragraph was merged into the next one (and removed).\n */\nfunction handleParagraphMark(\n siblings: FXPNode[],\n index: number,\n pNode: FXPNode,\n ctx: WalkContext,\n): boolean {\n const pPr = findChild(pNode, \"w:pPr\");\n if (!pPr) return false;\n const rPr = findChild(pPr, \"w:rPr\");\n if (!rPr) return false;\n const rPrChildren = getChildren(rPr);\n\n let removeParagraph = false;\n\n const insIdx = rPrChildren.findIndex((c) => getTagName(c) === \"w:ins\");\n if (insIdx !== -1 && matches(rPrChildren[insIdx]!, ctx)) {\n ctx.count++;\n rPrChildren.splice(insIdx, 1);\n if (ctx.mode === \"reject\") removeParagraph = true;\n }\n\n const delIdx = rPrChildren.findIndex((c) => getTagName(c) === \"w:del\");\n if (delIdx !== -1 && matches(rPrChildren[delIdx]!, ctx)) {\n ctx.count++;\n rPrChildren.splice(delIdx, 1);\n if (ctx.mode === \"accept\") removeParagraph = true;\n }\n\n // Tidy empty property containers\n if (rPrChildren.length === 0) {\n const pPrChildren = getChildren(pPr);\n pPrChildren.splice(pPrChildren.indexOf(rPr), 1);\n if (pPrChildren.length === 0) {\n const pChildren = getChildren(pNode);\n pChildren.splice(pChildren.indexOf(pPr), 1);\n }\n }\n\n if (!removeParagraph) return false;\n return mergeIntoNextParagraph(siblings, index, pNode);\n}\n\n/**\n * Merge a paragraph's content into the following paragraph and remove it.\n * If there is no following paragraph (end of body / cell, or a table comes\n * next) the paragraph is kept, as Word does.\n */\nfunction mergeIntoNextParagraph(\n siblings: FXPNode[],\n index: number,\n pNode: FXPNode,\n): boolean {\n // A section-break paragraph must survive or the section is lost\n const pPr = findChild(pNode, \"w:pPr\");\n if (pPr && findChild(pPr, \"w:sectPr\")) return false;\n\n let j = index + 1;\n while (j < siblings.length) {\n const t = getTagName(siblings[j]!);\n if (t === \"w:p\") break;\n // Whitespace text nodes and range markers do not separate paragraphs\n if (t === undefined || TRANSPARENT_SIBLINGS.has(t)) {\n j++;\n continue;\n }\n return false;\n }\n if (j >= siblings.length) return false;\n\n const next = siblings[j]!;\n const nextChildren = getChildren(next);\n const nextPPr = findChild(next, \"w:pPr\");\n const insertAt = nextPPr ? nextChildren.indexOf(nextPPr) + 1 : 0;\n\n const content = getChildren(pNode).filter((c) => getTagName(c) !== \"w:pPr\");\n nextChildren.splice(insertAt, 0, ...content);\n siblings.splice(index, 1);\n return true;\n}\n\n// =============================================================================\n// Table rows and cells\n// =============================================================================\n\n/** Returns true if the row was removed. */\nfunction handleRowMarkers(\n siblings: FXPNode[],\n index: number,\n trNode: FXPNode,\n ctx: WalkContext,\n): boolean {\n const trPr = findChild(trNode, \"w:trPr\");\n if (!trPr) return false;\n const trPrChildren = getChildren(trPr);\n\n const insIdx = trPrChildren.findIndex((c) => getTagName(c) === \"w:ins\");\n if (insIdx !== -1 && matches(trPrChildren[insIdx]!, ctx)) {\n ctx.count++;\n if (ctx.mode === \"reject\") {\n siblings.splice(index, 1);\n return true;\n }\n trPrChildren.splice(insIdx, 1);\n }\n\n const delIdx = trPrChildren.findIndex((c) => getTagName(c) === \"w:del\");\n if (delIdx !== -1 && matches(trPrChildren[delIdx]!, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n siblings.splice(index, 1);\n return true;\n }\n trPrChildren.splice(delIdx, 1);\n }\n\n return false;\n}\n\n/** Returns true if the cell was removed. */\nfunction handleCellMarkers(\n siblings: FXPNode[],\n index: number,\n tcNode: FXPNode,\n ctx: WalkContext,\n): boolean {\n const tcPr = findChild(tcNode, \"w:tcPr\");\n if (!tcPr) return false;\n const tcPrChildren = getChildren(tcPr);\n\n const cellIns = tcPrChildren.findIndex((c) => getTagName(c) === \"w:cellIns\");\n if (cellIns !== -1 && matches(tcPrChildren[cellIns]!, ctx)) {\n ctx.count++;\n if (ctx.mode === \"reject\") {\n siblings.splice(index, 1);\n return true;\n }\n tcPrChildren.splice(cellIns, 1);\n }\n\n const cellDel = tcPrChildren.findIndex((c) => getTagName(c) === \"w:cellDel\");\n if (cellDel !== -1 && matches(tcPrChildren[cellDel]!, ctx)) {\n ctx.count++;\n if (ctx.mode === \"accept\") {\n siblings.splice(index, 1);\n return true;\n }\n tcPrChildren.splice(cellDel, 1);\n }\n\n const cellMerge = tcPrChildren.find((c) => getTagName(c) === \"w:cellMerge\");\n if (cellMerge && matches(cellMerge, ctx)) {\n ctx.count++;\n if (ctx.mode === \"reject\") {\n restoreVerticalMerge(tcPrChildren, cellMerge); // may insert before the marker\n }\n tcPrChildren.splice(tcPrChildren.indexOf(cellMerge), 1);\n }\n\n return false;\n}\n\n/**\n * Rejecting a tracked vertical cell merge: put `w:vMerge` back to the state\n * recorded in `w:cellMerge/@w:vMergeOrig` (\"rest\" → restart, \"cont\" →\n * continue, absent → the cell was not merged, so drop `w:vMerge`).\n */\nfunction restoreVerticalMerge(tcPrChildren: FXPNode[], cellMerge: FXPNode): void {\n const orig = getAttr(cellMerge, \"w:vMergeOrig\");\n const existingIdx = tcPrChildren.findIndex((c) => getTagName(c) === \"w:vMerge\");\n\n if (orig === undefined) {\n if (existingIdx !== -1) tcPrChildren.splice(existingIdx, 1);\n return;\n }\n\n const vMerge: FXPNode = { \"w:vMerge\": [] };\n if (orig === \"rest\") setAttr(vMerge, \"w:val\", \"restart\");\n\n if (existingIdx !== -1) {\n tcPrChildren[existingIdx] = vMerge;\n } else {\n // Keep schema order: w:vMerge precedes w:tcBorders and the cell markers\n const beforeIdx = tcPrChildren.findIndex((c) => {\n const t = getTagName(c);\n return t === \"w:tcBorders\" || t === \"w:shd\" || t === \"w:noWrap\" || t === \"w:tcMar\" ||\n t === \"w:textDirection\" || t === \"w:tcFitText\" || t === \"w:vAlign\" || t === \"w:hideMark\" ||\n t === \"w:cellIns\" || t === \"w:cellDel\" || t === \"w:cellMerge\" || t === \"w:tcPrChange\";\n });\n if (beforeIdx === -1) tcPrChildren.push(vMerge);\n else tcPrChildren.splice(beforeIdx, 0, vMerge);\n }\n}\n","/**\n * Document metadata: docProps/core.xml, docProps/app.xml, docProps/custom.xml.\n *\n * - getCoreProperties / setCoreProperties read and write core.xml fields.\n * - scrubMetadata removes identifying information before a file leaves the\n * building: author fields in core.xml, Company/Manager/HyperlinkBase in\n * app.xml, and the whole custom.xml part.\n */\n\nimport type { NormalizedDocument, CoreProperties, FXPNode } from \"../types.js\";\nimport {\n createElement,\n escapeXmlText,\n getAttr,\n getChildren,\n getTagName,\n setAttr,\n unescapeXmlText,\n} from \"../xml/helpers.js\";\nimport {\n createRelsPart,\n createContentTypesPart,\n ensureRelationship,\n ensureContentTypeOverride,\n removeRelationshipsByType,\n removeContentTypeOverride,\n} from \"../patches/part-utils.js\";\n\nconst CP_NS = \"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\";\nconst DC_NS = \"http://purl.org/dc/elements/1.1/\";\nconst DCTERMS_NS = \"http://purl.org/dc/terms/\";\nconst DCMITYPE_NS = \"http://purl.org/dc/dcmitype/\";\nconst XSI_NS = \"http://www.w3.org/2001/XMLSchema-instance\";\n\nconst CORE_REL_TYPE =\n \"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties\";\nconst CORE_CONTENT_TYPE = \"application/vnd.openxmlformats-package.core-properties+xml\";\nconst CUSTOM_REL_TYPE =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties\";\n\n/** Core property → qualified element name as Word writes it. */\nconst CORE_TAGS: Record<keyof CoreProperties, string> = {\n creator: \"dc:creator\",\n lastModifiedBy: \"cp:lastModifiedBy\",\n title: \"dc:title\",\n subject: \"dc:subject\",\n description: \"dc:description\",\n keywords: \"cp:keywords\",\n category: \"cp:category\",\n contentStatus: \"cp:contentStatus\",\n revision: \"cp:revision\",\n created: \"dcterms:created\",\n modified: \"dcterms:modified\",\n};\n\n/** Fields cleared by scrubMetadata. */\nconst SCRUB_CORE_FIELDS: (keyof CoreProperties)[] = [\n \"creator\",\n \"lastModifiedBy\",\n \"description\",\n \"keywords\",\n \"category\",\n \"contentStatus\",\n];\n\n/** app.xml elements removed by scrubMetadata (matched by local name). */\nconst SCRUB_APP_FIELDS = new Set([\"Company\", \"Manager\", \"HyperlinkBase\"]);\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction localName(tag: string): string {\n const idx = tag.indexOf(\":\");\n return idx === -1 ? tag : tag.slice(idx + 1);\n}\n\nfunction findRoot(part: FXPNode[] | undefined, local: string): FXPNode | undefined {\n if (!part) return undefined;\n return part.find((n) => {\n const tag = getTagName(n);\n return tag !== undefined && localName(tag) === local;\n });\n}\n\nfunction findChildByLocalName(parent: FXPNode, local: string): FXPNode | undefined {\n return getChildren(parent).find((c) => {\n const tag = getTagName(c);\n return tag !== undefined && localName(tag) === local;\n });\n}\n\nfunction getElementText(node: FXPNode): string {\n let text = \"\";\n for (const c of getChildren(node)) {\n if (\"#text\" in c) text += unescapeXmlText(String(c[\"#text\"]));\n }\n return text;\n}\n\nfunction setElementText(node: FXPNode, text: string): void {\n const tag = getTagName(node)!;\n node[tag] = text.length > 0 ? [{ \"#text\": escapeXmlText(text) } as unknown as FXPNode] : [];\n}\n\nfunction removeChildByLocalName(parent: FXPNode, local: string): boolean {\n const children = getChildren(parent);\n const idx = children.findIndex((c) => {\n const tag = getTagName(c);\n return tag !== undefined && localName(tag) === local;\n });\n if (idx === -1) return false;\n children.splice(idx, 1);\n return true;\n}\n\n// =============================================================================\n// core.xml\n// =============================================================================\n\nfunction createCorePart(): FXPNode[] {\n return [\n { \"?xml\": [{ \"#text\": \"\" } as unknown as FXPNode], \":@\": { \"@_version\": \"1.0\", \"@_encoding\": \"UTF-8\", \"@_standalone\": \"yes\" } } as FXPNode,\n createElement(\"cp:coreProperties\", [], {\n \"xmlns:cp\": CP_NS,\n \"xmlns:dc\": DC_NS,\n \"xmlns:dcterms\": DCTERMS_NS,\n \"xmlns:dcmitype\": DCMITYPE_NS,\n \"xmlns:xsi\": XSI_NS,\n }),\n ];\n}\n\n/** Namespace declarations every element we may write into core.xml relies on. */\nconst CORE_NAMESPACES: Record<string, string> = {\n \"xmlns:cp\": CP_NS,\n \"xmlns:dc\": DC_NS,\n \"xmlns:dcterms\": DCTERMS_NS,\n \"xmlns:dcmitype\": DCMITYPE_NS,\n \"xmlns:xsi\": XSI_NS,\n};\n\n/**\n * Ensure docProps/core.xml exists as a parsed part, wired into the package,\n * and that its root declares every prefix we write (an existing file with no\n * date fields may lack `dcterms` / `xsi`).\n */\nfunction ensureCorePart(doc: NormalizedDocument): FXPNode {\n if (!doc.xmlParts.core) {\n doc.xmlParts.core = createCorePart();\n if (!doc.xmlParts.rootRels) doc.xmlParts.rootRels = createRelsPart();\n ensureRelationship(doc.xmlParts.rootRels, CORE_REL_TYPE, \"docProps/core.xml\");\n if (!doc.xmlParts.contentTypes) doc.xmlParts.contentTypes = createContentTypesPart();\n ensureContentTypeOverride(doc.xmlParts.contentTypes, \"/docProps/core.xml\", CORE_CONTENT_TYPE);\n }\n const root = findRoot(doc.xmlParts.core, \"coreProperties\");\n if (!root) throw new Error(\"docProps/core.xml has no coreProperties root\");\n for (const [attr, uri] of Object.entries(CORE_NAMESPACES)) {\n if (getAttr(root, attr) === undefined) setAttr(root, attr, uri);\n }\n return root;\n}\n\n/**\n * Read the core document properties. Missing fields are undefined.\n */\nexport function getCoreProperties(doc: NormalizedDocument): CoreProperties {\n const result: CoreProperties = {};\n const root = findRoot(doc.xmlParts.core, \"coreProperties\");\n if (!root) return result;\n\n for (const key of Object.keys(CORE_TAGS) as (keyof CoreProperties)[]) {\n const node = findChildByLocalName(root, localName(CORE_TAGS[key]));\n if (node) result[key] = getElementText(node);\n }\n return result;\n}\n\n/**\n * Set core document properties. Only the fields given are touched; an empty\n * string empties the element. Creates docProps/core.xml if the document has none.\n */\nexport function setCoreProperties(doc: NormalizedDocument, props: Partial<CoreProperties>): void {\n const root = ensureCorePart(doc);\n\n for (const key of Object.keys(props) as (keyof CoreProperties)[]) {\n const value = props[key];\n if (value === undefined || !(key in CORE_TAGS)) continue;\n\n const tag = CORE_TAGS[key];\n let node = findChildByLocalName(root, localName(tag));\n if (!node) {\n node = createElement(tag, []);\n getChildren(root).push(node);\n }\n setElementText(node, value);\n if (key === \"created\" || key === \"modified\") {\n setAttr(node, \"xsi:type\", \"dcterms:W3CDTF\");\n }\n }\n}\n\n// =============================================================================\n// Scrub\n// =============================================================================\n\n/**\n * Strip identifying metadata.\n *\n * - core.xml: removes creator, lastModifiedBy, description, keywords,\n * category, contentStatus; sets revision to \"1\". Title, created and\n * modified are left untouched.\n * - app.xml: removes Company, Manager, HyperlinkBase.\n * - custom.xml: removes the part, its package relationship and content type.\n *\n * @returns zip paths the caller must drop from the archive on save\n */\nexport function scrubMetadata(doc: NormalizedDocument): { removedParts: string[] } {\n const removedParts: string[] = [];\n\n const coreRoot = findRoot(doc.xmlParts.core, \"coreProperties\");\n if (coreRoot) {\n for (const key of SCRUB_CORE_FIELDS) {\n removeChildByLocalName(coreRoot, localName(CORE_TAGS[key]));\n }\n setCoreProperties(doc, { revision: \"1\" });\n }\n\n const appRoot = findRoot(doc.xmlParts.app, \"Properties\");\n if (appRoot) {\n const children = getChildren(appRoot);\n for (let i = children.length - 1; i >= 0; i--) {\n const tag = getTagName(children[i]!);\n if (tag !== undefined && SCRUB_APP_FIELDS.has(localName(tag))) {\n children.splice(i, 1);\n }\n }\n }\n\n const customTargets = removeRelationshipsByType(doc.xmlParts.rootRels, CUSTOM_REL_TYPE);\n removeContentTypeOverride(doc.xmlParts.contentTypes, \"/docProps/custom.xml\");\n removedParts.push(\"docProps/custom.xml\");\n for (const target of customTargets) {\n const path = target.replace(/^\\//, \"\");\n if (path && !removedParts.includes(path)) removedParts.push(path);\n }\n\n return { removedParts };\n}\n","/**\n * docxtor — Surgical, LLM-friendly DOCX editing.\n *\n * @example\n * ```ts\n * import { Docxtor } from \"@lucio-ai/docxtor\";\n * import { readFile, writeFile } from \"fs/promises\";\n *\n * // Load a document\n * const buffer = await readFile(\"contract.docx\");\n * const doc = await Docxtor.load(buffer);\n *\n * // Get an LLM-friendly projection\n * const projection = doc.project(\"markdown\");\n * // → \"[p0] (Heading1) Service Agreement\\n[p1] This agreement is valid...\"\n *\n * // Apply patches (e.g., from LLM output)\n * const results = doc.applyPatches([\n * { type: \"replace_text\", target: \"p1\", find: \"valid\", replace: \"enforceable\" },\n * ]);\n *\n * // Export the modified document\n * const output = await doc.toBuffer();\n * await writeFile(\"contract-edited.docx\", output);\n * ```\n *\n * @packageDocumentation\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport JSZip from \"jszip\";\nimport type {\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n Patch,\n PatchResult,\n PatchOptions,\n ProjectionFormat,\n ProjectionOptions,\n LoadOptions,\n BodyElement,\n HeaderFooter,\n StyleInfo,\n CoreProperties,\n RevisionFilterOptions,\n FXPNode,\n} from \"./types.js\";\nimport { readDocx, type DocxZipEntries } from \"./zip/reader.js\";\nimport { writeDocx } from \"./zip/writer.js\";\nimport { serializeXml } from \"./xml/serializer.js\";\nimport { normalizeDocument, reNormalizeDocument } from \"./normalizer/document.js\";\nimport { applyPatches } from \"./patches/engine.js\";\nimport { project, type JsonProjection } from \"./projection/llm.js\";\nimport {\n findParagraphById as _findParagraphById,\n findParagraphByParaId as _findParagraphByParaId,\n findTableById as _findTableById,\n validatePatch as _validatePatch,\n} from \"./patches/validator.js\";\nimport {\n acceptAllRevisions as _acceptAllRevisions,\n rejectAllRevisions as _rejectAllRevisions,\n} from \"./revisions/accept-reject.js\";\nimport {\n getCoreProperties as _getCoreProperties,\n setCoreProperties as _setCoreProperties,\n scrubMetadata as _scrubMetadata,\n} from \"./metadata/properties.js\";\n\n/**\n * The main entry point for docxtor.\n *\n * A Docxtor instance represents a loaded DOCX document with:\n * - A normalized AST for inspecting document structure\n * - An LLM projection system for generating simplified views\n * - A patch engine for applying surgical edits\n * - Export capabilities to produce modified DOCX files\n */\nexport class Docxtor {\n private zipEntries: DocxZipEntries;\n private doc: NormalizedDocument;\n /** Zip paths to drop on the next export (e.g., docProps/custom.xml after a scrub). */\n private removedParts = new Set<string>();\n\n private constructor(\n zipEntries: DocxZipEntries,\n doc: NormalizedDocument,\n ) {\n this.zipEntries = zipEntries;\n this.doc = doc;\n }\n\n /**\n * Load a DOCX document from a buffer or file path.\n */\n static async load(\n input: Buffer | ArrayBuffer | Uint8Array | string,\n options?: LoadOptions,\n ): Promise<Docxtor> {\n let buffer: Buffer | ArrayBuffer | Uint8Array;\n\n if (typeof input === \"string\") {\n buffer = await readFile(input);\n } else {\n buffer = input;\n }\n\n const zipEntries = await readDocx(buffer);\n const doc = normalizeDocument(zipEntries, options);\n\n return new Docxtor(zipEntries, doc);\n }\n\n /**\n * Create a new Docxtor instance.\n *\n * If `input` is provided, delegates to {@link Docxtor.load}.\n * Otherwise, creates a valid empty DOCX document in memory.\n *\n * @param input - Optional buffer, ArrayBuffer, Uint8Array, or file path to load from\n * @param options - Optional load options\n *\n * @example\n * ```ts\n * // Create a blank document\n * const doc = await Docxtor.create();\n *\n * // Equivalent to Docxtor.load(buffer)\n * const doc2 = await Docxtor.create(buffer);\n * ```\n */\n static async create(\n input?: Buffer | ArrayBuffer | Uint8Array | string,\n options?: LoadOptions,\n ): Promise<Docxtor> {\n if (input !== undefined) {\n return Docxtor.load(input, options);\n }\n\n // Build a minimal valid DOCX in memory\n const zip = new JSZip();\n\n const contentTypes = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n <Default Extension=\"xml\" ContentType=\"application/xml\"/>\n <Override PartName=\"/word/document.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml\"/>\n</Types>`;\n\n const topRels = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"word/document.xml\"/>\n</Relationships>`;\n\n const documentXml = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:document xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"\n xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n <w:body>\n <w:p>\n <w:r>\n <w:t></w:t>\n </w:r>\n </w:p>\n </w:body>\n</w:document>`;\n\n zip.file(\"[Content_Types].xml\", contentTypes);\n zip.file(\"_rels/.rels\", topRels);\n zip.file(\"word/document.xml\", documentXml);\n\n const buffer = await zip.generateAsync({\n type: \"nodebuffer\",\n compression: \"DEFLATE\",\n });\n\n return Docxtor.load(buffer, options);\n }\n\n // ===========================================================================\n // Projection\n // ===========================================================================\n\n /**\n * Generate an LLM-friendly projection of the document.\n *\n * @param format - \"json\" for structured data, \"markdown\" for text\n * @param options - Control what's included in the projection\n */\n project(format?: \"json\", options?: ProjectionOptions): JsonProjection;\n project(format: \"markdown\", options?: ProjectionOptions): string;\n project(\n format: ProjectionFormat = \"json\",\n options: ProjectionOptions = {},\n ): JsonProjection | string {\n return project(this.doc, format, options);\n }\n\n // ===========================================================================\n // Patching\n // ===========================================================================\n\n /**\n * Apply patches to the document.\n *\n * Patches are applied sequentially. After each patch, affected elements\n * are re-normalized so subsequent patches see updated text/mappings.\n *\n * @param patches - Array of patches to apply\n * @param options - Optional options (e.g., revision tracking)\n * @returns Array of results, one per patch\n */\n applyPatches(patches: Patch[], options?: PatchOptions): PatchResult[] {\n return applyPatches(this.doc, patches, options);\n }\n\n // ===========================================================================\n // Revisions\n // ===========================================================================\n\n /**\n * Accept every tracked change (optionally only those by `authors`):\n * insertions become plain content, deletions are removed, property changes\n * are dropped, deleted rows and paragraphs disappear.\n *\n * Positional ids are reassigned afterwards; `paraId`s are unchanged.\n */\n acceptAllRevisions(options?: RevisionFilterOptions): { accepted: number } {\n const result = _acceptAllRevisions(this.doc, options);\n reNormalizeDocument(this.doc);\n return result;\n }\n\n /**\n * Reject every tracked change (optionally only those by `authors`):\n * insertions are removed, deletions restored, old properties put back,\n * inserted rows and paragraphs disappear.\n *\n * Positional ids are reassigned afterwards; `paraId`s are unchanged.\n */\n rejectAllRevisions(options?: RevisionFilterOptions): { rejected: number } {\n const result = _rejectAllRevisions(this.doc, options);\n reNormalizeDocument(this.doc);\n return result;\n }\n\n // ===========================================================================\n // Metadata\n // ===========================================================================\n\n /** Read docProps/core.xml properties. */\n getCoreProperties(): CoreProperties {\n return _getCoreProperties(this.doc);\n }\n\n /** Set docProps/core.xml properties (creating the part if needed). */\n setCoreProperties(props: Partial<CoreProperties>): void {\n _setCoreProperties(this.doc, props);\n }\n\n /**\n * Strip identifying metadata: clears creator, lastModifiedBy, description,\n * keywords, category and contentStatus, sets revision to \"1\", clears\n * Company / Manager / HyperlinkBase in app.xml, and removes\n * docProps/custom.xml. Title, created and modified are left untouched.\n */\n scrubMetadata(): void {\n const { removedParts } = _scrubMetadata(this.doc);\n for (const path of removedParts) this.removedParts.add(path);\n }\n\n // ===========================================================================\n // Export\n // ===========================================================================\n\n /**\n * Export the current document as a raw OOXML string (word/document.xml content).\n *\n * Returns the serialized XML for the document body, reflecting any patches\n * that have been applied. This is the same XML that gets written into the\n * DOCX ZIP as word/document.xml.\n */\n toString(): string {\n return serializeXml(this.doc.xmlParts.document);\n }\n\n /**\n * Export the modified document as a Buffer.\n */\n async toBuffer(): Promise<Buffer> {\n const documentXml = serializeXml(this.doc.xmlParts.document);\n\n const headers = new Map<string, string>();\n for (const [path, fxp] of this.doc.xmlParts.headers) {\n headers.set(path, serializeXml(fxp));\n }\n\n const footers = new Map<string, string>();\n for (const [path, fxp] of this.doc.xmlParts.footers) {\n footers.set(path, serializeXml(fxp));\n }\n\n // Determine which header/footer zip entries should be removed from the\n // current in-memory zip state. This handles multi-export flows where a\n // part may be added in one export and removed in a later export.\n const removedParts: string[] = [];\n for (const path of Object.keys(this.zipEntries.zip.files)) {\n if (/^word\\/header\\d+\\.xml$/i.test(path)) {\n if (!this.doc.xmlParts.headers.has(path)) {\n removedParts.push(path);\n }\n } else if (/^word\\/footer\\d+\\.xml$/i.test(path)) {\n if (!this.doc.xmlParts.footers.has(path)) {\n removedParts.push(path);\n }\n }\n }\n\n for (const path of this.removedParts) {\n if (!removedParts.includes(path)) removedParts.push(path);\n }\n\n // Serialize every optional part that was parsed (or created on demand)\n const serialize = (part: FXPNode[] | undefined): string | undefined =>\n part ? serializeXml(part) : undefined;\n const parts = this.doc.xmlParts;\n\n return writeDocx({\n zip: this.zipEntries.zip,\n documentXml,\n headers: headers.size > 0 ? headers : undefined,\n footers: footers.size > 0 ? footers : undefined,\n documentRelsXml: serialize(parts.documentRels),\n contentTypesXml: serialize(parts.contentTypes),\n settingsXml: serialize(parts.settings),\n numberingXml: serialize(parts.numbering),\n footnotesXml: serialize(parts.footnotes),\n endnotesXml: serialize(parts.endnotes),\n commentsXml: serialize(parts.comments),\n coreXml: serialize(parts.core),\n appXml: serialize(parts.app),\n rootRelsXml: serialize(parts.rootRels),\n removedParts: removedParts.length > 0 ? removedParts : undefined,\n });\n }\n\n /**\n * Export the modified document to a file.\n */\n async toFile(filePath: string): Promise<void> {\n const { writeFile: fsWriteFile } = await import(\"node:fs/promises\");\n const buffer = await this.toBuffer();\n await fsWriteFile(filePath, buffer);\n }\n\n // ===========================================================================\n // Inspection\n // ===========================================================================\n\n /**\n * Get the full normalized AST.\n */\n getAST(): NormalizedDocument {\n return this.doc;\n }\n\n /**\n * Get all body-level elements (paragraphs and tables).\n */\n getBody(): BodyElement[] {\n return this.doc.body;\n }\n\n /**\n * Get all paragraphs in the document body (not inside tables).\n */\n getParagraphs(): NormalizedParagraph[] {\n return this.doc.body.filter(\n (e): e is NormalizedParagraph => e.kind === \"paragraph\",\n );\n }\n\n /**\n * Get all tables in the document body.\n */\n getTables(): NormalizedTable[] {\n return this.doc.body.filter(\n (e): e is NormalizedTable => e.kind === \"table\",\n );\n }\n\n /**\n * Get all headers.\n */\n getHeaders(): Map<string, HeaderFooter> {\n return this.doc.headers;\n }\n\n /**\n * Get all footers.\n */\n getFooters(): Map<string, HeaderFooter> {\n return this.doc.footers;\n }\n\n /**\n * Find a paragraph by its ID anywhere in the document\n * (body, tables, headers, footers, nested tables).\n */\n findParagraphById(id: string): NormalizedParagraph | undefined {\n return _findParagraphById(id, this.doc);\n }\n\n /**\n * Find a paragraph by its native `w14:paraId` (8 hex chars, case-insensitive).\n * Resolution order: body (including table cells), headers, footers.\n */\n findParagraphByParaId(paraId: string): NormalizedParagraph | undefined {\n return _findParagraphByParaId(paraId, this.doc);\n }\n\n /**\n * Find a table by its ID anywhere in the document body\n * (including nested tables).\n */\n findTableById(id: string): NormalizedTable | undefined {\n return _findTableById(id, this.doc);\n }\n\n /**\n * Get available styles from the document's styles.xml.\n * Useful for knowing which styles can be applied via apply_style patches.\n */\n getStyles(): StyleInfo[] {\n return this.doc.styles;\n }\n\n /**\n * Validate patches without applying them.\n * Returns validation results for each patch.\n */\n validatePatches(patches: Patch[]): { valid: boolean; error?: string }[] {\n return patches.map((patch) => _validatePatch(patch, this.doc));\n }\n}\n\n// Re-export types\nexport type {\n NormalizedDocument,\n NormalizedParagraph,\n NormalizedTable,\n NormalizedPageBreak,\n NormalizedTableRow,\n NormalizedTableCell,\n RunMapping,\n RunFormatting,\n RunFilter,\n ListInfo,\n HeaderFooter,\n BodyElement,\n Patch,\n ReplaceTextPatch,\n InsertTextPatch,\n DeleteTextPatch,\n AppendTextPatch,\n InsertParagraphPatch,\n DeleteParagraphPatch,\n AddCommentPatch,\n AddTablePatch,\n AddTableRowPatch,\n DeleteTableRowPatch,\n ReplaceCellTextPatch,\n ApplyFormattingPatch,\n ApplyStylePatch,\n AddHeaderPatch,\n AddFooterPatch,\n RemoveHeaderPatch,\n RemoveFooterPatch,\n AddListPatch,\n AddListItemPatch,\n RemoveListItemPatch,\n ConvertToListPatch,\n RemoveListPatch,\n SetListPropertiesPatch,\n HeaderFooterVariant,\n FormattingUpdate,\n StyleInfo,\n PatchResult,\n PatchOptions,\n RevisionMark,\n RevisionFilterOptions,\n CoreProperties,\n ProjectionFormat,\n ProjectionOptions,\n LoadOptions,\n FXPNode,\n} from \"./types.js\";\n\n// Re-export low-level utilities for advanced usage\nexport { readDocx } from \"./zip/reader.js\";\nexport { writeDocx } from \"./zip/writer.js\";\nexport { parseXml } from \"./xml/parser.js\";\nexport { serializeXml } from \"./xml/serializer.js\";\nexport { normalizeDocument, reNormalizeDocument } from \"./normalizer/document.js\";\nexport type { HeaderFooterRef } from \"./normalizer/document.js\";\nexport { applyPatches, cloneDocument } from \"./patches/engine.js\";\nexport { project, projectToJson, projectToMarkdown } from \"./projection/llm.js\";\nexport type { JsonProjection, JsonProjectionPageBreak, JsonProjectionRunFormatting } from \"./projection/llm.js\";\nexport { validatePatch, findParagraphById, findParagraphByParaId, findTableById } from \"./patches/validator.js\";\nexport type { ValidationResult } from \"./patches/validator.js\";\nexport { acceptAllRevisions, rejectAllRevisions } from \"./revisions/accept-reject.js\";\nexport { getCoreProperties, setCoreProperties, scrubMetadata } from \"./metadata/properties.js\";\n"],"mappings":"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"}