@coco-vexel/coco-office 0.0.0-stage → 1.0.1
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.
- package/LICENSE +202 -0
- package/README.md +120 -2
- package/THIRD_PARTY_NOTICES.md +1188 -0
- package/dist/excel-worker-browser.cjs +6544 -0
- package/dist/excel-worker-browser.cjs.map +1 -0
- package/dist/excel-worker-browser.d.cts +2 -0
- package/dist/excel-worker-browser.d.ts +2 -0
- package/dist/excel-worker-browser.js +6523 -0
- package/dist/excel-worker-browser.js.map +1 -0
- package/dist/excel-worker.cjs +6547 -0
- package/dist/excel-worker.cjs.map +1 -0
- package/dist/excel-worker.d.cts +2 -0
- package/dist/excel-worker.d.ts +2 -0
- package/dist/excel-worker.js +6526 -0
- package/dist/excel-worker.js.map +1 -0
- package/dist/excel.cjs +12523 -0
- package/dist/excel.cjs.map +1 -0
- package/dist/excel.d.cts +2252 -0
- package/dist/excel.d.ts +2252 -0
- package/dist/excel.js +12368 -0
- package/dist/excel.js.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel-worker-browser.cjs +12304 -0
- package/dist/kernel-worker-browser.cjs.map +1 -0
- package/dist/kernel-worker-browser.d.cts +2 -0
- package/dist/kernel-worker-browser.d.ts +2 -0
- package/dist/kernel-worker-browser.js +12283 -0
- package/dist/kernel-worker-browser.js.map +1 -0
- package/dist/kernel-worker.cjs +12309 -0
- package/dist/kernel-worker.cjs.map +1 -0
- package/dist/kernel-worker.d.cts +2 -0
- package/dist/kernel-worker.d.ts +2 -0
- package/dist/kernel-worker.js +12288 -0
- package/dist/kernel-worker.js.map +1 -0
- package/dist/pack.cjs +459 -0
- package/dist/pack.cjs.map +1 -0
- package/dist/pack.d.cts +119 -0
- package/dist/pack.d.ts +119 -0
- package/dist/pack.js +408 -0
- package/dist/pack.js.map +1 -0
- package/dist/ppt.cjs +7427 -0
- package/dist/ppt.cjs.map +1 -0
- package/dist/ppt.d.cts +1432 -0
- package/dist/ppt.d.ts +1432 -0
- package/dist/ppt.js +7355 -0
- package/dist/ppt.js.map +1 -0
- package/dist/shared.cjs +105 -0
- package/dist/shared.cjs.map +1 -0
- package/dist/shared.d.cts +31 -0
- package/dist/shared.d.ts +31 -0
- package/dist/shared.js +81 -0
- package/dist/shared.js.map +1 -0
- package/dist/types-BVkkdcfh.d.cts +54 -0
- package/dist/types-BVkkdcfh.d.ts +54 -0
- package/dist/types-C2vQfM-b.d.cts +102 -0
- package/dist/types-C2vQfM-b.d.ts +102 -0
- package/dist/view-DAkeR0RW.d.ts +1199 -0
- package/dist/view-DX2YAG5A.d.cts +1199 -0
- package/dist/word-view.cjs +1824 -0
- package/dist/word-view.cjs.map +1 -0
- package/dist/word-view.d.cts +2 -0
- package/dist/word-view.d.ts +2 -0
- package/dist/word-view.js +1791 -0
- package/dist/word-view.js.map +1 -0
- package/dist/word.cjs +14111 -0
- package/dist/word.cjs.map +1 -0
- package/dist/word.d.cts +3042 -0
- package/dist/word.d.ts +3042 -0
- package/dist/word.js +14016 -0
- package/dist/word.js.map +1 -0
- package/package.json +103 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/word/view.ts","../src/word/namespaces.ts","../src/pack/part.ts","../src/pack/package.ts","../src/word/style-queries.ts","../src/word/list-tables.ts","../src/word/runs.ts","../src/word/stories.ts","../src/word/list-drawings.ts","../src/word/indices.ts","../src/word/list-paragraphs.ts","../src/word/plan-executors/placeholder-image.ts","../src/word/section-queries.ts","../src/word/plan-executors/insert-image-ops.ts"],"sourcesContent":["export { W_NS, W14_NS } from './namespaces.js';\nexport { buildIndices, type PackageIndices } from './indices.js';\nexport {\n buildParagraphInfo,\n type ParagraphFormat,\n type ParagraphInfo,\n} from './list-paragraphs.js';\nexport { listTables, type TableInfo, type TableCellInfo } from './list-tables.js';\nexport { listDrawings, type DrawingInfo } from './list-drawings.js';\nexport {\n listSections,\n type HeaderFooterRef,\n type HeaderFooterType,\n type SectionInfo,\n type SectionType,\n} from './section-queries.js';\nexport {\n getImageBytesSync,\n type ImageBytesResult,\n} from './plan-executors/insert-image-ops.js';\nexport { getStoryParagraphs, listStories, type StoryId } from './stories.js';\nexport type { CommentInfo } from './comment-queries.js';\nexport type { RevisionInfo } from './revision-queries.js';\n","/**\n * OOXML 常用命名空间 URI 常量。\n *\n * 所有 XPath / DOM 查询必须按 URI 比较(不依赖前缀)—— serializer 可能改前缀,URI 不变。\n * 来源:docs/01 附录 A。\n */\n\n/** Word 正文命名空间 */\nexport const W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';\n\n/** 关系(.rels 文件 / r:id 引用) */\nexport const R_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';\n\n/** DrawingML(Word / Excel / PPT 共用) */\nexport const A_NS = 'http://schemas.openxmlformats.org/drawingml/2006/main';\n\n/** DrawingML wordprocessing 容器(wp:inline / wp:anchor / wp:extent / wp:wrap*) */\nexport const WP_NS = 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing';\n\n/** DrawingML picture(pic:pic) */\nexport const PIC_NS = 'http://schemas.openxmlformats.org/drawingml/2006/picture';\n\n/** DrawingML chart(c:chart 引用) */\nexport const CHART_NS = 'http://schemas.openxmlformats.org/drawingml/2006/chart';\n\n/** DrawingML diagram(SmartArt;dgm:relIds) */\nexport const DGM_NS = 'http://schemas.openxmlformats.org/drawingml/2006/diagram';\n\n/** Word 2010+ wordprocessing shape(wps:wsp / wps:txbx) */\nexport const WPS_NS = 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape';\n\n/** Word 2010+ wordprocessing group(wpg:wgp) */\nexport const WPG_NS = 'http://schemas.microsoft.com/office/word/2010/wordprocessingGroup';\n\n/** Word 2010+ wordprocessing canvas(wpc:wpc) */\nexport const WPC_NS = 'http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas';\n\n/** Markup Compatibility(Choice / Fallback) */\nexport const MC_NS = 'http://schemas.openxmlformats.org/markup-compatibility/2006';\n\n/** VML(旧版形状降级;v:line / v:rect / v:shape,多见于 mc:Fallback) */\nexport const VML_NS = 'urn:schemas-microsoft-com:vml';\n\n/** Word 2010+ 扩展(w14:paraId / w14:textId) */\nexport const W14_NS = 'http://schemas.microsoft.com/office/word/2010/wordml';\n\n/** Flat OPC 包格式 */\nexport const PKG_NS = 'http://schemas.microsoft.com/office/2006/xmlPackage';\n\n/** xml:space 等 xml 标准命名空间 */\nexport const XML_NS = 'http://www.w3.org/XML/1998/namespace';\n\n/** OMML 数学公式命名空间(Word「插入→公式」生成的 m:oMath / m:oMathPara) */\nexport const MATH_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/math';\n","/**\n * Part 构造 / 变更 / 查询 的辅助函数。\n *\n * 所有 Part 的\"创建\"都走这里(而非直接字面量),以便:\n * - 守住 path 格式(无前导斜杠)\n * - 守住 XML / 二进制 互斥\n * - 统一 `_dirty` / `_originalBytes` 初始化语义\n */\nimport type { Part } from './types.js';\n\nexport interface CreateXmlPartSpec {\n /** Part 路径,无前导斜杠,如 'word/document.xml' */\n path: string;\n /** MIME content type */\n contentType: string;\n /** 已解析好的 XML DOM(slimdom Document) */\n xmlDom: Document;\n /**\n * 装载时的原始字节。\n * - 传入(来自装载路径):`_dirty` 初始为 `false`,可在 `toZip` 时零 serialize 复用\n * - 省略(新建 part 的场景,后续 #9/#10 的插入新 XML part):`_dirty` 初始为 `true`\n */\n originalBytes?: Uint8Array;\n}\n\nexport interface CreateBinaryPartSpec {\n /** Part 路径,无前导斜杠,如 'word/media/image1.png' */\n path: string;\n /** MIME content type,如 'image/png' */\n contentType: string;\n /** 二进制载荷 */\n bytes: Uint8Array;\n /**\n * 装载时的原始字节;二进制通常装载即最终态,省略时默认 = `bytes`。\n * 二进制 part 初始 `_dirty` 恒为 `false`。\n */\n originalBytes?: Uint8Array;\n}\n\nfunction assertPath(path: string): void {\n if (typeof path !== 'string' || path.length === 0) {\n throw new Error(`invalid part path: must be non-empty string, got: ${JSON.stringify(path)}`);\n }\n if (path.startsWith('/')) {\n throw new Error(`invalid part path: must not start with '/', got: ${path}`);\n }\n}\n\nfunction assertContentType(contentType: string): void {\n if (typeof contentType !== 'string' || contentType.length === 0) {\n throw new Error(`invalid contentType: must be non-empty string`);\n }\n}\n\n/** 从已解析的 XML DOM 创建 XML part。`originalBytes` 传入 → dirty=false;否则 dirty=true(新建场景) */\nexport function createXmlPart(spec: CreateXmlPartSpec): Part {\n assertPath(spec.path);\n assertContentType(spec.contentType);\n if (!spec.xmlDom) {\n throw new Error(`createXmlPart: xmlDom required for ${spec.path}`);\n }\n return {\n path: spec.path,\n contentType: spec.contentType,\n xmlDom: spec.xmlDom,\n _originalBytes: spec.originalBytes,\n _dirty: spec.originalBytes === undefined,\n };\n}\n\n/** 从二进制字节创建 binary part。`originalBytes` 省略时默认 = `bytes`。dirty 恒为 false */\nexport function createBinaryPart(spec: CreateBinaryPartSpec): Part {\n assertPath(spec.path);\n assertContentType(spec.contentType);\n if (!spec.bytes) {\n throw new Error(`createBinaryPart: bytes required for ${spec.path}`);\n }\n return {\n path: spec.path,\n contentType: spec.contentType,\n bytes: spec.bytes,\n _originalBytes: spec.originalBytes ?? spec.bytes,\n _dirty: false,\n };\n}\n\n/** 标记 part 被改动过。toZip / toFlatOpc 会重 serialize 此 part */\nexport function markDirty(part: Part): void {\n part._dirty = true;\n}\n\n/** 查询 part 是否被改动(read-only;读操作不改 part) */\nexport function isDirty(part: Part): boolean {\n return part._dirty;\n}\n\n/** XML part 判定(拥有 xmlDom) */\nexport function isXmlPart(part: Part): boolean {\n return part.xmlDom !== undefined;\n}\n\n/** 二进制 part 判定(拥有 bytes 但无 xmlDom) */\nexport function isBinaryPart(part: Part): boolean {\n return part.bytes !== undefined && part.xmlDom === undefined;\n}\n","/**\n * Package 集合层:create / set / get / has / delete / list\n *\n * Package 只是 path→Part 的 Map 容器;零业务语义,存储格式由 adapter 层(#9 zip / #10 Flat OPC)决定。\n */\nimport type { Package, Part } from './types.js';\n\n/** 空包。后续由 adapter 层填充 parts */\nexport function createPackage(): Package {\n return { parts: new Map() };\n}\n\n/** 以 part.path 为 key 放入 pkg。已存在同 path 时覆盖 */\nexport function setPart(pkg: Package, part: Part): void {\n pkg.parts.set(part.path, part);\n}\n\n/** 按 path 取回 part;不存在返 undefined */\nexport function getPart(pkg: Package, path: string): Part | undefined {\n return pkg.parts.get(path);\n}\n\n/** 按 path 判断存在 */\nexport function hasPart(pkg: Package, path: string): boolean {\n return pkg.parts.has(path);\n}\n\n/** 删除 part,返回是否删除成功(存在即删、不存在返 false) */\nexport function deletePart(pkg: Package, path: string): boolean {\n return pkg.parts.delete(path);\n}\n\n/** 所有 parts 快照数组(稳定遍历顺序 = Map 插入顺序) */\nexport function listParts(pkg: Package): Part[] {\n return Array.from(pkg.parts.values());\n}\n","/**\n * style-queries —— styles.xml 解析与样式索引构建(#181)。\n *\n * 职责:\n * - `buildStylesIndex(pkg)`:扫 Package 找 styles.xml,构建 Map<styleId, StyleInfo>\n * - `listStyles(stylesIdx, opts?)`:按可选 type 过滤后返回数组\n * - `getStyleById(stylesIdx, styleId)`:O(1) 精确查找\n *\n * 健壮性契约:\n * - styles.xml 不存在 → 返回空 Map,不抛错\n * - `w:type` 未知值 → fallback 到 'paragraph'(OOXML 规范默认)\n * - `w:name` 子元素缺失 → name fallback 到 styleId 本身\n * - 同一 styleId 重复定义 → 后者覆盖前者(与 Word 行为一致)\n * - basedOn 环路 → SDK 不检测,消费方遍历时须自行防环\n *\n * 不暴露 `builtIn` 字段:`<w:builtIn/>` 元素在实际文档中不一致(Word 某些版本省略),\n * 单向可信(存在则肯定是内置,不存在不代表是自定义)会误导消费方。\n * 消费方应通过 `name` + basedOn 链判断语义角色。\n */\nimport type { Package } from '../pack/index.js';\nimport { W_NS } from './namespaces.js';\n\n// ─────────── 类型 ───────────\n\n/** OOXML 定义的四种样式类型(`w:style@w:type`)。 */\nexport type StyleType = 'paragraph' | 'character' | 'table' | 'numbering';\n\nexport interface StyleInfo {\n /** `w:style@w:styleId`。段落 `w:pStyle@w:val` 引用的 key,大小写敏感。 */\n styleId: string;\n /**\n * `w:name@w:val`。\n * - OOXML 规范:内置样式(如 Heading 1、Normal)的 name 永远是英文规范名,\n * 不随文档语言变化。消费方可据此判断语义角色。\n * - 自定义样式:name 为用户输入,可能是中文或任意字符串。\n * - 若 `<w:name>` 元素缺失(非规范文档),fallback 到 `styleId`。\n */\n name: string;\n /**\n * `w:style@w:type`。\n * 未知值 fallback 到 `'paragraph'`(OOXML 规范默认类型)。\n */\n type: StyleType;\n /**\n * 父样式的 `styleId`(`w:basedOn@w:val`)。\n * 根样式(如 Normal)无继承关系,此字段不存在。\n *\n * ⚠ 消费方遍历 basedOn 链时须自行防环,SDK 不检测循环继承。\n */\n basedOn?: string;\n /**\n * 样式定义中的大纲级别(`w:style/w:pPr/w:outlineLvl@w:val`),0–8。\n * 0 = 标题 1,1 = 标题 2,…,8 = 标题 9;9 表示正文(不视为标题)。\n *\n * WPS / Word 的自定义标题样式(如\"公文-一级标题\")通常不 basedOn heading 1,\n * 而是在此字段直接编码级别。消费方可将其作为 basedOn 链之外的第二信号。\n *\n * 字段不存在 = 样式未设置大纲级别(继承自父样式或使用默认值)。\n */\n outlineLevel?: number;\n}\n\nexport interface ListStylesOptions {\n /** 按样式类型过滤。不传则返回全部样式(含所有类型)。 */\n type?: StyleType;\n}\n\n// ─────────── styles.xml 的标准 ContentType ───────────\n\nconst STYLES_CONTENT_TYPE =\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml';\n\n// ─────────── 内部工具 ───────────\n\n/** 按 contentType 定位 styles.xml Part(不硬编码路径)。 */\nfunction findStylesPart(pkg: Package): { xmlDom: Document } | undefined {\n for (const part of pkg.parts.values()) {\n if (part.contentType === STYLES_CONTENT_TYPE && part.xmlDom) {\n return part as { xmlDom: Document };\n }\n }\n return undefined;\n}\n\n/** 取第一个直接子元素(指定 namespace + localName)的某属性值。 */\nfunction childAttr(\n el: Element,\n ns: string,\n localName: string,\n attrNs: string,\n attrLocal: string,\n): string | null {\n const children = el.childNodes;\n for (let i = 0; i < children.length; i++) {\n const c = children[i];\n if (\n c &&\n c.nodeType === 1 &&\n (c as Element).namespaceURI === ns &&\n (c as Element).localName === localName\n ) {\n return (c as Element).getAttributeNS(attrNs, attrLocal);\n }\n }\n return null;\n}\n\n/**\n * 从 `<w:style>` 元素读取其 pPr 中的 outlineLvl。\n * 返回 0–8 的数字,或 undefined(未设置 / 值为 9 表示正文不视为标题)。\n */\nfunction styleOutlineLevel(styleEl: Element): number | undefined {\n const children = styleEl.childNodes;\n for (let i = 0; i < children.length; i++) {\n const c = children[i];\n if (!c || c.nodeType !== 1) continue;\n const el = c as Element;\n if (el.namespaceURI !== W_NS || el.localName !== 'pPr') continue;\n\n // 找到 pPr,再找 outlineLvl\n const pPrChildren = el.childNodes;\n for (let j = 0; j < pPrChildren.length; j++) {\n const pc = pPrChildren[j];\n if (!pc || pc.nodeType !== 1) continue;\n const pcel = pc as Element;\n if (pcel.namespaceURI !== W_NS || pcel.localName !== 'outlineLvl') continue;\n\n const raw = pcel.getAttributeNS(W_NS, 'val');\n if (raw === null) return undefined;\n const n = parseInt(raw, 10);\n // 0–8 才是标题级别;9 表示正文,不视为标题\n return Number.isFinite(n) && n >= 0 && n <= 8 ? n : undefined;\n }\n }\n return undefined;\n}\n\n/** `w:type` 原始字符串 → StyleType(未知值 fallback 到 'paragraph')。 */\nfunction parseStyleType(raw: string | null): StyleType {\n switch (raw) {\n case 'paragraph':\n return 'paragraph';\n case 'character':\n return 'character';\n case 'table':\n return 'table';\n case 'numbering':\n return 'numbering';\n default:\n return 'paragraph';\n }\n}\n\n// ─────────── 公开 API ───────────\n\n/**\n * 扫 Package 构建样式索引。\n *\n * - styles.xml 不存在 → 返回空 Map,不抛错\n * - 同一 styleId 重复出现 → 后者覆盖(与 Word 行为一致)\n */\nexport function buildStylesIndex(pkg: Package): Map<string, StyleInfo> {\n const idx = new Map<string, StyleInfo>();\n\n const stylesPart = findStylesPart(pkg);\n if (!stylesPart) return idx;\n\n const styleEls = stylesPart.xmlDom.getElementsByTagNameNS(W_NS, 'style');\n for (let i = 0; i < styleEls.length; i++) {\n const el = styleEls[i];\n if (!el) continue;\n\n const styleId = el.getAttributeNS(W_NS, 'styleId');\n if (!styleId) continue; // styleId 缺失则跳过(非规范文档)\n\n const type = parseStyleType(el.getAttributeNS(W_NS, 'type'));\n const name = childAttr(el, W_NS, 'name', W_NS, 'val') ?? styleId;\n const basedOnRaw = childAttr(el, W_NS, 'basedOn', W_NS, 'val');\n\n const info: StyleInfo = { styleId, name, type };\n if (basedOnRaw) info.basedOn = basedOnRaw;\n\n // 大纲级别:从 w:style/w:pPr/w:outlineLvl@w:val 读取\n const outlineLvlVal = styleOutlineLevel(el);\n if (outlineLvlVal !== undefined) info.outlineLevel = outlineLvlVal;\n\n idx.set(styleId, info); // 重复时后者覆盖\n }\n\n return idx;\n}\n\n/**\n * 列出样式索引中的所有样式。\n *\n * @param stylesIdx 由 `buildStylesIndex` 构建的 Map\n * @param opts 可选过滤(目前支持按 type 过滤)\n */\nexport function listStyles(\n stylesIdx: Map<string, StyleInfo>,\n opts?: ListStylesOptions,\n): StyleInfo[] {\n const all = Array.from(stylesIdx.values());\n if (!opts?.type) return all;\n return all.filter((s) => s.type === opts.type);\n}\n\n/**\n * 按 styleId 精确查找,O(1)。\n * styleId 大小写敏感(与 OOXML 规范一致)。\n */\nexport function getStyleById(stylesIdx: Map<string, StyleInfo>, styleId: string): StyleInfo | null {\n return stylesIdx.get(styleId) ?? null;\n}\n","/**\n * 表格枚举与结构分析(#185)\n *\n * 为骨架化 M2 阶段(表格分类/取样/域化)提供表格数据。\n *\n * 设计原则:\n * - 只走 getElementsByTagNameNS,禁用 XPath(性能约束,同 list-paragraphs.ts)\n * - tableId 由 buildIndices 在装载期分配(tbl-0001 起),存入 tblIdx\n * - 默认只枚举主文档 body(scope: 'body'),不含嵌套表格(includeNested: false)\n * - 前后邻段落(prevParaId / nextParaId)只取紧邻兄弟 <w:p>,供题注绑定(#193)\n */\n\nimport { serializeToWellFormedString } from 'slimdom';\nimport { getPart, isXmlPart, type Package } from '../pack/index.js';\nimport { A_NS, R_NS, W_NS, W14_NS } from './namespaces.js';\nimport type { PackageIndices } from './indices.js';\nimport { getParagraphText } from './runs.js';\nimport { findStoryFor, resolveScope, type Story } from './stories.js';\n\n// ─────────── 公开类型 ───────────\n\n/**\n * 行高(对照 columnWidths)。w:tr/w:trPr/w:trHeight。\n *\n * 空行塌缩正是 hRule+val 没带过去所致,两者必须一起读/写。\n */\nexport interface RowHeight {\n /** w:trHeight/@w:val,行高 twips(1 twip = 1/20 磅)*/\n val: number;\n /** w:trHeight/@w:hRule:auto(默认,最小高度) / atLeast(至少) / exact(精确)。\n * 缺省(无 hRule 属性)按 OOXML 默认 'auto'。*/\n hRule: 'auto' | 'atLeast' | 'exact';\n}\n\n/**\n * 单个物理 <w:tc> 的 span 信息,携带文本与合并状态。\n * cellGrid[i][j] 对应第 i 行第 j 个物理 <w:tc>。\n */\nexport interface TableCellInfo {\n /** 单元格纯文本(多段以 \\n 连接),与 cells[i][j] 完全一致 */\n text: string;\n /** 水平合并跨列数(<w:gridSpan w:val>,无则默认 1) */\n gridSpan: number;\n /**\n * 垂直合并状态:\n * 'restart' = 纵向合并的起始格(<w:vMerge w:val=\"restart\"/>)\n * 'continue' = 被并入上方格的续行占位格(<w:vMerge/> 无 val 或 val=\"continue\")\n * null = 无纵向合并\n */\n vMerge: 'restart' | 'continue' | null;\n /** Direct cell width from w:tcPr/w:tcW, in twips. */\n width?: number;\n /** Direct cell shading from w:tcPr/w:shd. */\n shading?: {\n fill?: string;\n color?: string;\n val?: string;\n };\n /** Direct vertical alignment from w:tcPr/w:vAlign. */\n verticalAlignment?: 'top' | 'center' | 'bottom' | 'both';\n /**\n * 单元格首段(首个直属 `<w:p>`)的 paraId(w14:paraId)。无首段或首段缺 paraId 时 undefined。\n *\n * 用途(task-bug2-cell-image,决策点 C):merge 重建表后要往**正确 cell** 插单元格图,\n * 而 clone 出来的表 cell 段 paraId 是 cloneTableAfter 重分配的新值——调用方拿不到。\n * 暴露 firstParaId 后,调用方对 clone 表再 `tables.list({includeCellTexts:true})` 即可拿到\n * 每个 cell 首段 paraId,用 `plan.insertImageAfter(firstParaId)` 把图插进该 cell(insertImageAfter\n * 在 anchor 的 parentNode 内插段——anchor 在 `<w:tc>` 里,新图段就落在该 cell 内)。\n *\n * 与 replaceTableCellText 无冲突:后者只动 cell **首段**的 run,插在首段之后的独立图段不受影响。\n */\n firstParaId?: string;\n /**\n * Direct paragraph ids in this cell, in cell content order.\n *\n * Nested table paragraphs are not included.\n */\n paragraphIds: string[];\n /**\n * Direct content blocks in this cell, in OOXML child-node order.\n *\n * Paragraph drawings are emitted as drawing blocks as well, so callers can\n * preserve flows such as text paragraphs, then an image, then more text.\n */\n blocks: TableCellBlockInfo[];\n /**\n * 本单元格内直属 `<w:drawing>`(图片/形状等)的 drawingId 列表(与 drawingIdx 同 ID 空间)。\n *\n * 用途(task-bug2-cell-image,决策点 A1):表格单元格内的图此前在 cellGrid 这层就丢了\n * (cellGrid 只搬 cell 文字),导致单元格图在 structure 抽取阶段成孤儿(图字节在 word/media\n * 但 body 无 blip 引用)。收录 drawingId 后,typeset 抽表时把 cell 图当 content 带出、merge\n * 重建时用 `drawing.getImageBytes()` + `insertImageAfter(firstParaId)` 把图插回对应 cell。\n *\n * 收录范围:只取**本 cell 子树内、且不在嵌套表/嵌套 cell 内**的 drawing(嵌套表 cell 的图归属\n * 嵌套表自己的 cellGrid,不上浮污染外层)。空(无图)时为 `[]`(与 cells 同条件填充,\n * 字段恒在便于下游免判 undefined)。\n */\n imageDrawingIds: string[];\n}\n\nexport type TableCellBlockInfo =\n | { type: 'paragraph'; paraId: string; text: string }\n | { type: 'drawing'; drawingId: string; paraId?: string }\n | { type: 'table'; tableId: string };\n\nexport interface TableInfo {\n /** 分配的表格 ID(装载期顺序,'tbl-0001' 起),供 plan executor 引用 */\n tableId: string;\n /** 所在 part 路径(如 'word/document.xml')*/\n partPath: string;\n /** 在本次枚举结果中的顺序(1-based)*/\n index: number;\n /** 是否嵌套在另一个表格内(祖先链上存在 <w:tbl>)*/\n isNested: boolean;\n /** 行数(w:tr 直接子元素数量)*/\n rowCount: number;\n /** 列数(w:tblGrid/w:gridCol 数量;无 tblGrid 时为 0)*/\n colCount: number;\n /** 是否有合并单元格(gridSpan > 1 或存在 vMerge)*/\n hasMerge: boolean;\n /** 表格样式 ID(w:tblPr/w:tblStyle @w:val),未设时 undefined */\n tableStyleId?: string;\n /** 是否包含图片(w:drawing 元素)*/\n hasImages: boolean;\n /** 是否包含嵌套表格(直接子孙 <w:tbl>)*/\n hasNestedTables: boolean;\n /**\n * 紧邻前一个兄弟 <w:p> 的 paraId,用于题注绑定(#193)。\n * 仅检查紧邻兄弟:若前一个兄弟不是 <w:p>(如另一个 <w:tbl>)则为 undefined。\n */\n prevParaId?: string;\n /**\n * 紧邻后一个兄弟 <w:p> 的 paraId,用于题注绑定(#193)。\n * 仅检查紧邻兄弟。\n */\n nextParaId?: string;\n /**\n * 每列的列宽(twips,来自 `<w:tblGrid>/<w:gridCol w:w=\"...\">`)。\n *\n * - `columnWidths[i]` = 第 i 列的 `w:w` 值(1 twip = 1/20 磅)\n * - 无 `<w:tblGrid>` 时为 `[]`(空数组)\n * - `tblLayout=fixed` 的表格列宽完全由此决定;`auto` 布局表格也记录但仅供参考\n * - 供 merger 套用源表列宽(task-core-table-grid:克隆模板时保真列宽)\n */\n columnWidths: number[];\n /**\n * 每行行高,doc 序,每行一个;该行无 <w:trHeight> 则为 null。\n *\n * - `rowHeights[i]` = 第 i 行的 w:trPr/w:trHeight val+hRule\n * - 恒填(不依赖 includeCellTexts)\n * - 只取直属 <w:tr>,不误抓嵌套表行\n * - 供 merger 套用源表行高(空行塌缩修复)\n */\n rowHeights: (RowHeight | null)[];\n /**\n * 单元格文本网格(#186)。仅当 `includeCellTexts: true` 时填充。\n *\n * - `cells[i][j]` = 第 i 行第 j 列单元格的纯文本\n * - 单元格内含多段时用 `\\n` 连接(保留分段信号,供 has-multi-content 判定)\n * - 嵌套表格内的段落不展开(不污染外层网格)\n */\n cells?: string[][];\n /**\n * 单元格网格(span-aware),仅当 `includeCellTexts: true` 时填充(与 cells 同条件)。\n *\n * - `cellGrid[i][j]` 对应第 i 行第 j 个**物理** `<w:tc>`,携带 gridSpan/vMerge\n * - 与 cells 同源同序:`cells[i][j] === cellGrid[i][j].text`\n * - 嵌套表内段落不展开(与 extractCellTexts 一致)\n */\n cellGrid?: TableCellInfo[][];\n}\n\nexport interface ListTablesOptions {\n /** 枚举范围(默认 'body')*/\n scope?: 'body' | 'all';\n /** 是否包含嵌套在其他表格内的表格(默认 false,只返回顶层表格)*/\n includeNested?: boolean;\n /**\n * 是否抽取单元格文本(#186)。默认 false(保持 #185 默认行为)。\n *\n * 开启后每张 TableInfo 会带 `cells: string[][]`,用于关键词匹配/分类。\n * 性能:每张表会枚举所有 <w:tc> 及其直属 <w:p>,按需开启。\n */\n includeCellTexts?: boolean;\n}\n\n// ─────────── 内部工具 ───────────\n\n/** 把 HTMLCollection-ish 枚举结果快照成数组(避免 live 行为)*/\nfunction snapshot<T extends Element>(col: { length: number } & Record<number, T>): T[] {\n const out: T[] = [];\n for (let i = 0; i < col.length; i++) {\n const el = col[i];\n if (el) out.push(el);\n }\n return out;\n}\n\n/** 判断元素是否嵌套在某个祖先 <w:tbl> 内(止于 story 根元素)*/\nfunction hasTableAncestor(el: Element): boolean {\n let p = el.parentElement;\n while (p) {\n if (p.namespaceURI === W_NS) {\n if (p.localName === 'tbl') return true;\n // story 根元素:停止向上走\n if (\n p.localName === 'body' ||\n p.localName === 'hdr' ||\n p.localName === 'ftr' ||\n p.localName === 'footnote' ||\n p.localName === 'endnote' ||\n p.localName === 'comment'\n ) {\n return false;\n }\n }\n p = p.parentElement;\n }\n return false;\n}\n\n/** 紧邻前一个兄弟 <w:p> 的 paraId(只看直接前兄弟)*/\nfunction prevParaId(tbl: Element): string | undefined {\n const sib = tbl.previousElementSibling;\n if (sib && sib.namespaceURI === W_NS && sib.localName === 'p') {\n return sib.getAttributeNS(W14_NS, 'paraId') ?? undefined;\n }\n return undefined;\n}\n\n/** 紧邻后一个兄弟 <w:p> 的 paraId(只看直接后兄弟)*/\nfunction nextParaId(tbl: Element): string | undefined {\n const sib = tbl.nextElementSibling;\n if (sib && sib.namespaceURI === W_NS && sib.localName === 'p') {\n return sib.getAttributeNS(W14_NS, 'paraId') ?? undefined;\n }\n return undefined;\n}\n\n/**\n * 读取 <w:tc> 的直属 <w:tcPr> 中的 gridSpan(返回整数,无则 1)。\n * 只查 tcPr 直属子元素,不用 getElementsByTagNameNS(避免误抓嵌套表)。\n */\nfunction readGridSpan(tc: Element): number {\n for (let i = 0; i < tc.childNodes.length; i++) {\n const child = tc.childNodes[i] as Element;\n if (child.nodeType !== 1 || child.namespaceURI !== W_NS || child.localName !== 'tcPr') continue;\n // found tcPr — look for direct child gridSpan\n for (let j = 0; j < child.childNodes.length; j++) {\n const sub = child.childNodes[j] as Element;\n if (sub.nodeType !== 1 || sub.namespaceURI !== W_NS || sub.localName !== 'gridSpan') continue;\n const val = sub.getAttributeNS(W_NS, 'val');\n const parsed = val !== null ? parseInt(val, 10) : NaN;\n return !isNaN(parsed) && parsed > 1 ? parsed : 1;\n }\n break; // tcPr found but no gridSpan child\n }\n return 1;\n}\n\n/**\n * 读取 <w:tc> 的直属 <w:tcPr> 中的 vMerge 状态。\n * - 'restart' = <w:vMerge w:val=\"restart\"/>\n * - 'continue' = <w:vMerge/> (无 val 或 val=\"continue\")\n * - null = 无 vMerge 元素\n * 只查 tcPr 直属子元素,不用 getElementsByTagNameNS。\n */\nfunction readVMerge(tc: Element): 'restart' | 'continue' | null {\n for (let i = 0; i < tc.childNodes.length; i++) {\n const child = tc.childNodes[i] as Element;\n if (child.nodeType !== 1 || child.namespaceURI !== W_NS || child.localName !== 'tcPr') continue;\n // found tcPr — look for direct child vMerge\n for (let j = 0; j < child.childNodes.length; j++) {\n const sub = child.childNodes[j] as Element;\n if (sub.nodeType !== 1 || sub.namespaceURI !== W_NS || sub.localName !== 'vMerge') continue;\n const val = sub.getAttributeNS(W_NS, 'val');\n return val === 'restart' ? 'restart' : 'continue';\n }\n break; // tcPr found but no vMerge child\n }\n return null;\n}\n\n/**\n * 读取 <w:tr> 的直属 <w:trPr> 中的 trHeight(val + hRule)。\n * - 无 trPr 或 trPr 内无 trHeight → null\n * - 只查 trPr 直属子元素,不用 getElementsByTagNameNS(避免误抓嵌套表)。\n */\nfunction readCellWidth(tc: Element): number | undefined {\n const tcPr = firstDirectChildW(tc, 'tcPr');\n if (!tcPr) return undefined;\n const tcW = firstDirectChildW(tcPr, 'tcW');\n if (!tcW) return undefined;\n const raw = tcW.getAttributeNS(W_NS, 'w') ?? tcW.getAttribute('w:w');\n if (raw === null) return undefined;\n const parsed = parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction readCellShading(tc: Element): { fill?: string; color?: string; val?: string } | undefined {\n const tcPr = firstDirectChildW(tc, 'tcPr');\n if (!tcPr) return undefined;\n const shd = firstDirectChildW(tcPr, 'shd');\n if (!shd) return undefined;\n const result: { fill?: string; color?: string; val?: string } = {};\n const fill = shd.getAttributeNS(W_NS, 'fill') ?? shd.getAttribute('w:fill');\n const color = shd.getAttributeNS(W_NS, 'color') ?? shd.getAttribute('w:color');\n const val = shd.getAttributeNS(W_NS, 'val') ?? shd.getAttribute('w:val');\n if (fill !== null) result.fill = fill;\n if (color !== null) result.color = color;\n if (val !== null) result.val = val;\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\nfunction readCellVerticalAlignment(tc: Element): 'top' | 'center' | 'bottom' | 'both' | undefined {\n const tcPr = firstDirectChildW(tc, 'tcPr');\n if (!tcPr) return undefined;\n const vAlign = firstDirectChildW(tcPr, 'vAlign');\n if (!vAlign) return undefined;\n const raw = vAlign.getAttributeNS(W_NS, 'val') ?? vAlign.getAttribute('w:val');\n if (raw === 'top' || raw === 'center' || raw === 'bottom' || raw === 'both') return raw;\n return undefined;\n}\n\nfunction firstDirectChildW(el: Element, localName: string): Element | null {\n for (let i = 0; i < el.childNodes.length; i++) {\n const child = el.childNodes[i];\n if (\n child?.nodeType === 1 &&\n (child as Element).namespaceURI === W_NS &&\n (child as Element).localName === localName\n ) {\n return child as Element;\n }\n }\n return null;\n}\n\nfunction readTrHeight(tr: Element): RowHeight | null {\n for (let i = 0; i < tr.childNodes.length; i++) {\n const child = tr.childNodes[i] as Element;\n if (child.nodeType !== 1 || child.namespaceURI !== W_NS || child.localName !== 'trPr') continue;\n // found trPr — look for direct child trHeight\n for (let j = 0; j < child.childNodes.length; j++) {\n const sub = child.childNodes[j] as Element;\n if (sub.nodeType !== 1 || sub.namespaceURI !== W_NS || sub.localName !== 'trHeight') continue;\n const valAttr = sub.getAttributeNS(W_NS, 'val');\n const val = valAttr !== null ? parseInt(valAttr, 10) : 0;\n const hRuleAttr = sub.getAttributeNS(W_NS, 'hRule');\n let hRule: RowHeight['hRule'] = 'auto';\n if (hRuleAttr === 'atLeast') hRule = 'atLeast';\n else if (hRuleAttr === 'exact') hRule = 'exact';\n return { val: isNaN(val) ? 0 : val, hRule };\n }\n break; // trPr found but no trHeight child\n }\n return null;\n}\n\n/**\n * 取 `<w:tc>` 首个直属 `<w:p>` 的 w14:paraId(task-bug2-cell-image,决策点 C)。\n * 无直属 `<w:p>` 或缺 paraId 时返回 undefined。\n */\nfunction firstCellParaId(tc: Element): string | undefined {\n for (let k = 0; k < tc.childNodes.length; k++) {\n const p = tc.childNodes[k] as Element;\n if (!p || p.nodeType !== 1 || p.namespaceURI !== W_NS || p.localName !== 'p') continue;\n return p.getAttributeNS(W14_NS, 'paraId') ?? undefined;\n }\n return undefined;\n}\n\n/**\n * 收集**本 cell 子树内、不在嵌套表内**的 `<w:drawing>`,反查出 drawingId 列表\n * (task-bug2-cell-image,决策点 A1)。\n *\n * - 用 `getElementsByTagNameNS(W_NS,'drawing')` 取 cell 子树全部 drawing(含嵌套表内的),\n * 再用 `cellOf(drawing) === tc` 过滤掉嵌套表 cell 内的图(它们归属嵌套表自己的 cellGrid)。\n * - 反查 drawingId 走传入的 `drawingToId`(drawingIdx 反转),未命中(不在索引内,\n * 如 mc:Fallback 内被装载期跳过的 drawing)则跳过——与 drawings.list 枚举集保持一致。\n */\nfunction collectCellDrawingIds(tc: Element, drawingToId: Map<Element, string>): string[] {\n const ids: string[] = [];\n const drawings = tc.getElementsByTagNameNS(W_NS, 'drawing');\n for (let i = 0; i < drawings.length; i++) {\n const dr = drawings[i];\n if (!dr) continue;\n // 只收归属本 cell(最近祖先 <w:tc> 即 tc)的 drawing,排除嵌套表 cell 内的图。\n if (nearestCellOf(dr) !== tc) continue;\n const id = drawingToId.get(dr);\n if (id !== undefined) ids.push(id);\n }\n return ids;\n}\n\n/** 向上找元素最近的祖先 `<w:tc>`(止于 story 根);不在任何 cell 内返回 null。 */\nfunction nearestCellOf(el: Element): Element | null {\n let p = el.parentElement;\n while (p) {\n if (p.namespaceURI === W_NS) {\n if (p.localName === 'tc') return p;\n if (\n p.localName === 'body' ||\n p.localName === 'hdr' ||\n p.localName === 'ftr' ||\n p.localName === 'footnote' ||\n p.localName === 'endnote' ||\n p.localName === 'comment'\n ) {\n return null;\n }\n }\n p = p.parentElement;\n }\n return null;\n}\n\nfunction collectCellParagraphIds(tc: Element): string[] {\n const ids: string[] = [];\n for (let k = 0; k < tc.childNodes.length; k++) {\n const p = tc.childNodes[k] as Element;\n if (!p || p.nodeType !== 1 || p.namespaceURI !== W_NS || p.localName !== 'p') continue;\n const paraId = p.getAttributeNS(W14_NS, 'paraId');\n if (paraId) ids.push(paraId);\n }\n return ids;\n}\n\nfunction directDrawingIdsInParagraph(\n p: Element,\n ownerCell: Element,\n drawingToId: Map<Element, string>,\n): string[] {\n const ids: string[] = [];\n const drawings = p.getElementsByTagNameNS(W_NS, 'drawing');\n for (let i = 0; i < drawings.length; i++) {\n const dr = drawings[i];\n if (!dr || nearestCellOf(dr) !== ownerCell) continue;\n const id = drawingToId.get(dr);\n if (id !== undefined) ids.push(id);\n }\n return ids;\n}\n\nfunction collectCellBlocks(\n tc: Element,\n drawingToId: Map<Element, string>,\n tableToId: Map<Element, string>,\n): TableCellBlockInfo[] {\n const blocks: TableCellBlockInfo[] = [];\n for (let k = 0; k < tc.childNodes.length; k++) {\n const child = tc.childNodes[k] as Element;\n if (!child || child.nodeType !== 1 || child.namespaceURI !== W_NS) continue;\n\n if (child.localName === 'p') {\n const paraId = child.getAttributeNS(W14_NS, 'paraId') ?? undefined;\n const text = getParagraphText(child);\n const drawingIds = directDrawingIdsInParagraph(child, tc, drawingToId);\n if (paraId && (text.length > 0 || drawingIds.length === 0)) {\n blocks.push({ type: 'paragraph', paraId, text });\n }\n for (const drawingId of drawingIds) {\n blocks.push(\n paraId ? { type: 'drawing', drawingId, paraId } : { type: 'drawing', drawingId },\n );\n }\n continue;\n }\n\n if (child.localName === 'tbl') {\n const tableId = tableToId.get(child);\n if (tableId) blocks.push({ type: 'table', tableId });\n }\n }\n return blocks;\n}\n\n/**\n * 抽取整张表的单元格网格(span-aware, task-core-table-span)。\n * - 只看 <w:tbl> 直属的 <w:tr>,每行枚举直属 <w:tc>\n * - 每个 <w:tc> 内取直属 <w:p>(不展开嵌套表),多段以 `\\n` 连接\n * - 每格携带 gridSpan / vMerge(只读 tcPr 直属子元素,不误抓嵌套表)\n * - 每格携带 firstParaId(首段 paraId)/ imageDrawingIds(cell 内图 drawingId)\n * (task-bug2-cell-image:决策点 C / A1)\n *\n * @param drawingToId drawingIdx 反转(Element→drawingId),用于把 cell 内 `<w:drawing>` 反查成 ID。\n */\nfunction extractCellGrid(\n tbl: Element,\n drawingToId: Map<Element, string>,\n tableToId: Map<Element, string>,\n): TableCellInfo[][] {\n const rows: TableCellInfo[][] = [];\n for (let i = 0; i < tbl.childNodes.length; i++) {\n const tr = tbl.childNodes[i] as Element;\n if (!tr || tr.nodeType !== 1 || tr.namespaceURI !== W_NS || tr.localName !== 'tr') continue;\n\n const rowCells: TableCellInfo[] = [];\n for (let j = 0; j < tr.childNodes.length; j++) {\n const tc = tr.childNodes[j] as Element;\n if (!tc || tc.nodeType !== 1 || tc.namespaceURI !== W_NS || tc.localName !== 'tc') continue;\n\n const paragraphs: string[] = [];\n for (let k = 0; k < tc.childNodes.length; k++) {\n const p = tc.childNodes[k] as Element;\n if (!p || p.nodeType !== 1 || p.namespaceURI !== W_NS || p.localName !== 'p') continue;\n paragraphs.push(getParagraphText(p));\n }\n const cell: TableCellInfo = {\n text: paragraphs.join('\\n'),\n gridSpan: readGridSpan(tc),\n vMerge: readVMerge(tc),\n paragraphIds: collectCellParagraphIds(tc),\n blocks: collectCellBlocks(tc, drawingToId, tableToId),\n imageDrawingIds: collectCellDrawingIds(tc, drawingToId),\n };\n const fpid = firstCellParaId(tc);\n if (fpid !== undefined) cell.firstParaId = fpid;\n const width = readCellWidth(tc);\n if (width !== undefined) cell.width = width;\n const shading = readCellShading(tc);\n if (shading !== undefined) cell.shading = shading;\n const verticalAlignment = readCellVerticalAlignment(tc);\n if (verticalAlignment !== undefined) cell.verticalAlignment = verticalAlignment;\n rowCells.push(cell);\n }\n rows.push(rowCells);\n }\n return rows;\n}\n\n/** 从 <w:tbl> 元素提取结构信息。\n *\n * @param drawingToId drawingIdx 反转(Element→drawingId),仅在 includeCellTexts 时用于\n * cell 图 drawingId 反查;不开 includeCellTexts 时传空 Map 即可(不进 extractCellGrid)。\n */\nfunction analyzeTable(\n tbl: Element,\n tableId: string,\n partPath: string,\n index: number,\n includeCellTexts: boolean,\n drawingToId: Map<Element, string>,\n tableToId: Map<Element, string>,\n): TableInfo {\n let rowCount = 0;\n let hasMerge = false;\n const rowHeights: (RowHeight | null)[] = [];\n\n // 嵌套表深度检测(#203):嵌套表在 tbl/tr/tc/tbl 路径上,不是 tbl 直接子,\n // 旧逻辑扫直接子永远扫不到。getElementsByTagNameNS 只返回后代(不含自身),\n // 命中即说明存在嵌套表。\n const hasNestedTables = tbl.getElementsByTagNameNS(W_NS, 'tbl').length > 0;\n\n // 一次遍历直接子节点:统计行数、检测合并单元格、读取行高\n for (let i = 0; i < tbl.childNodes.length; i++) {\n const child = tbl.childNodes[i] as Element;\n if (child.nodeType !== 1 /* ELEMENT_NODE */) continue;\n if (child.namespaceURI !== W_NS) continue;\n\n if (child.localName === 'tr') {\n rowCount++;\n // 读取行高(直属 tr,不误抓嵌套表行)\n rowHeights.push(readTrHeight(child));\n // 检测合并单元格(只在未确认时扫)\n if (!hasMerge) {\n const cells = snapshot(child.getElementsByTagNameNS(W_NS, 'tc'));\n for (const tc of cells) {\n // 水平合并:gridSpan > 1\n const gridSpanEls = tc.getElementsByTagNameNS(W_NS, 'gridSpan');\n if (gridSpanEls.length > 0) {\n const val = gridSpanEls[0]!.getAttributeNS(W_NS, 'val');\n if (val && Number(val) > 1) {\n hasMerge = true;\n break;\n }\n }\n // 垂直合并:vMerge 存在即可\n if (tc.getElementsByTagNameNS(W_NS, 'vMerge').length > 0) {\n hasMerge = true;\n break;\n }\n }\n }\n }\n }\n\n // 列数 + 列宽:w:tblGrid/w:gridCol 数量及每列 w:w(task-core-table-grid)\n let colCount = 0;\n const columnWidths: number[] = [];\n const tblGridEls = tbl.getElementsByTagNameNS(W_NS, 'tblGrid');\n if (tblGridEls.length > 0) {\n const gridCols = tblGridEls[0]!.getElementsByTagNameNS(W_NS, 'gridCol');\n colCount = gridCols.length;\n for (let i = 0; i < gridCols.length; i++) {\n const wAttr = gridCols[i]!.getAttributeNS(W_NS, 'w');\n columnWidths.push(wAttr !== null ? Number(wAttr) : 0);\n }\n }\n\n // 表格样式\n let tableStyleId: string | undefined;\n const tblStyleEls = tbl.getElementsByTagNameNS(W_NS, 'tblStyle');\n if (tblStyleEls.length > 0) {\n tableStyleId = tblStyleEls[0]!.getAttributeNS(W_NS, 'val') ?? undefined;\n }\n\n // 图片检测:w:drawing 子孙\n const hasImages = tbl.getElementsByTagNameNS(W_NS, 'drawing').length > 0;\n\n const info: TableInfo = {\n tableId,\n partPath,\n index,\n isNested: hasTableAncestor(tbl),\n rowCount,\n colCount,\n hasMerge,\n hasImages,\n hasNestedTables,\n columnWidths,\n rowHeights,\n };\n if (tableStyleId !== undefined) info.tableStyleId = tableStyleId;\n const prev = prevParaId(tbl);\n if (prev !== undefined) info.prevParaId = prev;\n const next = nextParaId(tbl);\n if (next !== undefined) info.nextParaId = next;\n if (includeCellTexts) {\n const grid = extractCellGrid(tbl, drawingToId, tableToId);\n info.cellGrid = grid;\n info.cells = grid.map((row) => row.map((cell) => cell.text));\n }\n\n return info;\n}\n\n/**\n * 把 drawingIdx(id→Element)反转成 Element→id,供 cell 图 drawingId 反查\n * (task-bug2-cell-image, A1)。仅 includeCellTexts 时构建(避免无谓开销)。\n */\nfunction buildDrawingReverseMap(drawingIdx: Map<string, Element>): Map<Element, string> {\n const rev = new Map<Element, string>();\n for (const [id, el] of drawingIdx) rev.set(el, id);\n return rev;\n}\n\nfunction buildTableReverseMap(tblIdx: Map<string, Element>): Map<Element, string> {\n const rev = new Map<Element, string>();\n for (const [id, el] of tblIdx) rev.set(el, id);\n return rev;\n}\n\n/** 找元素所属的 Story(用 contains 检测,stories 数量通常 < 20)*/\nfunction findStoryForEl(el: Element, stories: Story[]): Story | undefined {\n for (const s of stories) {\n if (s.root.contains(el as unknown as Node)) return s;\n }\n return undefined;\n}\n\n// ─────────── 公开函数 ───────────\n\n/**\n * 枚举文档表格,按文档顺序返回(与 tblIdx 插入顺序一致)。\n *\n * - `scope: 'body'`(默认)—— 仅主文档 <w:body>\n * - `includeNested: false`(默认)—— 跳过嵌套在其他表格内的表格\n */\nexport function listTables(\n pkg: Package,\n indices: PackageIndices,\n opts?: ListTablesOptions,\n): TableInfo[] {\n const scope = opts?.scope ?? 'body';\n const includeNested = opts?.includeNested ?? false;\n const includeCellTexts = opts?.includeCellTexts ?? false;\n\n const stories = resolveScope(pkg, scope);\n const result: TableInfo[] = [];\n let idx = 1;\n\n // cell 图 drawingId 反查表:仅 includeCellTexts 时构建(A1)。\n const drawingToId = includeCellTexts\n ? buildDrawingReverseMap(indices.drawingIdx)\n : EMPTY_DRAWING_REV;\n const tableToId = includeCellTexts ? buildTableReverseMap(indices.tblIdx) : EMPTY_TABLE_REV;\n\n // 直接遍历 tblIdx(插入顺序 = 文档顺序)\n for (const [tableId, tbl] of indices.tblIdx) {\n if (!includeNested && hasTableAncestor(tbl)) continue;\n\n const story = findStoryForEl(tbl, stories);\n if (!story) continue; // 不在请求的 scope 内\n\n result.push(\n analyzeTable(tbl, tableId, story.partPath, idx++, includeCellTexts, drawingToId, tableToId),\n );\n }\n\n return result;\n}\n\n/** includeCellTexts=false 时复用的空反查表(不分配)。 */\nconst EMPTY_DRAWING_REV: Map<Element, string> = new Map();\nconst EMPTY_TABLE_REV: Map<Element, string> = new Map();\n\n/**\n * 按 tableId 精确取单个表格,O(1)。不存在返回 null。\n * 入参 `includeCellTexts` 与 `listTables` 同义(默认 false)。\n */\nexport function getTableById(\n pkg: Package,\n indices: PackageIndices,\n tableId: string,\n opts?: { includeCellTexts?: boolean },\n): TableInfo | null {\n const tbl = indices.tblIdx.get(tableId);\n if (!tbl) return null;\n\n // 找所属 story 以获取 partPath\n const stories = resolveScope(pkg, 'all');\n const story = findStoryForEl(tbl, stories);\n if (!story) return null;\n\n const includeCellTexts = opts?.includeCellTexts ?? false;\n const drawingToId = includeCellTexts\n ? buildDrawingReverseMap(indices.drawingIdx)\n : EMPTY_DRAWING_REV;\n const tableToId = includeCellTexts ? buildTableReverseMap(indices.tblIdx) : EMPTY_TABLE_REV;\n\n return analyzeTable(tbl, tableId, story.partPath, 0, includeCellTexts, drawingToId, tableToId);\n}\n\n// ─────────── getTableVerbatim(task-20260616-table-content-driven)───────────\n\n/** 一张格内图的预解析字节(按源 <a:blip @r:embed> 原始 rId 索引)。 */\nexport interface TableVerbatimImage {\n originalRId: string;\n bytes: Uint8Array;\n contentType: string;\n}\n\n/** verbatim 取一张表的自包含 payload(供跨文档搬表 insertTableFrom)。 */\nexport interface TableVerbatim {\n /** `<w:tbl>` 子树序列化字符串(命名空间随子树带入)。 */\n tableXml: string;\n /** 表内所有格内图的预解析字节(按原 rId)。原 rId 解析不到字节的图不收(搬入时 warn 降级)。 */\n images: TableVerbatimImage[];\n}\n\n/**\n * 按 tableId verbatim 取一张表的 OOXML 子树 + 其格内图字节,打包成自包含 payload。\n *\n * 这是\"数据表 = 内容块\"(§13.9)的**源侧读取**:调用方(typeset)拿到此 payload 后用\n * `plan.insertTableFrom(...)` 把整表 verbatim 搬进输出文档(与图片 getImageBytes→insertImageAfter\n * 同构)。表的行/列/值/合并/嵌套/格内图全保真——这里只做读取 + 序列化 + 解析格内图字节,\n * **不裁剪、不重建**。\n *\n * tableId 不存在 → 返回 null。\n */\nexport function getTableVerbatim(\n pkg: Package,\n indices: PackageIndices,\n tableId: string,\n): TableVerbatim | null {\n const tbl = indices.tblIdx.get(tableId);\n if (!tbl) return null;\n\n // 序列化 <w:tbl> 子树(命名空间随子树带入,已验证 importNode 往返保真)。\n const tableXml = serializeToWellFormedString(tbl as never);\n\n // 收集表内所有 <a:blip @r:embed>,按原 rId 解析出原始字节。\n const story = findStoryFor(pkg, tbl);\n const hostPartPath = story?.partPath;\n const images: TableVerbatimImage[] = [];\n if (hostPartPath) {\n const seen = new Set<string>();\n const blips = tbl.getElementsByTagNameNS(A_NS, 'blip');\n for (let i = 0; i < blips.length; i++) {\n const blip = blips[i];\n if (!blip) continue;\n const rId = blip.getAttributeNS(R_NS, 'embed');\n if (!rId || seen.has(rId)) continue;\n seen.add(rId);\n const resolved = resolveImageBytesByRId(pkg, hostPartPath, rId);\n if (resolved) {\n images.push({ originalRId: rId, bytes: resolved.bytes, contentType: resolved.contentType });\n }\n // 解析不到(如外链图/损坏 rels)→ 不收;搬入时该 blip embed 清掉 + warn(结构/文字保真)。\n }\n }\n\n return { tableXml, images };\n}\n\n/**\n * 按宿主 part + rId 解析图片字节(rels → media part)。\n * 与 insert-image-ops.getImageBytesSync 的 rels/media 解析同源,但入口是 rId(非 drawingId),\n * 供 getTableVerbatim 直接拿格内图的原始字节。\n */\nfunction resolveImageBytesByRId(\n pkg: Package,\n hostPartPath: string,\n rId: string,\n): { bytes: Uint8Array; contentType: string } | null {\n const relsPath = relsPathFor(hostPartPath);\n const relsPart = getPart(pkg, relsPath);\n if (!relsPart || !isXmlPart(relsPart) || !relsPart.xmlDom) return null;\n const RELS_NS = 'http://schemas.openxmlformats.org/package/2006/relationships';\n const rels = relsPart.xmlDom.getElementsByTagNameNS(RELS_NS, 'Relationship');\n let mediaPath: string | null = null;\n for (let i = 0; i < rels.length; i++) {\n const r = rels[i]!;\n if (r.getAttribute('Id') === rId) {\n mediaPath = resolveRelTarget(relsPath, r.getAttribute('Target') ?? '');\n break;\n }\n }\n if (!mediaPath) return null;\n const mediaPart = getPart(pkg, mediaPath);\n if (!mediaPart) return null;\n const bytes = (mediaPart as unknown as { bytes?: Uint8Array }).bytes;\n if (!bytes) return null;\n return { bytes, contentType: mediaPart.contentType };\n}\n\n/** part 路径 → 对应 .rels 路径('word/document.xml' → 'word/_rels/document.xml.rels')。 */\nfunction relsPathFor(partPath: string): string {\n const idx = partPath.lastIndexOf('/');\n if (idx < 0) return `_rels/${partPath}.rels`;\n const dir = partPath.slice(0, idx);\n const name = partPath.slice(idx + 1);\n return `${dir}/_rels/${name}.rels`;\n}\n\n/** rels Target → 绝对 package 路径(处理 '/' 前缀、'./'、'../' 回溯)。 */\nfunction resolveRelTarget(relsPartPath: string, target: string): string {\n let t = target.trim();\n if (t.startsWith('/')) return t.slice(1);\n while (t.startsWith('./')) t = t.slice(2);\n const idx = relsPartPath.lastIndexOf('/_rels/');\n if (idx < 0) return t;\n const hostDir = relsPartPath.slice(0, idx);\n let parts = hostDir.split('/').filter(Boolean);\n while (t.startsWith('../')) {\n parts = parts.slice(0, -1);\n t = t.slice(3);\n }\n return parts.length === 0 ? t : `${parts.join('/')}/${t}`;\n}\n\n/**\n * 装载期构建 tblIdx:枚举 Package 中所有内容 part 的 <w:tbl>,\n * 按文档顺序分配 'tbl-NNNN' ID。由 buildIndices 调用。\n *\n * 内部函数,不对外导出。\n */\nexport function buildTableIndex(\n parts: Iterable<{\n contentType: string;\n xmlDom?: {\n getElementsByTagNameNS(\n ns: string,\n local: string,\n ): { length: number } & Record<number, Element>;\n };\n }>,\n): Map<string, Element> {\n const tblIdx = new Map<string, Element>();\n let n = 1;\n\n for (const part of parts) {\n const ct = part.contentType;\n // 只扫 Word 内容 part(同 buildIndices 的 isWordContentPart 逻辑)\n if (\n !ct.includes('wordprocessingml') &&\n !ct.endsWith('.main+xml') &&\n !ct.endsWith('.header+xml') &&\n !ct.endsWith('.footer+xml') &&\n !ct.endsWith('.footnotes+xml') &&\n !ct.endsWith('.endnotes+xml')\n // 不扫 comments.xml:注释内通常无表格,跳过\n ) {\n continue;\n }\n if (!part.xmlDom) continue;\n\n const tbls = part.xmlDom.getElementsByTagNameNS(W_NS, 'tbl');\n for (let i = 0; i < tbls.length; i++) {\n const tbl = tbls[i];\n if (!tbl) continue;\n const id = `tbl-${String(n).padStart(4, '0')}`;\n tblIdx.set(id, tbl);\n n++;\n }\n }\n\n return tblIdx;\n}\n","/**\n * Run 操作原语 —— 给后续 #28 replaceTextInParagraph 调用。\n *\n * 本模块只提供最小、可组合的基本操作:\n * - getParagraphRuns / getRunText / getParagraphText:只读查询\n * - splitRunAt:在 run 内 text offset 处拆一个 run 为两个(共享 <w:rPr>)\n *\n * **边界**(本期限制,后续按需扩展):\n * - splitRunAt 仅支持 \"run 下恰有一个 <w:t>\" 的简单情况\n * - 复杂结构(多个 <w:t>、含 <w:br> / <w:tab> / <w:sym> / <w:fldChar> 等)明确抛错\n * - <w:t xml:space=\"preserve\"> 的 xml:space 属性在拆分时保留\n *\n * **保真读 opt-in**(task-20260623-core-tab-faithful-read):\n * - `getRunText(run, { structuralWhitespace: true })` 按 appendSegmentsToRun 的精确逆运算还原\n * `<w:tab/>` → `\\t`、`<w:br/>` (无 type 或 textWrapping)→ `\\n`。\n * - 默认(不传 opts 或 structuralWhitespace: false)行为逐字节不变:只拼 `<w:t>`。\n * - **偏移算术调用方**(bookmark-ops / comment-ops / paragraph-ops 共 7 处)\n * **不得**传 `structuralWhitespace: true`——偏移依赖 tab 算 0 长保持稳定。\n */\nimport { W_NS } from './namespaces.js';\n\n/**\n * `getRunText` / `getParagraphText` 可选参数(task-20260623-core-tab-faithful-read)。\n *\n * - `structuralWhitespace: true`:保真读模式——按 appendSegmentsToRun 的精确逆运算,\n * `<w:tab/>` → `\\t`、`<w:br/>` (无 w:type 或 w:type=\"textWrapping\")→ `\\n`。\n * - 缺省 / `false`:现行为(只拼 `<w:t>`),**偏移算术安全**。\n */\nexport interface RunTextOptions {\n /**\n * 启用结构空白保真读:`<w:tab/>` → `\\t`,`<w:br/>` → `\\n`。\n * **仅用于内容读取**,不得用于偏移算术(bookmark/comment/replaceText 等)。\n */\n structuralWhitespace?: boolean;\n}\n\nfunction isWElement(node: Node, localName: string): boolean {\n return (\n node.nodeType === 1 &&\n (node as Element).namespaceURI === W_NS &&\n (node as Element).localName === localName\n );\n}\n\n/** 段落 `<w:p>` 下所有直接 `<w:r>` 子元素(不递归到嵌套段落 / smartTag 等) */\nexport function getParagraphRuns(paragraph: Element): Element[] {\n const runs: Element[] = [];\n const children = paragraph.childNodes;\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child && isWElement(child, 'r')) {\n runs.push(child as Element);\n }\n }\n return runs;\n}\n\n/**\n * 取 run 的文本。\n *\n * - 默认(opts 缺省 / `structuralWhitespace: false`):只拼 `<w:t>` 文本,忽略 `<w:br>` / `<w:tab>`。\n * **偏移算术安全**:`<w:tab/>` 算 0 长,保持 bookmark/comment/replaceText 偏移稳定。\n * - `structuralWhitespace: true`:保真读——按文档顺序遍历子节点:\n * `<w:t>` → textContent、`<w:tab/>` → `\\t`、`<w:br/>` (无 w:type 或 textWrapping)→ `\\n`。\n * 这是 `appendSegmentsToRun` 的精确逆运算。\n */\nexport function getRunText(run: Element, opts?: RunTextOptions): string {\n const faithful = opts?.structuralWhitespace === true;\n let text = '';\n const children = run.childNodes;\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (!child || child.nodeType !== 1) continue;\n const el = child as Element;\n if (el.namespaceURI !== W_NS) continue;\n const local = el.localName;\n if (local === 't') {\n text += el.textContent ?? '';\n } else if (faithful) {\n if (local === 'tab') {\n text += '\\t';\n } else if (local === 'br') {\n // 只还原无 type 或 type=\"textWrapping\" 的 <w:br/>(写侧产出的两种)\n // page/column break 等不在写侧逆运算内,忽略\n const type = el.getAttributeNS(W_NS, 'type');\n if (type === null || type === '' || type === 'textWrapping') {\n text += '\\n';\n }\n }\n }\n }\n return text;\n}\n\n/**\n * 段落的纯文本(所有直接 run 的文本拼接)。\n *\n * - 默认(opts 缺省):只拼 `<w:t>`,偏移算术安全。\n * - `structuralWhitespace: true`:保真读,透传到 `getRunText`。\n */\nexport function getParagraphText(paragraph: Element, opts?: RunTextOptions): string {\n return getParagraphRuns(paragraph)\n .map((r) => getRunText(r, opts))\n .join('');\n}\n\n/** splitRunAt 返回值 */\nexport interface SplitResult {\n /** 原 run,文本截取到 [0, offset) */\n before: Element;\n /**\n * 分裂出的新 run,文本为 [offset, end)。\n * 与 `before` 共享 `<w:rPr>` 结构(深拷贝,不共享对象引用)。\n * offset=0 或 offset=runLen 时为 null(未发生分裂)\n */\n after: Element | null;\n}\n\n/**\n * 在 run 的文本 offset 处拆分。\n *\n * - `offset === 0` 或 `offset === getRunText(run).length` → 不拆,返回 { before: run, after: null }\n * - 其它情况 → 新 run 插入到原 run 的 nextSibling 位置,返回 { before: run, after: newRun }\n *\n * 本期限制:run 必须恰有 1 个 `<w:t>` 子;否则抛错(多 <w:t>、<w:br>、<w:tab> 等复杂结构交给\n * 后续 #28 的扩展实现)。\n */\nexport function splitRunAt(run: Element, offset: number): SplitResult {\n if (!isWElement(run, 'r')) {\n throw new Error('splitRunAt: argument is not a <w:r> element');\n }\n const tElements: Element[] = [];\n const children = run.childNodes;\n for (let i = 0; i < children.length; i++) {\n const c = children[i];\n if (c && isWElement(c, 't')) tElements.push(c as Element);\n }\n if (tElements.length === 0) {\n throw new Error(\n 'splitRunAt: run has no <w:t> children; this primitive supports single-<w:t> runs only',\n );\n }\n if (tElements.length > 1) {\n throw new Error(\n `splitRunAt: run has ${tElements.length} <w:t> children; this primitive supports single-<w:t> runs only`,\n );\n }\n\n const originalT = tElements[0]!;\n const runText = originalT.textContent ?? '';\n if (offset < 0 || offset > runText.length) {\n throw new Error(`splitRunAt: offset ${offset} out of range [0, ${runText.length}]`);\n }\n if (offset === 0 || offset === runText.length) {\n return { before: run, after: null };\n }\n\n const leftText = runText.slice(0, offset);\n const rightText = runText.slice(offset);\n\n // 截取原 run 的文本到左半\n originalT.textContent = leftText;\n\n // 克隆整个 run 作为 after;克隆深拷贝会把 <w:rPr> 和 <w:t xml:space> 一起复制\n const afterRun = run.cloneNode(true) as Element;\n // 找到 after run 中的那个 <w:t>,写入右半文本\n const afterChildren = afterRun.childNodes;\n for (let i = 0; i < afterChildren.length; i++) {\n const c = afterChildren[i];\n if (c && isWElement(c, 't')) {\n (c as Element).textContent = rightText;\n break;\n }\n }\n\n // 插入 afterRun 到原 run 之后\n const parent = run.parentNode;\n if (!parent) {\n throw new Error('splitRunAt: run has no parent; cannot insert new run');\n }\n parent.insertBefore(afterRun, run.nextSibling);\n\n return { before: run, after: afterRun };\n}\n","/**\n * Word 跨 stories 枚举(docs/03 §10)。\n *\n * 一个 Word 文档可能包含 6 类 story:\n * | 类型 | contentType 尾缀 | 根元素 | 数量 |\n * | body | .document.main+xml | <w:document>/<w:body> | 1 |\n * | header | .header+xml | <w:hdr> | 0..N |\n * | footer | .footer+xml | <w:ftr> | 0..N |\n * | footnote | .footnotes+xml 内每个 | 单个 <w:footnote>(该 part 多个) | 0..M |\n * | endnote | .endnotes+xml 内每个 | 单个 <w:endnote> | 0..M |\n * | comment | .comments+xml 内每个 | 单个 <w:comment> | 0..M |\n *\n * **关键约束**:枚举实现**必须**走 `Document#getElementsByTagNameNS`。\n * `fontoxpath` 的 `//w:p` 无谓词 XPath 在 10 万段落上慢 460×(见 bench/xml-stack-worker)。\n * CI 的 `pnpm check:xpath` 会在源码里扫到此类调用即失败(scripts/check-no-unqualified-xpath.mjs)。\n */\nimport type { Package } from '../pack/index.js';\nimport { isXmlPart } from '../pack/index.js';\nimport { W_NS } from './namespaces.js';\n\nexport type StoryKind = 'body' | 'header' | 'footer' | 'footnote' | 'endnote' | 'comment';\n\n/**\n * Story 唯一标识:\n * - 'body' — 唯一主 body\n * - 'header:<part path>' / 'footer:<part path>' — 1 part 1 story\n * - 'footnote:<id>' / 'endnote:<id>' / 'comment:<id>' — 多 sub-story 按 w:id 区分\n */\nexport type StoryId =\n | 'body'\n | `header:${string}`\n | `footer:${string}`\n | `footnote:${number}`\n | `endnote:${number}`\n | `comment:${number}`;\n\nexport interface Story {\n id: StoryId;\n kind: StoryKind;\n /** story 的根 Element(body = <w:body>,footnote = 单个 <w:footnote>,依此类推) */\n root: Element;\n /** 所在 part 的 path */\n partPath: string;\n}\n\nexport interface ListStoriesOptions {\n /** 仅枚举指定 kinds(默认全枚举)*/\n kinds?: StoryKind[];\n}\n\n/** 把 HTMLCollection-ish 枚举结果快照成数组 */\nfunction snapshot<T extends Element>(col: { length: number } & Record<number, T>): T[] {\n const out: T[] = [];\n for (let i = 0; i < col.length; i++) {\n const el = col[i];\n if (el) out.push(el);\n }\n return out;\n}\n\n/**\n * 枚举 Package 内所有 stories。\n *\n * 按 contentType 尾缀识别 part,内部用 `getElementsByTagNameNS` 提取根元素或\n * sub-story 元素。**不做**任何带谓词/路径的 XPath。\n */\nexport function listStories(pkg: Package, opts: ListStoriesOptions = {}): Story[] {\n const wanted = new Set<StoryKind>(\n opts.kinds ?? ['body', 'header', 'footer', 'footnote', 'endnote', 'comment'],\n );\n const out: Story[] = [];\n\n for (const part of pkg.parts.values()) {\n if (!isXmlPart(part) || !part.xmlDom) continue;\n const ct = part.contentType;\n\n if (\n wanted.has('body') &&\n (ct.endsWith('.document.main+xml') ||\n ct.endsWith('.document.macroEnabled.main+xml') ||\n ct.endsWith('.template.main+xml') ||\n ct.endsWith('.template.macroEnabledTemplate.main+xml'))\n ) {\n // body 是 <w:document>/<w:body> 的唯一子元素\n const bodies = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'body'));\n if (bodies.length > 0) {\n out.push({ id: 'body', kind: 'body', root: bodies[0]!, partPath: part.path });\n }\n continue;\n }\n\n if (wanted.has('header') && ct.endsWith('.header+xml')) {\n const hdrs = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'hdr'));\n if (hdrs.length > 0) {\n out.push({\n id: `header:${part.path}`,\n kind: 'header',\n root: hdrs[0]!,\n partPath: part.path,\n });\n }\n continue;\n }\n\n if (wanted.has('footer') && ct.endsWith('.footer+xml')) {\n const ftrs = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'ftr'));\n if (ftrs.length > 0) {\n out.push({\n id: `footer:${part.path}`,\n kind: 'footer',\n root: ftrs[0]!,\n partPath: part.path,\n });\n }\n continue;\n }\n\n if (wanted.has('footnote') && ct.endsWith('.footnotes+xml')) {\n const notes = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'footnote'));\n for (const note of notes) {\n const id = numAttr(note, W_NS, 'id');\n if (id === null) continue;\n out.push({\n id: `footnote:${id}`,\n kind: 'footnote',\n root: note,\n partPath: part.path,\n });\n }\n continue;\n }\n\n if (wanted.has('endnote') && ct.endsWith('.endnotes+xml')) {\n const notes = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'endnote'));\n for (const note of notes) {\n const id = numAttr(note, W_NS, 'id');\n if (id === null) continue;\n out.push({\n id: `endnote:${id}`,\n kind: 'endnote',\n root: note,\n partPath: part.path,\n });\n }\n continue;\n }\n\n if (wanted.has('comment') && ct.endsWith('.comments+xml')) {\n const comments = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'comment'));\n for (const c of comments) {\n const id = numAttr(c, W_NS, 'id');\n if (id === null) continue;\n out.push({\n id: `comment:${id}`,\n kind: 'comment',\n root: c,\n partPath: part.path,\n });\n }\n }\n }\n\n return out;\n}\n\n/**\n * 取 story 根下的所有 <w:p> 元素。\n *\n * - body / header / footer:story root 整个子树的 w:p\n * - footnote / endnote / comment:单个 sub-story root 下的 w:p(不跨 sub-story)\n */\nexport function getStoryParagraphs(story: Story): Element[] {\n // 使用 getElementsByTagNameNS 枚举 —— 根元素调用时只返回该子树下的匹配\n return snapshot(story.root.getElementsByTagNameNS(W_NS, 'p'));\n}\n\n/**\n * 反查元素所属 story(#25)。用 `Node.contains()` 逐个比对,命中首个返回。\n *\n * 一个 pkg 内 stories 数量通常 <20,contains 是 ~O(depth) 的祖先遍历,整体代价\n * 可忽略。找不到(元素不在任何已识别 story 的子树里)返回 null 。\n */\nexport function findStoryFor(pkg: Package, el: Element): Story | null {\n for (const s of listStories(pkg)) {\n if (s.root.contains(el as unknown as Node)) return s;\n }\n return null;\n}\n\n/**\n * scope 枚举解析为 Story 列表(#24 / #26 共用)。\n *\n * - `'body'` —— 只枚举 body\n * - `'all'` —— 全部 6 类 story\n * - `StoryId[]` —— 精确筛选;**未命中的 id 忽略**(调用方自己负责正确性)\n */\nexport function resolveScope(pkg: Package, scope: 'body' | 'all' | StoryId[]): Story[] {\n if (scope === 'body') return listStories(pkg, { kinds: ['body'] });\n if (scope === 'all') return listStories(pkg);\n const wanted = new Set<StoryId>(scope);\n return listStories(pkg).filter((s) => wanted.has(s.id));\n}\n\nfunction numAttr(el: Element, ns: string, local: string): number | null {\n const v = el.getAttributeNS(ns, local);\n if (v === null) return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n}\n","/**\n * Drawing 对象枚举(#189)\n *\n * 为骨架化 M3 阶段(图片分类/Shape 分类/占位图替换)提供 drawing 数据。\n *\n * 边界(实施前澄清,见 #189 评论):\n * - **本模块只做\"枚举 + 形态信号\"**,不下 A / B / C 业务分类结论\n * - C 类细分(水印 / 装饰线 / 背景图)需语义判断,属于 #191\n * - 分组键计算、代表样本选取属于 #191 / #192\n * - 占位图替换属于 #190\n *\n * 设计原则(对齐 #185 list-tables.ts):\n * - 只走 getElementsByTagNameNS / 直接子节点遍历,禁用 XPath(性能约束)\n * - drawingId 由 buildIndices 装载期分配('dr-0001' 起),存入 drawingIdx\n * - 默认枚举主文档 body(scope: 'body')\n * - 提供 isInHeaderFooter 字段,让 #191 直接识别 C 类页眉页脚 logo(无需自己查 part 路径)\n */\n\nimport type { Package } from '../pack/index.js';\nimport {\n W_NS,\n WP_NS,\n A_NS,\n PIC_NS,\n CHART_NS,\n DGM_NS,\n WPS_NS,\n WPG_NS,\n WPC_NS,\n W14_NS,\n MC_NS,\n VML_NS,\n} from './namespaces.js';\nimport type { PackageIndices } from './indices.js';\nimport { getRunText, type RunTextOptions } from './runs.js';\nimport { resolveScope, type Story } from './stories.js';\n\n// ─────────── 公开类型 ───────────\n\n/** 锚定方式:inline(行内嵌入)/ anchor(浮动定位)*/\nexport type DrawingAnchoringMode = 'inline' | 'anchor';\n\n/**\n * Drawing 内容形态(**OOXML 子树类型**,非 A/B/C 业务分类)。\n *\n * 识别优先级:a:graphicData @uri → 命中预设 URI 之一时按表分发;都不命中时落 'other'。\n * 'textBox' 是 'shape' 的特化(wps:wsp 内含 w:txbxContent),单独抽出便于 #192 处理。\n *\n * | kind | URI 或子元素信号 | 典型场景 |\n * |----------|---------------------------------------------------------------------------|----------|\n * | picture | uri = .../picture | 嵌入/浮动图片 |\n * | chart | uri = .../chart | Word 原生图表 |\n * | smartArt | uri = .../diagram | SmartArt(1.0/2010+)|\n * | shape | uri = .../wordprocessingShape,且 **无** w:txbxContent | 形状(无文字) |\n * | textBox | uri = .../wordprocessingShape,且 **有** w:txbxContent | 文本框 / 标注 |\n * | group | uri = .../wordprocessingGroup | 形状组合 |\n * | canvas | uri = .../wordprocessingCanvas | 绘图画布 |\n * | other | 都不命中 | 未来扩展占位 |\n */\nexport type DrawingKind =\n | 'picture'\n | 'chart'\n | 'smartArt'\n | 'shape'\n | 'textBox'\n | 'group'\n | 'canvas'\n | 'other';\n\nexport type TextBoxBlockInfo =\n | { type: 'paragraph'; paraId: string; text: string }\n | { type: 'drawing'; drawingId: string; paraId?: string }\n | { type: 'table'; tableId: string };\n\n/**\n * 文字环绕方式(仅 anchor 有)。\n *\n * - none:wp:wrapNone(不环绕;配合 behindDoc 形成\"衬于文字下方/上方\")\n * - square:wp:wrapSquare(四周型 / 矩形环绕)\n * - tight:wp:wrapTight(紧密型)\n * - through:wp:wrapThrough(穿越型)\n * - topAndBottom:wp:wrapTopAndBottom(上下型)\n * - behind:wp:wrapNone + behindDoc=\"1\"(衬于文字下方)\n * - inFront:wp:wrapNone + behindDoc=\"0\"(浮于文字上方)\n */\nexport type DrawingWrapType =\n | 'none'\n | 'square'\n | 'tight'\n | 'through'\n | 'topAndBottom'\n | 'behind'\n | 'inFront';\n\n/** 浮动定位的单方向描述(原值透传,由 #191 自行二次解释)*/\nexport interface DrawingPositionAxis {\n /** wp:positionH/V @relativeFrom 原值(如 'page' / 'margin' / 'column' / 'character' / 'paragraph' / 'line')*/\n relativeFrom?: string;\n /** wp:posOffset 文本内容(EMU 整数)。和 align 二选一。*/\n posOffset?: number;\n /** wp:align 文本内容('left' / 'center' / 'right' / 'top' / 'bottom' / 'inside' / 'outside')。和 posOffset 二选一。*/\n align?: string;\n}\n\n/** 浮动位置(仅 anchor 有)*/\nexport interface DrawingPosition {\n horizontal?: DrawingPositionAxis;\n vertical?: DrawingPositionAxis;\n}\n\nexport interface DrawingInfo {\n /** 装载期分配的 ID('dr-NNNN',从 0001 起),供 #190 / #191 / #192 引用 */\n drawingId: string;\n /** 所在 part 路径('word/document.xml' / 'word/header1.xml' 等)*/\n partPath: string;\n /** 枚举结果顺序(1-based,与文档顺序一致)*/\n index: number;\n /** 锚定方式:inline 行内嵌入 / anchor 浮动定位 */\n anchoringMode: DrawingAnchoringMode;\n /** 内容形态(OOXML 层级,非业务分类)*/\n kind: DrawingKind;\n /**\n * 显示尺寸(EMU;1 inch = 914400 EMU)。\n * 来源:wp:extent @cx @cy。\n * 未声明或解析失败时为 { cx: 0, cy: 0 }(保持字段必存在,避免下游需要判 undefined)。\n */\n extentEMU: { cx: number; cy: number };\n /** 文字环绕方式(仅 anchor,inline 不含此字段)*/\n wrap?: DrawingWrapType;\n /** 浮动位置(仅 anchor,inline 不含此字段)*/\n position?: DrawingPosition;\n /**\n * 承载此 drawing 的段落 paraId(w:p @w14:paraId)。\n *\n * 多数情况下,drawing 挂在 w:r 下、该 w:r 属于某个 w:p,本字段就是这个 w:p 的 paraId。\n *\n * **关键例外(CR 设计说明)**:嵌套在 **TextBox 内**(即位于 `w:txbxContent` 子树)\n * 的 drawing,本字段为 `undefined`。这是**主动设计**:textBox 内段落与外层段落是两套\n * 独立的段落流,把外层段落 paraId 挂给文本框内 drawing 会让\"删段落 → 顺带删图\"等\n * 操作误伤外层段落。下游需要文本框内图片的段落引用时,应:\n * 1. 用 `runIndex === undefined && anchorParaId === undefined` 识别该情形\n * 2. 自行向上找最近的 `w:txbxContent`,再用其外层 anchor 段落定位\n *\n * 缺 paraId 时也为 undefined(合法但少见;由 ensureParaIds 在装载期补齐,但本字段\n * 不依赖这一保证)。\n */\n anchorParaId?: string;\n /**\n * 在所在段落里的 run 序号(0-based,承载 drawing 的 w:r 在父 w:p 直接子 w:r 中的位置)。\n * 同段多 drawing 时用于区分;用于题注绑定(#193)/ 占位图替换(#190)的精确引用。\n */\n runIndex?: number;\n /**\n * 是否位于页眉页脚 part(contentType 以 .header+xml / .footer+xml 结尾)。\n *\n * 这是**形态信号**,不是 C 类结论。#191 在做 A/B/C 业务分类时:\n * - true + kind = picture → 大概率 C 类页眉页脚 logo,由 .dot 原生保留\n * - 其余情况由 #191 自行判断\n */\n isInHeaderFooter: boolean;\n /**\n * 形态信号:是否含 pic:pic 子树(A 类正向信号)。\n * - kind = 'picture' 时必为 true\n * - 理论上 chart/shape 也可能套 pic:pic,此时 kind 不变但本字段为 true\n */\n hasPicReference: boolean;\n /**\n * Shape 专属:是否为「线条 / 连接线(line / connector)」几何。仅 kind = 'shape' 时可能为 true,\n * 其余 kind(picture/chart/.../textBox/group/canvas/other)一律 undefined。\n *\n * **形态信号,非业务结论**。下游(skeleton #192 shape-role)用它把版记横线一类「纯几何线条」\n * 与矩形/椭圆等装饰噪声区分开:线条原样保留,其余几何仍按现状裁掉。\n *\n * 识别(命中任一即 true,从强到弱):\n * 1. DrawingML 预设几何:`<a:prstGeom prst=\"...\">` 的 prst 命中线条/连接线预设\n * (line / straightConnector1 / bentConnectorN / curvedConnectorN,N=2..5;见 LINE_CONNECTOR_PRESETS)\n * 2. 连接线非可视属性:wps:wsp 内含 `<wps:cNvCnPr>`(connector non-visual props;\n * 普通 shape 用 wps:cNvSpPr,连接线用 wps:cNvCnPr)\n * 3. VML 回退:子树含 `<v:line>`(mc:Fallback 内的旧版降级线条)\n *\n * 母版实测(生成的公文__会议纪要.docx):版记 3 根横线均 prst=\"line\" + wps:cNvCnPr + v:line 三信号齐全;\n * 红头艺术字文本框是 prst=\"rect\" + wps:cNvSpPr,不命中。\n */\n isLineOrConnector?: boolean;\n /**\n * Shape / TextBox 专属:是否有边框(a:spPr/a:ln 存在且未声明 a:noFill)。\n * 非 shape/textBox 一律 undefined。\n */\n hasBorder?: boolean;\n /**\n * Shape / TextBox 专属:是否有填充(a:spPr 下含 a:solidFill / a:gradFill / a:blipFill / a:pattFill)。\n * 显式 a:noFill 视作无填充。非 shape/textBox 一律 undefined。\n */\n hasFill?: boolean;\n /**\n * TextBox 专属:是否含实质内部文字(w:txbxContent 内 w:t 文本拼接后非空)。\n * 非 textBox 一律 undefined。\n */\n hasInnerText?: boolean;\n /**\n * TextBox 专属:内部文字原文(w:txbxContent 内全部 w:t 文本按文档顺序拼接后 trim)。\n *\n * 供下游(skeleton #192 / typeset 投影)读取红头艺术字等内容型文本框的原文做语义命名/字段化\n * (如「江临市滨河区人民政府」「专题会议纪要」)。\n *\n * 语义:\n * - 仅 kind = 'textBox' 时可能有值;非 textBox 一律 undefined。\n * - trim 后为空(无文字 / 仅空白)→ undefined(与 hasInnerText=false 对应)。\n * - 多段 / 多 run 文本框按 w:t 出现顺序直接拼接,**不插入换行/空格**(保持与 hasInnerText\n * 同源的拼接口径);下游需要分段语义时应自行用 paragraphs API 处理,本字段只给原文快照。\n */\n innerText?: string;\n /**\n * TextBox 专属:文本框内容流里的段落 paraId,按文档顺序排列。\n *\n * 这些 paraId 指向 `w:txbxContent` 内部段落。调用方可通过\n * `doc.paragraphs.get(paraId)` 或 `TextBoxDrawing.getParagraphs()` 继续进行段落/文字级操作。\n * 非 textBox 一律 undefined。\n */\n innerParagraphIds?: string[];\n /**\n * TextBox 专属:文本框内容流的直接内容块,按 OOXML child-node 顺序排列。\n *\n * 目前包含 direct paragraph、paragraph 内 direct drawing,以及 direct table。\n * 非 textBox 一律 undefined。\n */\n innerBlocks?: TextBoxBlockInfo[];\n /**\n * TextBox 专属:首个内部段落的对齐(w:txbxContent 第一段 w:pPr/w:jc @val)。\n *\n * 注:文本框内可能多段且对齐不同,#192 需自行决定如何取代表值;本字段只提供首段值便于快速分组。\n * 非 textBox 或无 jc 设置一律 undefined。\n */\n innerTextAlign?: string;\n}\n\nexport interface ListDrawingsOptions {\n /**\n * 枚举范围(默认 'body')。\n * - 'body':仅主文档 body(不含页眉页脚 / 注释 / 脚注尾注)\n * - 'all':所有 stories(body + headers + footers + footnotes + endnotes + comments)\n *\n * 注意:#189 的 isInHeaderFooter 字段只在 'all' 模式下才可能为 true;\n * 'body' 模式下页眉页脚 drawing 不会被枚出。\n */\n scope?: 'body' | 'all';\n /**\n * 启用结构空白保真读(task-20260623-core-tab-faithful-read)。\n * `true` 时 TextBox 的 `innerText` 含 `\\t`(制表符)和 `\\n`(软换行)——\n * 是 `appendSegmentsToRun` 的精确逆运算。\n * 默认 `false`:只拼 `<w:t>`(与历史行为一致)。\n */\n structuralWhitespace?: boolean;\n}\n\n// ─────────── 内部工具 ───────────\n\n/** 把 HTMLCollection-ish 枚举结果快照成数组(避免 live 行为)*/\nfunction snapshot<T extends Element>(col: { length: number } & Record<number, T>): T[] {\n const out: T[] = [];\n for (let i = 0; i < col.length; i++) {\n const el = col[i];\n if (el) out.push(el);\n }\n return out;\n}\n\n/**\n * OOXML on/off 属性解析。\n *\n * 同一布尔语义在 OOXML 文档中可序列化为多种字面形态(ECMA-376 §17.18.93 ST_OnOff):\n * - true: \"1\" / \"true\" / \"on\"\n * - false: \"0\" / \"false\" / \"off\"\n *\n * 缺值(null)/ 未识别字面 → undefined(区分\"未声明\"与\"显式 false\",由调用方决策默认值)。\n *\n * 触发场景:`<wp:anchor behindDoc=\"true\">`(部分模板/第三方工具用 true 而非 1)\n * —— 旧实现只匹配 \"1\" / \"0\",会把 behindDoc=\"true\" 误判为 inFront。\n */\nfunction parseOnOff(value: string | null): boolean | undefined {\n if (value === null) return undefined;\n const v = value.trim().toLowerCase();\n if (v === '1' || v === 'true' || v === 'on') return true;\n if (v === '0' || v === 'false' || v === 'off') return false;\n return undefined;\n}\n\n/**\n * 判断 element 是否嵌套在 mc:AlternateContent / mc:Fallback 之中。\n *\n * 触发场景:第三方工具或某些 Word 旧版会把 SmartArt / Chart 的\"降级 fallback\"\n * 也用 `<w:drawing>` 包装放进 `<mc:Fallback>`,再被 mc:Choice 里的真版本\"覆盖\"。\n * 此时同一对象会被深遍历枚两次(Choice + Fallback),导致 drawingIdx 双计。\n *\n * 防御方式:装载期主动跳过 Fallback 内部的 drawing —— mc:Choice 已经是规范的\n * \"首选呈现\",Fallback 只在 reader 不识别 Choice 时使用,不应进枚举结果。\n *\n * 实测:Word / WPS 生成的真实文档 Fallback 用 VML `<w:pict>`,不命中本守卫;\n * 加上这一层只为防御第三方工具。\n */\nfunction hasFallbackAncestor(el: Element): boolean {\n let p = el.parentElement;\n while (p) {\n if (p.namespaceURI === MC_NS && p.localName === 'Fallback') return true;\n p = p.parentElement;\n }\n return false;\n}\n\n/** 取 element 直接子中首个匹配 (ns, local) 的,未找到返回 null */\nfunction firstDirectChild(parent: Element, ns: string, local: string): Element | null {\n for (let i = 0; i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i] as Element;\n if (\n child &&\n child.nodeType === 1 /* ELEMENT_NODE */ &&\n child.namespaceURI === ns &&\n child.localName === local\n ) {\n return child;\n }\n }\n return null;\n}\n\n/** 取 element 子孙中首个匹配 (ns, local) 的,未找到返回 null */\nfunction firstDescendant(parent: Element, ns: string, local: string): Element | null {\n const col = parent.getElementsByTagNameNS(ns, local);\n return col.length > 0 ? col[0]! : null;\n}\n\n/** 解析 number-like attribute;缺/非数为 undefined */\nfunction readNumAttr(el: Element, ns: string | null, local: string): number | undefined {\n const v = ns === null ? el.getAttribute(local) : el.getAttributeNS(ns, local);\n if (v === null) return undefined;\n const n = Number(v);\n return Number.isFinite(n) ? n : undefined;\n}\n\n/** 取元素直接文本(不递归)*/\nfunction textContentOfDirect(el: Element): string {\n let s = '';\n for (let i = 0; i < el.childNodes.length; i++) {\n const child = el.childNodes[i] as Node & { nodeValue?: string };\n if (child.nodeType === 3 /* TEXT_NODE */) {\n s += child.nodeValue ?? '';\n }\n }\n return s;\n}\n\n/** 取 wp:positionH / wp:positionV 元素的 axis 描述 */\nfunction readPositionAxis(axisEl: Element): DrawingPositionAxis {\n const out: DrawingPositionAxis = {};\n const relativeFrom = axisEl.getAttribute('relativeFrom');\n if (relativeFrom !== null) out.relativeFrom = relativeFrom;\n\n const posOffsetEl = firstDirectChild(axisEl, WP_NS, 'posOffset');\n if (posOffsetEl) {\n const txt = textContentOfDirect(posOffsetEl).trim();\n const n = Number(txt);\n if (Number.isFinite(n)) out.posOffset = n;\n } else {\n const alignEl = firstDirectChild(axisEl, WP_NS, 'align');\n if (alignEl) {\n const txt = textContentOfDirect(alignEl).trim();\n if (txt) out.align = txt;\n }\n }\n return out;\n}\n\n/** 从 wp:anchor 解析 wrap 类型 */\nfunction readWrap(anchor: Element): DrawingWrapType | undefined {\n // 优先 topAndBottom / square / tight / through\n if (firstDirectChild(anchor, WP_NS, 'wrapTopAndBottom')) return 'topAndBottom';\n if (firstDirectChild(anchor, WP_NS, 'wrapSquare')) return 'square';\n if (firstDirectChild(anchor, WP_NS, 'wrapTight')) return 'tight';\n if (firstDirectChild(anchor, WP_NS, 'wrapThrough')) return 'through';\n if (firstDirectChild(anchor, WP_NS, 'wrapNone')) {\n // wp:wrapNone 自身存在;behindDoc 在 anchor 元素属性上\n // ECMA-376 ST_OnOff:支持 \"1\"/\"true\"/\"on\" 与 \"0\"/\"false\"/\"off\" 多种字面(见 parseOnOff)\n const behind = parseOnOff(anchor.getAttribute('behindDoc'));\n if (behind === true) return 'behind';\n if (behind === false) return 'inFront';\n return 'none';\n }\n return undefined;\n}\n\n/** 从 wp:inline / wp:anchor 解析 extent */\nfunction readExtent(container: Element): { cx: number; cy: number } {\n const ext = firstDirectChild(container, WP_NS, 'extent');\n if (!ext) return { cx: 0, cy: 0 };\n const cx = readNumAttr(ext, null, 'cx') ?? 0;\n const cy = readNumAttr(ext, null, 'cy') ?? 0;\n return { cx, cy };\n}\n\n/** 从 wp:anchor 解析 position */\nfunction readPosition(anchor: Element): DrawingPosition {\n const out: DrawingPosition = {};\n const h = firstDirectChild(anchor, WP_NS, 'positionH');\n if (h) out.horizontal = readPositionAxis(h);\n const v = firstDirectChild(anchor, WP_NS, 'positionV');\n if (v) out.vertical = readPositionAxis(v);\n return out;\n}\n\n/**\n * 从 wp:inline / wp:anchor 子树识别 kind。\n *\n * 一级判定:a:graphicData @uri;命中 6 个预设 URI 之一即落对应 kind。\n * 二级判定:shape kind 内若有 w:txbxContent → 重定为 textBox。\n * 都不命中 → 'other'。\n */\nfunction detectKind(container: Element): DrawingKind {\n const graphic = firstDescendant(container, A_NS, 'graphic');\n if (!graphic) return 'other';\n const gd = firstDescendant(graphic, A_NS, 'graphicData');\n if (!gd) return 'other';\n const uri = gd.getAttribute('uri') ?? '';\n\n if (uri === PIC_NS) return 'picture';\n if (uri === CHART_NS) return 'chart';\n if (uri === DGM_NS) return 'smartArt';\n if (uri === WPG_NS) return 'group';\n if (uri === WPC_NS) return 'canvas';\n if (uri === WPS_NS) {\n // 区分 shape vs textBox:wps:wsp 子树里若有 w:txbxContent,认定为 textBox\n if (gd.getElementsByTagNameNS(W_NS, 'txbxContent').length > 0) return 'textBox';\n return 'shape';\n }\n return 'other';\n}\n\n/** 检测整个 drawing 子树是否含 pic:pic(A 类正向信号;包括套在其他对象内的情况)*/\nfunction hasPicReferenceIn(container: Element): boolean {\n return container.getElementsByTagNameNS(PIC_NS, 'pic').length > 0;\n}\n\n/**\n * 线条 / 连接线的 DrawingML 预设几何(a:prstGeom @prst)白名单。\n *\n * - `line`:经典直线(Word「插入→形状→直线」生成,母版版记横线即此)\n * - `straightConnector1`:直线连接符\n * - `bentConnector2..5` / `curvedConnector2..5`:折线 / 曲线连接符(流程图连线)\n *\n * 仅收\"视觉上是一根线/连线\"的预设;矩形 rect / 椭圆 ellipse / 标注 callout 等不在内。\n * 收紧白名单而非黑名单:未知新预设默认按\"非线条\"处理,保守不误保。\n */\nconst LINE_CONNECTOR_PRESETS = new Set<string>([\n 'line',\n 'straightConnector1',\n 'bentConnector2',\n 'bentConnector3',\n 'bentConnector4',\n 'bentConnector5',\n 'curvedConnector2',\n 'curvedConnector3',\n 'curvedConnector4',\n 'curvedConnector5',\n]);\n\n/**\n * 是否为「线条 / 连接线」几何(只对 kind = 'shape' 有意义)。\n *\n * 命中任一即 true(从强到弱):\n * 1. a:prstGeom @prst ∈ LINE_CONNECTOR_PRESETS\n * 2. 含 wps:cNvCnPr(连接线非可视属性;普通 shape 是 wps:cNvSpPr)\n * 3. 含 VML v:line(旧版降级线条;防御性兜底,通常在 mc:Fallback 内,\n * 而本函数收到的 container 是 mc:Choice 内的 wp:inline/anchor,故此项多数情况不命中,\n * 仅覆盖 container 内嵌 VML 的边角情形)\n */\nfunction isLineOrConnectorShape(container: Element): boolean {\n // 信号 1:a:prstGeom @prst 命中线条/连接线预设\n const prstGeoms = container.getElementsByTagNameNS(A_NS, 'prstGeom');\n for (let i = 0; i < prstGeoms.length; i++) {\n const prst = prstGeoms[i]?.getAttribute('prst');\n if (prst && LINE_CONNECTOR_PRESETS.has(prst)) return true;\n }\n // 信号 2:wps:cNvCnPr(connector non-visual props)\n if (container.getElementsByTagNameNS(WPS_NS, 'cNvCnPr').length > 0) return true;\n // 信号 3:VML v:line(防御性兜底)\n if (container.getElementsByTagNameNS(VML_NS, 'line').length > 0) return true;\n return false;\n}\n\n/**\n * Shape / TextBox 形态信号:边框 / 填充 / 内部文字 / 首段对齐。\n *\n * 只在 kind = shape | textBox 时调用。返回顺序与 DrawingInfo 字段一致:\n * { hasBorder, hasFill, hasInnerText?, innerText?, innerParagraphIds?, innerTextAlign? }\n *\n * @param runTextOpts 传 `{ structuralWhitespace: true }` 时 innerText 保真读(含 \\t / \\n)\n */\nfunction readShapeSignals(\n container: Element,\n isTextBox: boolean,\n runTextOpts?: RunTextOptions,\n drawingToId: Map<Element, string> = new Map(),\n tableToId: Map<Element, string> = new Map(),\n): {\n hasBorder: boolean;\n hasFill: boolean;\n hasInnerText?: boolean;\n innerText?: string;\n innerParagraphIds?: string[];\n innerBlocks?: TextBoxBlockInfo[];\n innerTextAlign?: string;\n} {\n // wps:spPr 是 wps namespace 下的 spPr(与 a:spPr 不同的命名空间,但元素结构一致)\n // 也可能在 a:spPr 下 —— 实际生成的 docx 主要走 wps:spPr,我们两个 NS 都查\n const wspSpPr = firstDescendant(container, WPS_NS, 'spPr');\n const aSpPr = firstDescendant(container, A_NS, 'spPr');\n const spPr = wspSpPr ?? aSpPr;\n\n let hasBorder = false;\n let hasFill = false;\n if (spPr) {\n // 边框:a:ln 存在 且 未声明 a:noFill 子元素 且 w 属性 != \"0\"\n //\n // 三个否定条件:\n // 1. a:ln 不存在 → 无线条定义 → hasBorder = false\n // 2. <a:ln><a:noFill/></a:ln> → 显式声明无填充 → hasBorder = false\n // 3. <a:ln w=\"0\"/> → 零 EMU 宽度 → 视觉无边框 → hasBorder = false\n const ln = firstDirectChild(spPr, A_NS, 'ln');\n if (ln) {\n const noLineFill = firstDirectChild(ln, A_NS, 'noFill');\n // w 是 a:ln 的属性(非命名空间属性),可能为 \"0\" 表示零宽线\n const widthAttr = ln.getAttribute('w');\n const isZeroWidth = widthAttr !== null && widthAttr.trim() === '0';\n hasBorder = !noLineFill && !isZeroWidth;\n }\n // 填充:a:solidFill / a:gradFill / a:blipFill / a:pattFill 之一存在,且不是 noFill\n const noFill = firstDirectChild(spPr, A_NS, 'noFill');\n if (!noFill) {\n hasFill =\n !!firstDirectChild(spPr, A_NS, 'solidFill') ||\n !!firstDirectChild(spPr, A_NS, 'gradFill') ||\n !!firstDirectChild(spPr, A_NS, 'blipFill') ||\n !!firstDirectChild(spPr, A_NS, 'pattFill');\n }\n }\n\n const out: {\n hasBorder: boolean;\n hasFill: boolean;\n hasInnerText?: boolean;\n innerText?: string;\n innerParagraphIds?: string[];\n innerBlocks?: TextBoxBlockInfo[];\n innerTextAlign?: string;\n } = { hasBorder, hasFill };\n\n if (isTextBox) {\n const txbx = firstDescendant(container, W_NS, 'txbxContent');\n if (txbx) {\n const paragraphs = snapshot(txbx.getElementsByTagNameNS(W_NS, 'p'));\n const ids: string[] = [];\n for (const p of paragraphs) {\n const id = p.getAttributeNS(W14_NS, 'paraId');\n if (id) ids.push(id.toUpperCase());\n }\n out.innerParagraphIds = ids;\n out.innerBlocks = collectTextBoxBlocks(txbx, drawingToId, tableToId, runTextOpts);\n\n // 内部文字:按 runTextOpts 决定拼接口径:\n // - 默认(无 opts):所有 w:t 节点文本按文档顺序拼接(与历史行为一致,hasInnerText 同源)\n // - structuralWhitespace: true:按 run 子节点顺序走保真读(\\t/\\n 还原)\n let joined = '';\n if (runTextOpts?.structuralWhitespace) {\n // 保真读:枚举 txbxContent 内所有直属段落的直属 run,逐个 getRunText 保真\n const runs = snapshot(txbx.getElementsByTagNameNS(W_NS, 'r'));\n for (const r of runs) {\n joined += getRunText(r, runTextOpts);\n }\n } else {\n // 默认:只拼 w:t(历史行为,hasInnerText 计算依据不变)\n const tEls = snapshot(txbx.getElementsByTagNameNS(W_NS, 't'));\n for (const t of tEls) {\n joined += textContentOfDirect(t);\n }\n }\n out.hasInnerText = joined.length > 0;\n const trimmed = joined.trim();\n if (trimmed.length > 0) out.innerText = trimmed;\n\n // 首段对齐:第一个 w:p 的 w:pPr/w:jc @val\n const firstP = firstDescendant(txbx, W_NS, 'p');\n if (firstP) {\n const pPr = firstDirectChild(firstP, W_NS, 'pPr');\n if (pPr) {\n const jc = firstDirectChild(pPr, W_NS, 'jc');\n if (jc) {\n const val = jc.getAttributeNS(W_NS, 'val');\n if (val) out.innerTextAlign = val;\n }\n }\n }\n } else {\n out.hasInnerText = false;\n }\n }\n\n return out;\n}\n\n/**\n * 取承载 drawing 的 w:p 元素和在父 w:p 下的 run 序号;找不到时返回 nulls。\n *\n * **两段式**(CR 设计澄清):\n *\n * 1. 向上找最近的 `w:r` 和最近的 `w:p`(这一步遇到 `txbxContent` 前会先撞到内层 `w:p`,\n * 旧实现把 txbxContent 守卫放在这一步是死代码 —— 永远触发不到)\n *\n * 2. 找到 `w:p` 后**继续向上**检查它是否落在 `w:txbxContent` 子树里。若是,返回\n * `{ para: null, runIndex: undefined }`,让 `DrawingInfo.anchorParaId` 留空。\n *\n * 为什么这样设计:文本框内的 `w:p` 与外层 `w:p` 是**两套独立段落流**(不在同一\n * paraIdx scope)。如果把\"文本框内段落 paraId\"挂给 drawing:\n * - 下游\"删 paraId\" 命令找不到(文本框内段落不进 paraIdx body 视图)\n * - 误以为\"删这个段落 = 删这张图\",结果删的是文本框内的整段,留下空文本框\n *\n * 文档站根(body / hdr / ftr / footnote / endnote / comment)作为向上扫描的边界,\n * 避免穿过 story 边界。\n */\nfunction findAnchorParagraph(drawing: Element): {\n para: Element | null;\n runIndex: number | undefined;\n} {\n // 第 1 步:向上找最近的 w:r 与最近的 w:p\n let p: Element | null = drawing.parentElement;\n let runEl: Element | null = null;\n let containerP: Element | null = null;\n while (p) {\n if (p.namespaceURI === W_NS) {\n if (p.localName === 'r' && !runEl) runEl = p;\n if (p.localName === 'p') {\n containerP = p;\n break;\n }\n }\n p = p.parentElement;\n }\n if (!containerP) return { para: null, runIndex: undefined };\n\n // 第 2 步:检查 containerP 是否落在 txbxContent 内 —— 若是,主动返回 null\n // 防御 story 根(body / hdr / ftr / footnote / endnote / comment)作为扫描边界\n let q: Element | null = containerP.parentElement;\n while (q) {\n if (q.namespaceURI === W_NS) {\n if (q.localName === 'txbxContent') {\n return { para: null, runIndex: undefined };\n }\n if (\n q.localName === 'body' ||\n q.localName === 'hdr' ||\n q.localName === 'ftr' ||\n q.localName === 'footnote' ||\n q.localName === 'endnote' ||\n q.localName === 'comment'\n ) {\n break;\n }\n }\n q = q.parentElement;\n }\n\n // 第 3 步:在 containerP 直接子中找 runEl 的序号\n let runIndex: number | undefined;\n if (runEl) {\n let n = 0;\n for (let i = 0; i < containerP.childNodes.length; i++) {\n const child = containerP.childNodes[i] as Element;\n if (child && child.nodeType === 1 && child.namespaceURI === W_NS && child.localName === 'r') {\n if (child === runEl) {\n runIndex = n;\n break;\n }\n n++;\n }\n }\n }\n\n return { para: containerP, runIndex };\n}\n\n/** 判断 drawing 是否嵌套在另一个 drawing 内(用于 group / canvas 内子图过滤的预判)*/\nfunction hasDrawingAncestor(drawing: Element): boolean {\n let p = drawing.parentElement;\n while (p) {\n if (p.namespaceURI === W_NS && p.localName === 'drawing' && p !== drawing) return true;\n p = p.parentElement;\n }\n return false;\n}\n\nfunction nearestAncestor(el: Element, ns: string, localName: string): Element | null {\n let p = el.parentElement;\n while (p) {\n if (p.namespaceURI === ns && p.localName === localName) return p;\n p = p.parentElement;\n }\n return null;\n}\n\nfunction paragraphText(p: Element, runTextOpts?: RunTextOptions): string {\n let text = '';\n for (let i = 0; i < p.childNodes.length; i++) {\n const child = p.childNodes[i] as Element;\n if (!child || child.nodeType !== 1 || child.namespaceURI !== W_NS) continue;\n if (child.localName === 'r') text += getRunText(child, runTextOpts);\n }\n return text;\n}\n\nfunction directDrawingIdsInTextBoxParagraph(\n p: Element,\n ownerTxbx: Element,\n drawingToId: Map<Element, string>,\n): string[] {\n const ids: string[] = [];\n const drawings = p.getElementsByTagNameNS(W_NS, 'drawing');\n for (let i = 0; i < drawings.length; i++) {\n const drawing = drawings[i];\n if (!drawing || nearestAncestor(drawing, W_NS, 'txbxContent') !== ownerTxbx) continue;\n const id = drawingToId.get(drawing);\n if (id) ids.push(id);\n }\n return ids;\n}\n\nfunction collectTextBoxBlocks(\n txbx: Element,\n drawingToId: Map<Element, string>,\n tableToId: Map<Element, string>,\n runTextOpts?: RunTextOptions,\n): TextBoxBlockInfo[] {\n const blocks: TextBoxBlockInfo[] = [];\n for (let i = 0; i < txbx.childNodes.length; i++) {\n const child = txbx.childNodes[i] as Element;\n if (!child || child.nodeType !== 1 || child.namespaceURI !== W_NS) continue;\n\n if (child.localName === 'p') {\n const paraId = child.getAttributeNS(W14_NS, 'paraId') ?? undefined;\n const text = paragraphText(child, runTextOpts);\n const drawingIds = directDrawingIdsInTextBoxParagraph(child, txbx, drawingToId);\n if (paraId && (text.length > 0 || drawingIds.length === 0)) {\n blocks.push({ type: 'paragraph', paraId: paraId.toUpperCase(), text });\n }\n for (const drawingId of drawingIds) {\n blocks.push(\n paraId\n ? { type: 'drawing', drawingId, paraId: paraId.toUpperCase() }\n : { type: 'drawing', drawingId },\n );\n }\n continue;\n }\n\n if (child.localName === 'tbl') {\n const tableId = tableToId.get(child);\n if (tableId) blocks.push({ type: 'table', tableId });\n }\n }\n return blocks;\n}\n\nfunction reverseElementIndex(index: Map<string, Element>): Map<Element, string> {\n const reversed = new Map<Element, string>();\n for (const [id, el] of index) reversed.set(el, id);\n return reversed;\n}\n\n/** 取所在 story(用 contains 检测,stories 数量通常 < 20)*/\nfunction findStoryForEl(el: Element, stories: Story[]): Story | undefined {\n for (const s of stories) {\n if (s.root.contains(el as unknown as Node)) return s;\n }\n return undefined;\n}\n\n/** 判 partPath 对应的 contentType 是否是 header / footer */\nfunction isHeaderFooterPart(pkg: Package, partPath: string): boolean {\n const part = pkg.parts.get(partPath);\n if (!part) return false;\n const ct = part.contentType;\n return ct.endsWith('.header+xml') || ct.endsWith('.footer+xml');\n}\n\n/** 分析单个 w:drawing → DrawingInfo(不含 drawingId / partPath / index / isInHeaderFooter)*/\nfunction analyzeDrawing(\n drawing: Element,\n runTextOpts?: RunTextOptions,\n drawingToId?: Map<Element, string>,\n tableToId?: Map<Element, string>,\n): Omit<DrawingInfo, 'drawingId' | 'partPath' | 'index' | 'isInHeaderFooter'> {\n // 容器:drawing 的直接子是 wp:inline 或 wp:anchor\n const inlineEl = firstDirectChild(drawing, WP_NS, 'inline');\n const anchorEl = firstDirectChild(drawing, WP_NS, 'anchor');\n const container = inlineEl ?? anchorEl;\n\n const anchoringMode: DrawingAnchoringMode = anchorEl ? 'anchor' : 'inline';\n\n const kind: DrawingKind = container ? detectKind(container) : 'other';\n const extentEMU = container ? readExtent(container) : { cx: 0, cy: 0 };\n const hasPicReference = container ? hasPicReferenceIn(container) : false;\n\n const { para, runIndex } = findAnchorParagraph(drawing);\n const anchorParaId = para?.getAttributeNS(W14_NS, 'paraId') ?? undefined;\n\n const info: Omit<DrawingInfo, 'drawingId' | 'partPath' | 'index' | 'isInHeaderFooter'> = {\n anchoringMode,\n kind,\n extentEMU,\n hasPicReference,\n };\n\n if (anchorEl) {\n const wrap = readWrap(anchorEl);\n if (wrap !== undefined) info.wrap = wrap;\n const pos = readPosition(anchorEl);\n if (pos.horizontal || pos.vertical) info.position = pos;\n }\n\n if (anchorParaId !== undefined) info.anchorParaId = anchorParaId;\n if (runIndex !== undefined) info.runIndex = runIndex;\n\n if (kind === 'shape' || kind === 'textBox') {\n const sig = container\n ? readShapeSignals(container, kind === 'textBox', runTextOpts, drawingToId, tableToId)\n : { hasBorder: false, hasFill: false };\n info.hasBorder = sig.hasBorder;\n info.hasFill = sig.hasFill;\n if (sig.hasInnerText !== undefined) info.hasInnerText = sig.hasInnerText;\n if (sig.innerText !== undefined) info.innerText = sig.innerText;\n if (sig.innerParagraphIds !== undefined) info.innerParagraphIds = sig.innerParagraphIds;\n if (sig.innerBlocks !== undefined) info.innerBlocks = sig.innerBlocks;\n if (sig.innerTextAlign !== undefined) info.innerTextAlign = sig.innerTextAlign;\n }\n\n // 线条/连接线信号仅对纯几何 shape 有意义(textBox 是内容载体,不参与线条判定)\n if (kind === 'shape' && container) {\n info.isLineOrConnector = isLineOrConnectorShape(container);\n }\n\n return info;\n}\n\n// ─────────── 公开函数 ───────────\n\n/**\n * 枚举文档 drawing 对象,按文档顺序返回(与 drawingIdx 插入顺序一致)。\n *\n * - `scope: 'body'`(默认)—— 仅主文档 body 内的 drawing\n * - `scope: 'all'` —— 包含 headers / footers / footnotes / endnotes / comments\n *\n * **不做**业务分类(A/B/C)/ 分组键计算 / 代表样本选取 —— 这些属于 #191/#192。\n * 嵌套在另一个 drawing 内的子 drawing(如 group 的成员、canvas 的成员)**会被一并枚出**,\n * 由调用方按 hasDrawingAncestor 自行决定是否合并到外层(保持枚举层\"完整暴露\"的原则)。\n */\nexport function listDrawings(\n pkg: Package,\n indices: PackageIndices,\n opts?: ListDrawingsOptions,\n): DrawingInfo[] {\n const scope = opts?.scope ?? 'body';\n const stories = resolveScope(pkg, scope);\n const result: DrawingInfo[] = [];\n let idx = 1;\n const runTextOpts: RunTextOptions | undefined = opts?.structuralWhitespace\n ? { structuralWhitespace: true }\n : undefined;\n const drawingToId = reverseElementIndex(indices.drawingIdx);\n const tableToId = reverseElementIndex(indices.tblIdx);\n\n // 直接遍历 drawingIdx(插入顺序 = 文档顺序)\n for (const [drawingId, drawing] of indices.drawingIdx) {\n const story = findStoryForEl(drawing, stories);\n if (!story) continue; // 不在请求的 scope 内\n\n const info: DrawingInfo = {\n drawingId,\n partPath: story.partPath,\n index: idx++,\n isInHeaderFooter: isHeaderFooterPart(pkg, story.partPath),\n ...analyzeDrawing(drawing, runTextOpts, drawingToId, tableToId),\n };\n result.push(info);\n }\n\n return result;\n}\n\n/**\n * 按 drawingId 精确取单个 drawing。不存在返回 null。\n *\n * **index 字段语义**:返回 drawing 在 **全局 drawingIdx 插入顺序**中的 1-based 位置\n * —— 与 `listDrawings({ scope: 'all' })` 的 index 一致;与 `listDrawings({ scope: 'body' })`\n * 的 1-based 位置**不一定**相等(后者可能过滤掉了页眉页脚 drawing)。\n *\n * 复杂度:drawingIdx 查找 O(1);全局序号扫描 O(N),N = 包内 drawing 总数。\n * 调用方需要严格按 scope 取序号时应直接走 listDrawings。\n *\n * 旧实现复用了 list-tables.ts 的 `index: 0` 哨兵,下游用 `index` 排序/引用会拿到\n * 错误值(CR BUG-1)—— 改为真实序号。\n */\nexport function getDrawingById(\n pkg: Package,\n indices: PackageIndices,\n drawingId: string,\n): DrawingInfo | null {\n const drawing = indices.drawingIdx.get(drawingId);\n if (!drawing) return null;\n\n // 找所属 story 以获取 partPath\n const stories = resolveScope(pkg, 'all');\n const story = findStoryForEl(drawing, stories);\n if (!story) return null;\n const drawingToId = reverseElementIndex(indices.drawingIdx);\n const tableToId = reverseElementIndex(indices.tblIdx);\n\n // 计算全局 1-based 序号(drawingIdx 是 Map,迭代序 = 文档顺序)\n let globalIndex = 0;\n let i = 0;\n for (const id of indices.drawingIdx.keys()) {\n i++;\n if (id === drawingId) {\n globalIndex = i;\n break;\n }\n }\n\n return {\n drawingId,\n partPath: story.partPath,\n index: globalIndex,\n isInHeaderFooter: isHeaderFooterPart(pkg, story.partPath),\n ...analyzeDrawing(drawing, undefined, drawingToId, tableToId),\n };\n}\n\n/**\n * 装载期构建 drawingIdx:枚举 Package 中所有 Word 内容 part 的 <w:drawing>,\n * 按文档顺序分配 'dr-NNNN' ID。由 buildIndices 调用。\n *\n * 内部函数风格对齐 buildTableIndex(list-tables.ts)。\n */\nexport function buildDrawingIndex(\n parts: Iterable<{\n contentType: string;\n xmlDom?: {\n getElementsByTagNameNS(\n ns: string,\n local: string,\n ): { length: number } & Record<number, Element>;\n };\n }>,\n): Map<string, Element> {\n const drawingIdx = new Map<string, Element>();\n let n = 1;\n\n for (const part of parts) {\n const ct = part.contentType;\n // 与 ListDrawingsOptions / DrawingsApi.list 的 scope:'all' 语义对齐:\n // body + headers + footers + footnotes + endnotes + comments\n //\n // CR 修复:旧实现注释「comments.xml 内通常不含 drawing,跳过」是错误假设\n // —— 评论 part 内合法可含 drawing(如批注图示),漏掉会让 scope:'all' 与文档承诺不一致。\n if (\n !ct.includes('wordprocessingml') &&\n !ct.endsWith('.main+xml') &&\n !ct.endsWith('.header+xml') &&\n !ct.endsWith('.footer+xml') &&\n !ct.endsWith('.footnotes+xml') &&\n !ct.endsWith('.endnotes+xml') &&\n !ct.endsWith('.comments+xml')\n ) {\n continue;\n }\n if (!part.xmlDom) continue;\n\n const drawings = part.xmlDom.getElementsByTagNameNS(W_NS, 'drawing');\n for (let i = 0; i < drawings.length; i++) {\n const dr = drawings[i];\n if (!dr) continue;\n // CR BUG-3:跳过 mc:Fallback 内的 drawing —— mc:Choice 已是首选呈现,\n // Fallback 是降级方案,把它也计入会导致同一对象双计。\n if (hasFallbackAncestor(dr as Element)) continue;\n // 嵌套 drawing:group / canvas 的成员 drawing 仍是 w:drawing,但通常 group/canvas\n // 的 child shapes 在 wpg:grpSp / wpc:wpc 下,不会用 w:drawing 包装。不做主动过滤\n // —— 让上层(#191 / #192)按需用 hasDrawingAncestor 自己判。\n const id = `dr-${String(n).padStart(4, '0')}`;\n drawingIdx.set(id, dr);\n n++;\n }\n }\n\n return drawingIdx;\n}\n\n/** 暴露内部工具:判断元素是否嵌套在另一个 drawing 内(#191 合并 group 子图时用)*/\nexport { hasDrawingAncestor };\n","/**\n * Word 装载期三索引(docs/03 §13.1 / §13.6)。\n *\n * - paraIdx:paraId(大写 8 hex)→ <w:p> Element\n * - bookmarkIdx:name → { start: <w:bookmarkStart>; end: <w:bookmarkEnd>; partPath }\n * - commentIdx:id(number)→ { comment?: <w:comment>; anchors: { start?, end?, ref? } }\n *\n * 同时完成装载期书签校验:**空名抛错**;**重名容错**(保留第一个、忽略后续,绝不崩 ——\n * 真实文档常见重复的 `_GoBack` 等编辑痕迹书签)。未来由 #15 WordError 统一,先用 Error + code。\n *\n * 性能目标:10 万段落 < 100ms(Chrome/Node;WPS WebView 宽松到 < 300ms)。由 `ensureParaIds`\n * 同样走 getElementsByTagNameNS 保证,这里复用同一枚举路径。\n */\nimport type { Package, Part } from '../pack/index.js';\nimport { isXmlPart } from '../pack/index.js';\nimport { W_NS, W14_NS } from './namespaces.js';\nimport { buildStylesIndex, type StyleInfo } from './style-queries.js';\nimport { buildTableIndex } from './list-tables.js';\nimport { buildDrawingIndex } from './list-drawings.js';\n\n/** 书签校验错误 code(暂不挂 WordError;#15 会统一到 WordErrorCode) */\nexport type BookmarkLoadErrorCode =\n // 【已弃用 / 不再抛出】重名书签现为容错(保留第一个、忽略后续),buildIndices 不再产生此 code。\n // 保留在 union 中仅作向后兼容,避免破坏可能引用此类型的消费方。\n 'BOOKMARK_NAME_DUPLICATE_ON_LOAD' | 'BOOKMARK_NAME_EMPTY_ON_LOAD';\n\n/** 装载期抛出的书签错误 */\nexport class BookmarkLoadError extends Error {\n readonly code: BookmarkLoadErrorCode;\n readonly details: Record<string, unknown>;\n constructor(code: BookmarkLoadErrorCode, message: string, details: Record<string, unknown> = {}) {\n super(message);\n this.name = 'BookmarkLoadError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport interface BookmarkEntry {\n /** `<w:bookmarkStart>` 元素 */\n start: Element;\n /** `<w:bookmarkEnd>` 元素;若装载数据里缺失可能为 null */\n end: Element | null;\n /** 所在 part 的 path(如 'word/document.xml') */\n partPath: string;\n}\n\nexport interface CommentEntry {\n /** `<w:comment>` 元素(来自 comments.xml);若无 comments.xml 可能为 null */\n comment: Element | null;\n /** 锚点三件套(在 document.xml / headers / footers 等 body-side part 上) */\n anchors: {\n start: Element | null;\n end: Element | null;\n ref: Element | null;\n };\n}\n\nexport interface PackageIndices {\n /** paraId → <w:p> */\n paraIdx: Map<string, Element>;\n /** bookmark name → entry(含 start/end/partPath) */\n bookmarkIdx: Map<string, BookmarkEntry>;\n /** comment id → entry(comment 主体 + 三锚点) */\n commentIdx: Map<number, CommentEntry>;\n /**\n * styleId → StyleInfo(来自 styles.xml)。\n * styles.xml 不存在时为空 Map。\n * 消费方按 styleId 做 O(1) 查找,沿 basedOn 链判断语义角色时须自行防环。\n */\n stylesIdx: Map<string, StyleInfo>;\n /**\n * tableId('tbl-NNNN')→ <w:tbl> Element(#185)。\n * 装载期按文档顺序分配,包含所有 part 的所有表格(含嵌套)。\n * 供 listTables / getTableById / plan executor(deleteTable)使用。\n */\n tblIdx: Map<string, Element>;\n /**\n * drawingId('dr-NNNN')→ <w:drawing> Element(#189)。\n * 装载期按文档顺序分配,包含所有 Word 内容 part 的所有 drawing。\n * 供 listDrawings / getDrawingById / 占位图替换(#190)使用。\n *\n * 注意:嵌套在 group / canvas 内的子 drawing 也会被收入(保持枚举层完整暴露),\n * 上层(#191 / #192)按需用 hasDrawingAncestor 自行决定合并策略。\n */\n drawingIdx: Map<string, Element>;\n}\n\n// re-export StyleInfo 供其他 word 内部模块从 indices 一站式引入\nexport type { StyleInfo };\n\n/** 判断 part 是否 Word 主要内容 part(按常见 contentType 前缀匹配) */\nfunction isWordContentPart(part: Part): boolean {\n const ct = part.contentType;\n return (\n ct.includes('wordprocessingml') ||\n ct.endsWith('.main+xml') ||\n ct.endsWith('.header+xml') ||\n ct.endsWith('.footer+xml') ||\n ct.endsWith('.footnotes+xml') ||\n ct.endsWith('.endnotes+xml') ||\n ct.endsWith('.comments+xml')\n );\n}\n\n/** 判断 part 是否 comments 存储 part(commentIdx 的 comment 主体来自这里) */\nfunction isCommentsPart(part: Part): boolean {\n return part.contentType.endsWith('.comments+xml');\n}\n\n/** 把 HTMLCollection-ish 枚举结果快照成数组(避免 live 行为) */\nfunction snapshot<T extends Element>(col: { length: number } & Record<number, T>): T[] {\n const out: T[] = [];\n for (let i = 0; i < col.length; i++) {\n const el = col[i];\n if (el) out.push(el);\n }\n return out;\n}\n\n/**\n * 扫 Package 构建三索引。\n *\n * - **装载期书签校验**:遇空名抛 `BookmarkLoadError`(code = BOOKMARK_NAME_EMPTY_ON_LOAD);\n * 遇重名**容错**(保留第一个、忽略后续,不抛错 —— `_GoBack` 等编辑痕迹书签可安全忽略)\n * - 孤儿 `bookmarkEnd`(无匹配 start)忽略\n * - 缺 `bookmarkEnd` 的书签仍入索引,`entry.end` 为 null\n * - 不抛错的其它不一致(comment 无 <w:comment> 主体、anchors 不全)进索引,由调用方决定是否告警\n */\nexport function buildIndices(pkg: Package): PackageIndices {\n const paraIdx = new Map<string, Element>();\n const bookmarkIdx = new Map<string, BookmarkEntry>();\n const commentIdx = new Map<number, CommentEntry>();\n\n // bookmarkStart 临时:id -> { start, partPath, name }(用于和后续 bookmarkEnd 配对)\n const pendingStarts = new Map<string, { start: Element; partPath: string; name: string }>();\n\n for (const part of pkg.parts.values()) {\n if (!isXmlPart(part) || !part.xmlDom) continue;\n if (!isWordContentPart(part)) continue;\n\n // paraIdx\n const paragraphs = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'p'));\n for (const p of paragraphs) {\n const id = p.getAttributeNS(W14_NS, 'paraId');\n if (id) paraIdx.set(id.toUpperCase(), p);\n }\n\n // bookmarkIdx — 先收集 start,后续匹配 end\n const starts = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'bookmarkStart'));\n for (const start of starts) {\n const bId = start.getAttributeNS(W_NS, 'id');\n const bName = start.getAttributeNS(W_NS, 'name');\n if (bName === null) {\n // 缺 name 属性 —— 当作空名\n throw new BookmarkLoadError(\n 'BOOKMARK_NAME_EMPTY_ON_LOAD',\n `bookmarkStart with w:id=\"${bId ?? ''}\" has no w:name`,\n { partPath: part.path, bookmarkId: bId ?? null },\n );\n }\n if (bName.length === 0) {\n throw new BookmarkLoadError(\n 'BOOKMARK_NAME_EMPTY_ON_LOAD',\n `bookmarkStart with w:id=\"${bId ?? ''}\" has empty w:name`,\n { partPath: part.path, bookmarkId: bId ?? null },\n );\n }\n if (bookmarkIdx.has(bName)) {\n // 容错:重名书签**保留第一个、忽略后续**,不抛错(绝不因装载期数据瑕疵崩溃)。\n //\n // 真实文档(多次编辑 / 多工具处理过)经常出现重复书签名 —— 最典型的是 Word 自动\n // 插入的 `_GoBack`(标记上次编辑位置),也可能是模板拼接残留的其它重名书签。\n // OOXML 实践中 Word 自己也容忍重复书签名;bookmarkIdx 是 name→entry 的 Map,按 name\n // 唯一本就是其数据结构约束,保留首个即可满足所有下游(反查 / 重名检查 / 删除)。\n //\n // 注意:这里只跳过往 bookmarkIdx 登记,但**不会**把后续重名的 bookmarkStart 当作\n // pendingStart 处理,因此其配套的 bookmarkEnd 不会错配到第一个 entry 上(end 仍只认\n // 第一个 start 的 id 作用域)。元素本身保留在 DOM 里,保存时原样写回,内容不丢失。\n continue;\n }\n // 先登记:end 稍后补\n bookmarkIdx.set(bName, { start, end: null, partPath: part.path });\n if (bId !== null) {\n pendingStarts.set(`${part.path}::${bId}`, { start, partPath: part.path, name: bName });\n }\n }\n\n // 匹配 bookmarkEnd(按 w:id,作用域限定到 part 内)\n const ends = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'bookmarkEnd'));\n for (const end of ends) {\n const eId = end.getAttributeNS(W_NS, 'id');\n if (eId === null) continue;\n const matched = pendingStarts.get(`${part.path}::${eId}`);\n if (matched) {\n const entry = bookmarkIdx.get(matched.name);\n if (entry) entry.end = end;\n }\n }\n\n // commentIdx — 锚点(document.xml / headers / footers 等 body-side part)\n const anchorStarts = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'commentRangeStart'));\n const anchorEnds = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'commentRangeEnd'));\n const anchorRefs = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'commentReference'));\n for (const anchor of anchorStarts) {\n const id = numAttr(anchor, W_NS, 'id');\n if (id === null) continue;\n getOrCreateCommentEntry(commentIdx, id).anchors.start = anchor;\n }\n for (const anchor of anchorEnds) {\n const id = numAttr(anchor, W_NS, 'id');\n if (id === null) continue;\n getOrCreateCommentEntry(commentIdx, id).anchors.end = anchor;\n }\n for (const anchor of anchorRefs) {\n const id = numAttr(anchor, W_NS, 'id');\n if (id === null) continue;\n getOrCreateCommentEntry(commentIdx, id).anchors.ref = anchor;\n }\n\n // commentIdx — 主体(comments.xml)\n if (isCommentsPart(part)) {\n const comments = snapshot(part.xmlDom.getElementsByTagNameNS(W_NS, 'comment'));\n for (const c of comments) {\n const id = numAttr(c, W_NS, 'id');\n if (id === null) continue;\n getOrCreateCommentEntry(commentIdx, id).comment = c;\n }\n }\n }\n\n const stylesIdx = buildStylesIndex(pkg);\n const tblIdx = buildTableIndex(pkg.parts.values());\n const drawingIdx = buildDrawingIndex(pkg.parts.values());\n\n return { paraIdx, bookmarkIdx, commentIdx, stylesIdx, tblIdx, drawingIdx };\n}\n\nfunction numAttr(el: Element, ns: string, local: string): number | null {\n const v = el.getAttributeNS(ns, local);\n if (v === null) return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction getOrCreateCommentEntry(idx: Map<number, CommentEntry>, id: number): CommentEntry {\n let e = idx.get(id);\n if (!e) {\n e = { comment: null, anchors: { start: null, end: null, ref: null } };\n idx.set(id, e);\n }\n return e;\n}\n","/**\n * listParagraphs —— 段落查询纯工具(docs/03 §9 / docs/03.1 §1.6,#24)。\n *\n * 职责:按 scope / filter / textFormat / pagination 返回 ParagraphInfo 列表。\n *\n * 架构:**纯函数**,拿 pkg 给结果,不知道文档尺寸、不做智能默认。\"默认分页\"\n * 是 `{ mode: 'byCount', pageSize: 25, pageIndex: 0 }`,要全量请显式传\n * `{ pagination: { mode: 'all' } }`(2026-04-22 从早期\"≤500 段自动 all\"的智能\n * 默认撤回;见 docs/03 §9.2 的历史说明)。\n *\n * 实现约束:\n * - 段落枚举走 `getStoryParagraphs`(基于 `getElementsByTagNameNS`),**不**用\n * fontoxpath。10 万段落上 `//w:p` 慢 460× —— CI 的 `pnpm check:xpath` 会在\n * 源码里扫到此类无谓词 XPath 即失败。\n * - `filter.contains` 是 O(N) 子串匹配,大档慢(docs 已标注)。\n * - `hasComments` / `hasRevisions` / `hasBookmarks` 用 `el.getElementsByTagNameNS(...).length > 0`\n * 检测段内是否存在对应标记元素,不递归手遍。\n */\nimport type { Package } from '../pack/index.js';\nimport type { PackageIndices } from './indices.js';\nimport { buildTableIndex } from './list-tables.js';\nimport { MATH_NS, W14_NS, W_NS } from './namespaces.js';\nimport { getParagraphText, type RunTextOptions } from './runs.js';\nimport { findStoryFor, getStoryParagraphs, resolveScope, type StoryId } from './stories.js';\n\n/** list scope —— 'body' / 'all' 或显式 StoryId 列表 */\nexport type ListParagraphsScope = 'body' | 'all' | StoryId[];\n\n/** text 返回格式。'preview' = 首 100 字 */\nexport type ListParagraphsTextFormat = 'full' | 'preview';\n\nexport interface ListParagraphsFilter {\n /** 等值匹配 `<w:pPr>/<w:pStyle w:val>` */\n styleId?: string;\n /** **O(N) 子串匹配**,区分大小写。大档谨慎使用。 */\n contains?: string;\n /** 段内是否含 comment 标记(commentRangeStart / commentReference / commentRangeEnd) */\n hasComments?: boolean;\n /** 段内是否含 revision 标记(Tier 1 只看 w:ins / w:del,不含 rPrChange/pPrChange) */\n hasRevisions?: boolean;\n /**\n * 排除位于表格单元格(`<w:tc>`)内的段落。\n *\n * 表格内容由独立的 tables phase 处理,段落 phase 只关心主文本流段落。\n * 开启后会对每个段落向上遍历祖先链(深度通常 ≤5),找到 `<w:tc>` 即排除。\n */\n excludeTableCells?: boolean;\n}\n\nexport type ListParagraphsPagination =\n | { mode: 'all' }\n | { mode: 'byCount'; pageSize: number; pageIndex: number }\n | { mode: 'byChar'; charPerPage: number; pageIndex: number };\n\n/**\n * 段落直接格式属性(#182)。\n *\n * 仅包含在 `<w:pPr>` 上**直接设置**的值,不做样式继承计算。\n * 消费方(如骨架化引擎)可将这些值与 styleId 组合为聚类 key,\n * 区分同一样式但有手工格式覆盖的段落。\n *\n * 所有字段均为可选——未直接设置的属性不出现在结果中(继承自样式或默认值)。\n */\nexport interface ParagraphFormat {\n /**\n * 水平对齐(`w:pPr/w:jc@w:val`)。\n * `'both'` 对应 OOXML 的 \"both\"(两端对齐)。\n */\n alignment?: 'left' | 'center' | 'right' | 'both' | 'distribute';\n /**\n * 缩进(`w:pPr/w:ind`),单位 twips(1/20 磅)。\n * `firstLine` 正值为首行缩进,负值为悬挂缩进(来自 `w:hanging`)。\n */\n indent?: {\n left?: number;\n right?: number;\n /** 首行缩进(正)或悬挂缩进(负,源自 `w:hanging`) */\n firstLine?: number;\n };\n /**\n * 段间距(`w:pPr/w:spacing`),单位 twips(line 例外:受 lineRule 影响)。\n */\n spacing?: {\n before?: number;\n after?: number;\n line?: number;\n lineRule?: 'auto' | 'exact' | 'atLeast';\n };\n /**\n * 段落级默认字体大小(`w:pPr/w:rPr/w:sz@w:val ÷ 2`),单位磅。\n * 仅当段落属性中显式设置了 `w:rPr/w:sz` 时有值;继承自样式的字号不在此字段。\n */\n fontSize?: number;\n /**\n * 段落级默认粗体(`w:pPr/w:rPr/w:b`)。\n * `true` = 显式开启;`false` = 显式关闭(`w:val=\"0\"`);\n * 字段不存在 = 未直接设置(继承自样式)。\n */\n bold?: boolean;\n /**\n * 大纲级别(`w:pPr/w:outlineLvl@w:val`),0–8 对应标题 1–9,9 表示正文。\n * WPS / Word 的自定义标题样式通常在此字段直接编码级别,即使 basedOn 是 Normal。\n * 字段不存在 = 未在段落级直接设置(继承自样式或默认值)。\n */\n outlineLevel?: number;\n /**\n * 段落是否直接挂有编号属性(`w:pPr/w:numPr` 存在且含 `w:numId@w:val != \"0\"`)。\n * 用于列表检测:有编号属性的段落通常是列表项。\n */\n hasNumbering?: boolean;\n /**\n * 段落是否强制从新页起始(`w:pPr/w:pageBreakBefore`,#211)。\n * true = 渲染该段时从新页开始(封面段的强信号之一)。\n * 字段不存在 = 未在段落级直接设置(继承自样式或默认 false)。\n */\n pageBreakBefore?: boolean;\n /**\n * 段落是否承载节属性(`w:pPr/w:sectPr` 存在,#211)。\n * true = 该段是其所属节的最后一段,节边界在此结束。\n * 与 doc.sections.list() 的 lastParaId 字段对应。\n */\n hasSectionBreak?: boolean;\n /**\n * 段落内是否含手工分页符(`w:r/w:br[@w:type=\"page\"]`,#211)。\n * 与 pageBreakBefore 互补 —— 后者作用于段首,前者可出现在段中/段尾。\n * 字段不存在 = 段内无手工分页符。\n */\n hasManualPageBreak?: boolean;\n /**\n * 段落级默认字体颜色(`w:pPr/w:rPr/w:color@w:val`,task-20260615-redhead-fingerprint)。\n * 段标 run 颜色,作用于无独立 rPr 的 run。值为 6 位十六进制 `RRGGBB`(如 `FF0000`)或 `auto`。\n * 仅当段落属性中显式设置了 `w:pPr/w:rPr/w:color` 时有值;继承自样式的颜色不在此字段。\n *\n * 用途(骨架化 / 排版的视觉簇判据):判定\"封面式文头\"等需区分色异段(如红头)。\n * 注意:行内首 run 的颜色单独由 {@link firstRunColor} 提供——很多文档把红色编码在 run 级\n * 而非段标级,两者互补使用。\n */\n color?: string;\n /**\n * 段落**首个**直属 run(`w:r/w:rPr/w:color@w:val`)的字体颜色(task-20260615-redhead-fingerprint)。\n * 值为 6 位十六进制 `RRGGBB` 或 `auto`。仅当首个直属 run 显式设了 `w:color` 时有值。\n *\n * 与 {@link color}(段标级)互补:红头/标题等视觉显著段的颜色常落在 run 级 rPr 而非段标 rPr,\n * 需读首 run 才能拿到。取**首个直属 run**(非表格/文本框下钻),与 getParagraphText 的直属 run\n * 语义对齐;段内多 run 颜色不一致时只反映首 run(满足\"段首视觉身份\"判据,不做整段聚合)。\n */\n firstRunColor?: string;\n}\n\nexport interface ListParagraphsOptions {\n /** 默认 'body' */\n scope?: ListParagraphsScope;\n filter?: ListParagraphsFilter;\n /** 默认 'full' */\n textFormat?: ListParagraphsTextFormat;\n /** 默认 `{ mode: 'byCount', pageSize: 25, pageIndex: 0 }`(不再有\"智能默认\") */\n pagination?: ListParagraphsPagination;\n /**\n * 是否在结果中包含段落直接格式属性(`ParagraphFormat`)。\n * 默认 `false`。开启后每个 `ParagraphInfo.format` 会携带从 `<w:pPr>` 读取的直接设置值。\n * 仅包含直接设置的属性,不做样式继承计算。\n */\n includeFormat?: boolean;\n /**\n * 启用结构空白保真读(task-20260623-core-tab-faithful-read)。\n * `true` 时 `ParagraphInfo.text` 含 `\\t`(制表符)和 `\\n`(软换行),\n * 是 `appendSegmentsToRun` 的精确逆运算。\n * 默认 `false`:只拼 `<w:t>`,偏移算术安全(与历史行为完全一致)。\n */\n structuralWhitespace?: boolean;\n /**\n * 是否在结果中包含段落所在表格的坐标信息(task-20260625-para-table-info)。\n * 默认 `false`。\n *\n * 开启后,位于表格单元格内的段落会携带 `ParagraphInfo.table`(tableId / row / col)。\n * 表格外段落的 `table` 字段不出现。\n *\n * 与 `includeFormat` 是独立开关:表格归属是结构信息,不是格式。\n * 开启时会对每个段落做一次祖先链遍历(深度 ≤ 5),\n * 并在首次调用时构建 `<w:tbl> Element → tableId` 反查映射(O(表格数))。\n */\n includeTableInfo?: boolean;\n}\n\nexport interface ParagraphInfo {\n paraId: string;\n story: StoryId;\n /** textFormat='preview' 时为首 100 字 */\n text: string;\n /** preview 模式被截断时为 true;full 模式/未截断不给 */\n truncated?: boolean;\n /** `<w:pStyle w:val>` 未设则缺 */\n styleId?: string;\n hasRevisions?: boolean;\n hasComments?: boolean;\n hasBookmarks?: boolean;\n /**\n * 段落子树是否含 OMML 公式对象(`<m:oMath>`,math namespace)。\n * Word「插入→公式」生成的公式对象。纯文字写的\"公式\"(如 \"F=ma\")不算。\n * 含则为 true;不含时不给(与 hasComments 等同风格)。\n */\n hasMath?: boolean;\n /**\n * 段落直接格式属性。仅在 `listParagraphs` 传入 `includeFormat: true` 时存在。\n * 不包含从样式继承的属性值。\n */\n format?: ParagraphFormat;\n /**\n * 段落所在表格坐标。仅当 `includeTableInfo=true` 且段落位于表格单元格内时存在。\n *\n * - `tableId`:与 `doc.tables.list()` / `getTable()` 使用的 id 完全一致(装载期\n * `buildIndices` 分配的 `tbl-NNNN`)。\n * - `row`:所在 `<w:tr>` 在该表中的 0 基行号。\n * - `col`:所在 `<w:tc>` 在该行中的 0 基列号(物理序号)。\n *\n * 注:`col` 是物理 `<w:tc>` 序号,**不**做 gridSpan / vMerge 的逻辑列号校正——\n * 含合并单元格的表格中,物理序号与逻辑列号可能不同。v1 取物理序号已能满足\n * \"区分表格内/外编号序列\"的主场景;如需精确逻辑列号,可在 v2 补充。\n *\n * 嵌套表格时报**最内层**的表格。\n */\n table?: { tableId: string; row: number; col: number };\n}\n\nexport interface ListResult {\n paragraphs: ParagraphInfo[];\n pagination: {\n mode: 'all' | 'byCount' | 'byChar';\n pageIndex: number;\n totalPages: number;\n totalParagraphs: number;\n hasNext: boolean;\n /**\n * 保留字段,当前实现恒不填(#24 把\"≤500 段自动 all\"的智能默认撤回,改为恒定\n * byCount(25,0)默认;docs/03 §9.2)。调用方不应依赖。\n */\n autoPaginated?: boolean;\n };\n}\n\n/** 默认分页:首页、25 条 */\nexport const DEFAULT_PAGINATION: ListParagraphsPagination = {\n mode: 'byCount',\n pageSize: 25,\n pageIndex: 0,\n};\n\n/** preview 截断字数 */\nexport const PREVIEW_CHAR_LIMIT = 100;\n\nexport function listParagraphs(pkg: Package, opts: ListParagraphsOptions = {}): ListResult {\n // 1. scope → stories(resolveScope 在 stories.ts,#26 起被 listRevisions 共用)\n const stories = resolveScope(pkg, opts.scope ?? 'body');\n\n // 2. flatten (story, paragraphEl)\n interface Entry {\n story: StoryId;\n el: Element;\n }\n const all: Entry[] = [];\n for (const story of stories) {\n for (const el of getStoryParagraphs(story)) {\n all.push({ story: story.id, el });\n }\n }\n\n // 3. filter(在分页前,totalParagraphs 取过滤后)\n const filtered = opts.filter ? all.filter((e) => matchesFilter(e.el, opts.filter!)) : all;\n\n // 4. 分页\n const pagination = opts.pagination ?? DEFAULT_PAGINATION;\n const { slice, meta } = paginate(filtered, pagination);\n\n // 5. 映射为 ParagraphInfo(textFormat 在此应用 preview 截断)\n const textFormat = opts.textFormat ?? 'full';\n const includeFormat = opts.includeFormat ?? false;\n const runTextOpts: RunTextOptions | undefined = opts.structuralWhitespace\n ? { structuralWhitespace: true }\n : undefined;\n\n // includeTableInfo=true 时构建 tblElement → tableId 反查 Map(O(表格数),只做一次)\n // buildTableIndex 返回 Map<tableId, tblEl>,反转后可从 Element 查 tableId\n let tblReverseIdx: Map<Element, string> | undefined;\n if (opts.includeTableInfo) {\n const tblIdx = buildTableIndex(pkg.parts.values());\n tblReverseIdx = new Map<Element, string>();\n for (const [id, el] of tblIdx) {\n tblReverseIdx.set(el, id);\n }\n }\n\n const paragraphs = slice.map((e) =>\n buildInfo(e.story, e.el, textFormat, includeFormat, runTextOpts, tblReverseIdx),\n );\n\n return { paragraphs, pagination: meta };\n}\n\n// ─────────── filter ───────────\n\nfunction matchesFilter(p: Element, f: ListParagraphsFilter): boolean {\n // excludeTableCells 放最前:祖先遍历 O(depth≤5),比文本扫描快,能快速排除大量表格段落\n if (f.excludeTableCells) {\n if (isInTableCell(p)) return false;\n }\n if (f.styleId !== undefined) {\n if (getStyleId(p) !== f.styleId) return false;\n }\n if (f.hasComments !== undefined) {\n if (detectHasComments(p) !== f.hasComments) return false;\n }\n if (f.hasRevisions !== undefined) {\n if (detectHasRevisions(p) !== f.hasRevisions) return false;\n }\n // contains 放最后:O(N) 全文扫,前面的廉价过滤已能淘汰大部分段落\n if (f.contains !== undefined) {\n if (!getParagraphText(p).includes(f.contains)) return false;\n }\n return true;\n}\n\n// ─────────── 属性检测 ───────────\n\nfunction getStyleId(p: Element): string | undefined {\n // <w:p><w:pPr><w:pStyle w:val=\"xxx\"/></w:pPr>\n const pPr = firstDirectChild(p, W_NS, 'pPr');\n if (!pPr) return undefined;\n const pStyle = firstDirectChild(pPr, W_NS, 'pStyle');\n if (!pStyle) return undefined;\n const val = pStyle.getAttributeNS(W_NS, 'val');\n return val ?? undefined;\n}\n\nfunction firstDirectChild(el: Element, ns: string, local: string): Element | null {\n const children = el.childNodes;\n for (let i = 0; i < children.length; i++) {\n const c = children[i];\n if (\n c &&\n c.nodeType === 1 &&\n (c as Element).namespaceURI === ns &&\n (c as Element).localName === local\n ) {\n return c as Element;\n }\n }\n return null;\n}\n\n/**\n * 判断 `<w:p>` 是否位于表格单元格(`<w:tc>`)内。\n *\n * 向上遍历祖先链:遇到 `<w:tc>` 返回 true,遇到 `<w:body>` 或到达根节点返回 false。\n * 典型深度:body > tbl > tr > tc > p = 4 跳,代价可忽略。\n */\nfunction isInTableCell(p: Element): boolean {\n let el: Element | null = p.parentElement;\n while (el) {\n if (el.namespaceURI === W_NS) {\n if (el.localName === 'tc') return true;\n if (el.localName === 'body') return false; // 到达 body 层,不可能再有 tc\n }\n el = el.parentElement;\n }\n return false;\n}\n\n/**\n * 一次祖先遍历,同时拿到 `<w:tc>`、`<w:tr>`、最内层 `<w:tbl>` 三个祖先元素。\n *\n * 遍历策略:向上走,遇到首个 `<w:tc>` 记录为 tc,继续向上遇到 `<w:tr>` 记录为 tr,\n * 再继续向上遇到首个 `<w:tbl>` 记录为 tbl(即最内层表格),然后停止。\n * 遇到 story 根元素(body / hdr / ftr 等)仍未找到 `<w:tc>` 则返回 null(段落不在表格内)。\n *\n * 嵌套表格时 tbl 是最内层表格(因为自下而上遇到的第一个 tbl 就是最内层的)。\n */\nfunction getTableCellAncestors(p: Element): { tc: Element; tr: Element; tbl: Element } | null {\n let tc: Element | null = null;\n let tr: Element | null = null;\n let el: Element | null = p.parentElement;\n\n while (el) {\n if (el.namespaceURI === W_NS) {\n if (!tc) {\n if (el.localName === 'tc') {\n tc = el;\n } else if (\n el.localName === 'body' ||\n el.localName === 'hdr' ||\n el.localName === 'ftr' ||\n el.localName === 'footnote' ||\n el.localName === 'endnote' ||\n el.localName === 'comment'\n ) {\n // 到达 story 根,段落不在表格内\n return null;\n }\n } else if (!tr) {\n if (el.localName === 'tr') {\n tr = el;\n }\n } else {\n // tc 和 tr 都已找到,找最内层 tbl\n if (el.localName === 'tbl') {\n return { tc, tr, tbl: el };\n }\n }\n }\n el = el.parentElement;\n }\n return null;\n}\n\n/**\n * 计算 `<w:tr>` 在其父 `<w:tbl>` 的直属 `<w:tr>` 子元素序列中的 0 基行号。\n * 只计直属子元素(不误抓嵌套表的 tr)。\n */\nfunction rowIndexOf(tr: Element, tbl: Element): number {\n let idx = 0;\n const children = tbl.childNodes;\n for (let i = 0; i < children.length; i++) {\n const c = children[i] as Element;\n if (c.nodeType !== 1 || c.namespaceURI !== W_NS || c.localName !== 'tr') continue;\n if (c === tr) return idx;\n idx++;\n }\n return -1; // 理论上不可达\n}\n\n/**\n * 计算 `<w:tc>` 在其父 `<w:tr>` 的直属 `<w:tc>` 子元素序列中的 0 基列号(物理序号)。\n *\n * 注:此为物理列号,不做 gridSpan / vMerge 的逻辑列号校正——含合并单元格时物理序号\n * 与逻辑列号可能不同。v1 物理序号已满足\"区分表格内外编号序列\"的主场景。\n */\nfunction colIndexOf(tc: Element, tr: Element): number {\n let idx = 0;\n const children = tr.childNodes;\n for (let i = 0; i < children.length; i++) {\n const c = children[i] as Element;\n if (c.nodeType !== 1 || c.namespaceURI !== W_NS || c.localName !== 'tc') continue;\n if (c === tc) return idx;\n idx++;\n }\n return -1; // 理论上不可达\n}\n\n/** 段内是否含 revision 标记(Tier 1:只看 w:ins / w:del) */\nfunction detectHasRevisions(p: Element): boolean {\n return (\n p.getElementsByTagNameNS(W_NS, 'ins').length > 0 ||\n p.getElementsByTagNameNS(W_NS, 'del').length > 0\n );\n}\n\n/** 段内是否含 comment 标记 */\nfunction detectHasComments(p: Element): boolean {\n return (\n p.getElementsByTagNameNS(W_NS, 'commentRangeStart').length > 0 ||\n p.getElementsByTagNameNS(W_NS, 'commentRangeEnd').length > 0 ||\n p.getElementsByTagNameNS(W_NS, 'commentReference').length > 0\n );\n}\n\n/** 段内是否含 bookmark 标记 */\nfunction detectHasBookmarks(p: Element): boolean {\n return (\n p.getElementsByTagNameNS(W_NS, 'bookmarkStart').length > 0 ||\n p.getElementsByTagNameNS(W_NS, 'bookmarkEnd').length > 0\n );\n}\n\n/**\n * 段落子树是否含 OMML 公式对象(`<m:oMath>`)。\n * 覆盖块级(oMathPara > oMath)和内联(段内裸 oMath、run/hyperlink 内嵌 oMath)两种情形。\n * `getElementsByTagNameNS` 递归子树,天然覆盖所有深度。\n */\nfunction detectHasMath(p: Element): boolean {\n return p.getElementsByTagNameNS(MATH_NS, 'oMath').length > 0;\n}\n\n// ─────────── 分页 ───────────\n\ninterface PaginationOut<T> {\n slice: T[];\n meta: ListResult['pagination'];\n}\n\nfunction paginate<T extends { el: Element }>(\n items: T[],\n pag: ListParagraphsPagination,\n): PaginationOut<T> {\n const total = items.length;\n\n if (pag.mode === 'all') {\n return {\n slice: items,\n meta: {\n mode: 'all',\n pageIndex: 0,\n totalPages: total === 0 ? 0 : 1,\n totalParagraphs: total,\n hasNext: false,\n },\n };\n }\n\n if (pag.mode === 'byCount') {\n validatePositiveInt('pageSize', pag.pageSize);\n validateNonNegativeInt('pageIndex', pag.pageIndex);\n const totalPages = total === 0 ? 0 : Math.ceil(total / pag.pageSize);\n const start = pag.pageSize * pag.pageIndex;\n const slice = items.slice(start, start + pag.pageSize);\n return {\n slice,\n meta: {\n mode: 'byCount',\n pageIndex: pag.pageIndex,\n totalPages,\n totalParagraphs: total,\n hasNext: pag.pageIndex < totalPages - 1,\n },\n };\n }\n\n // byChar(docs/03 §9.3):按段落文本长度打包成页\n // 1. 累加当前段 text.length 到 buffer\n // 2. buf == 0 且 cur.length > charPerPage → 单段成页(保证完整)\n // 3. buf + next.length > charPerPage → 当前页结束,next 进下一页\n // 4. buf + next.length <= charPerPage → next 追加进当前页\n validatePositiveInt('charPerPage', pag.charPerPage);\n validateNonNegativeInt('pageIndex', pag.pageIndex);\n const pages: T[][] = [];\n let cur: T[] = [];\n let buf = 0;\n for (const item of items) {\n const len = getParagraphText(item.el).length;\n if (buf === 0 && len > pag.charPerPage) {\n // 规则 2:单段成页\n pages.push([item]);\n cur = [];\n buf = 0;\n continue;\n }\n if (buf + len > pag.charPerPage) {\n // 规则 3:当前页结束\n if (cur.length > 0) pages.push(cur);\n cur = [item];\n buf = len;\n } else {\n // 规则 4:追加到当前页\n cur.push(item);\n buf += len;\n }\n }\n if (cur.length > 0) pages.push(cur);\n const totalPages = pages.length;\n const slice = pages[pag.pageIndex] ?? [];\n return {\n slice,\n meta: {\n mode: 'byChar',\n pageIndex: pag.pageIndex,\n totalPages,\n totalParagraphs: total,\n hasNext: pag.pageIndex < totalPages - 1,\n },\n };\n}\n\nfunction validatePositiveInt(name: string, v: number): void {\n if (!Number.isInteger(v) || v < 1) {\n throw new Error(`listParagraphs: ${name} must be a positive integer, got: ${v}`);\n }\n}\n\nfunction validateNonNegativeInt(name: string, v: number): void {\n if (!Number.isInteger(v) || v < 0) {\n throw new Error(`listParagraphs: ${name} must be a non-negative integer, got: ${v}`);\n }\n}\n\n// ─────────── 单段查询(#25)───────────\n\n/**\n * 按 paraId 精确取段。走 paraIdx → O(1) 命中后反查 story。不存在或所属 story 识别\n * 不出(理论上只在 pkg 与 indices 脱钩时发生)返回 null。\n *\n * 总是返回 full 文本(无 textFormat/preview 选项,与 docs/03.1 §2 的签名一致)。\n */\nexport function getParagraphByParaId(\n pkg: Package,\n indices: PackageIndices,\n paraId: string,\n): ParagraphInfo | null {\n const el = indices.paraIdx.get(paraId.toUpperCase());\n if (!el) return null;\n const story = findStoryFor(pkg, el);\n if (!story) return null;\n return buildParagraphInfo(story.id, el, 'full');\n}\n\n// ─────────── ParagraphInfo 构建 ───────────\n\n/**\n * 从单个 `<w:p>` Element 构建 ParagraphInfo。\n *\n * 在 listParagraphs 内部给每个命中段落调用;**也供 `getParagraph`(#25)复用** ——\n * 它通过 paraIdx 拿到 Element 后,用 `findStoryFor` 找到 story,再调这里组装。\n */\nexport function buildParagraphInfo(\n story: StoryId,\n p: Element,\n textFormat: ListParagraphsTextFormat = 'full',\n includeFormat = false,\n runTextOpts?: RunTextOptions,\n): ParagraphInfo {\n return buildInfo(story, p, textFormat, includeFormat, runTextOpts);\n}\n\nfunction buildInfo(\n story: StoryId,\n p: Element,\n textFormat: ListParagraphsTextFormat,\n includeFormat: boolean,\n runTextOpts?: RunTextOptions,\n tblReverseIdx?: Map<Element, string>,\n): ParagraphInfo {\n const paraIdRaw = p.getAttributeNS(W14_NS, 'paraId') ?? '';\n // 与 indices.ts 的 paraIdx 一致,对外用大写(indices.ts:116 也做了 toUpperCase)\n const paraId = paraIdRaw.toUpperCase();\n\n const fullText = getParagraphText(p, runTextOpts);\n const truncated = textFormat === 'preview' && fullText.length > PREVIEW_CHAR_LIMIT;\n const text = truncated ? fullText.slice(0, PREVIEW_CHAR_LIMIT) : fullText;\n\n const styleId = getStyleId(p);\n const hasRevisions = detectHasRevisions(p);\n const hasComments = detectHasComments(p);\n const hasBookmarks = detectHasBookmarks(p);\n const hasMath = detectHasMath(p);\n\n const info: ParagraphInfo = { paraId, story, text };\n if (truncated) info.truncated = true;\n if (styleId !== undefined) info.styleId = styleId;\n if (hasRevisions) info.hasRevisions = true;\n if (hasComments) info.hasComments = true;\n if (hasBookmarks) info.hasBookmarks = true;\n if (hasMath) info.hasMath = true;\n\n if (includeFormat) {\n const pPr = firstDirectChild(p, W_NS, 'pPr');\n let fmt = pPr ? parseParagraphFormat(pPr) : undefined;\n // hasManualPageBreak 来源是 run 而非 pPr,单独检测后合并\n if (detectManualPageBreak(p)) {\n fmt = { ...(fmt ?? {}), hasManualPageBreak: true };\n }\n // firstRunColor 来源是首个直属 run 的 rPr(task-20260615-redhead-fingerprint),单独检测后合并\n const runColor = detectFirstRunColor(p);\n if (runColor !== undefined) {\n fmt = { ...(fmt ?? {}), firstRunColor: runColor };\n }\n if (fmt) info.format = fmt;\n }\n\n // includeTableInfo=true 时:一次祖先遍历拿 tc/tr/tbl,反查 tableId,计算 row/col\n if (tblReverseIdx !== undefined) {\n const ancestors = getTableCellAncestors(p);\n if (ancestors !== null) {\n const { tc, tr, tbl } = ancestors;\n const tableId = tblReverseIdx.get(tbl);\n if (tableId !== undefined) {\n const row = rowIndexOf(tr, tbl);\n const col = colIndexOf(tc, tr);\n info.table = { tableId, row, col };\n }\n }\n }\n\n return info;\n}\n\n// ─────────── 段落直接格式解析(#182)───────────\n\n/**\n * 从 `<w:pPr>` Element 解析段落直接格式属性。\n *\n * 只读取 pPr 上**直接写明**的值;继承自样式或文档默认值的属性不在此处理。\n * 若 pPr 中没有任何已知格式元素,返回 undefined(调用方可跳过 format 字段赋值)。\n */\nfunction parseParagraphFormat(pPr: Element): ParagraphFormat | undefined {\n const fmt: ParagraphFormat = {};\n let hasAny = false;\n\n // ── 对齐 w:jc ──\n const jc = firstDirectChild(pPr, W_NS, 'jc');\n if (jc) {\n const val = jc.getAttributeNS(W_NS, 'val');\n if (\n val === 'left' ||\n val === 'center' ||\n val === 'right' ||\n val === 'both' ||\n val === 'distribute'\n ) {\n fmt.alignment = val;\n hasAny = true;\n }\n }\n\n // ── 缩进 w:ind ──\n const ind = firstDirectChild(pPr, W_NS, 'ind');\n if (ind) {\n const left = numAttrTwips(ind, 'left');\n const right = numAttrTwips(ind, 'right');\n const firstLineRaw = numAttrTwips(ind, 'firstLine');\n const hangingRaw = numAttrTwips(ind, 'hanging');\n\n // firstLine 和 hanging 互斥,同时出现时 OOXML 规范以 firstLine 优先\n const firstLine =\n firstLineRaw !== undefined\n ? firstLineRaw\n : hangingRaw !== undefined\n ? -hangingRaw\n : undefined;\n\n if (left !== undefined || right !== undefined || firstLine !== undefined) {\n fmt.indent = {};\n if (left !== undefined) fmt.indent.left = left;\n if (right !== undefined) fmt.indent.right = right;\n if (firstLine !== undefined) fmt.indent.firstLine = firstLine;\n hasAny = true;\n }\n }\n\n // ── 段间距 w:spacing ──\n const spacing = firstDirectChild(pPr, W_NS, 'spacing');\n if (spacing) {\n const before = numAttrTwips(spacing, 'before');\n const after = numAttrTwips(spacing, 'after');\n const line = numAttrTwips(spacing, 'line');\n const lineRuleRaw = spacing.getAttributeNS(W_NS, 'lineRule');\n const lineRule =\n lineRuleRaw === 'auto' || lineRuleRaw === 'exact' || lineRuleRaw === 'atLeast'\n ? lineRuleRaw\n : undefined;\n\n if (\n before !== undefined ||\n after !== undefined ||\n line !== undefined ||\n lineRule !== undefined\n ) {\n fmt.spacing = {};\n if (before !== undefined) fmt.spacing.before = before;\n if (after !== undefined) fmt.spacing.after = after;\n if (line !== undefined) fmt.spacing.line = line;\n if (lineRule !== undefined) fmt.spacing.lineRule = lineRule;\n hasAny = true;\n }\n }\n\n // ── 段落级默认字体大小 w:pPr/w:rPr/w:sz ──\n const pRpr = firstDirectChild(pPr, W_NS, 'rPr');\n if (pRpr) {\n const sz = firstDirectChild(pRpr, W_NS, 'sz');\n if (sz) {\n const szVal = numAttrTwips(sz, 'val'); // w:sz 单位是半磅\n if (szVal !== undefined) {\n fmt.fontSize = szVal / 2; // 转为磅\n hasAny = true;\n }\n }\n\n // ── 段落级默认粗体 w:pPr/w:rPr/w:b ──\n const b = firstDirectChild(pRpr, W_NS, 'b');\n if (b) {\n const bVal = b.getAttributeNS(W_NS, 'val');\n // 无 val 属性或 val=\"1\"/\"true\" → bold; val=\"0\"/\"false\" → 显式关闭\n fmt.bold = bVal !== '0' && bVal !== 'false';\n hasAny = true;\n }\n\n // ── 段落级默认颜色 w:pPr/w:rPr/w:color(task-20260615-redhead-fingerprint)──\n const color = firstDirectChild(pRpr, W_NS, 'color');\n if (color) {\n const cVal = color.getAttributeNS(W_NS, 'val');\n if (cVal) {\n fmt.color = cVal;\n hasAny = true;\n }\n }\n }\n\n // ── 大纲级别 w:outlineLvl ──\n const outlineLvl = firstDirectChild(pPr, W_NS, 'outlineLvl');\n if (outlineLvl) {\n const val = numAttrTwips(outlineLvl, 'val');\n if (val !== undefined && val >= 0 && val <= 9) {\n fmt.outlineLevel = val;\n hasAny = true;\n }\n }\n\n // ── 编号属性 w:numPr ──\n const numPr = firstDirectChild(pPr, W_NS, 'numPr');\n if (numPr) {\n const numId = firstDirectChild(numPr, W_NS, 'numId');\n // numId@w:val=\"0\" 表示移除继承来的编号,不算\"有编号\"\n const numIdVal = numId?.getAttributeNS(W_NS, 'val');\n if (numIdVal !== undefined && numIdVal !== '0') {\n fmt.hasNumbering = true;\n hasAny = true;\n }\n }\n\n // ── 强制分页 w:pageBreakBefore(#211)──\n // 存在 = true;带 val=\"0\"/\"false\" = 显式关闭\n const pbb = firstDirectChild(pPr, W_NS, 'pageBreakBefore');\n if (pbb) {\n const v = pbb.getAttributeNS(W_NS, 'val');\n fmt.pageBreakBefore = v !== '0' && v !== 'false';\n hasAny = true;\n }\n\n // ── 节属性 w:sectPr(#211)──\n // 存在即视为该段承载节边界\n if (firstDirectChild(pPr, W_NS, 'sectPr')) {\n fmt.hasSectionBreak = true;\n hasAny = true;\n }\n\n return hasAny ? fmt : undefined;\n}\n\n/**\n * 检测段落内是否含手工分页符(`<w:r>/<w:br w:type=\"page\"/>`,#211)。\n *\n * 遍历段落直属 `<w:r>` 的 `<w:br>` 子元素,找 type='page' 的即返回 true。\n * 段属性外的 ins/del 包装内 run 暂不深入(保守:v1 只看直属 run,与 getRunText 行为对齐)。\n */\nfunction detectManualPageBreak(p: Element): boolean {\n const runs = p.getElementsByTagNameNS(W_NS, 'r');\n for (let i = 0; i < runs.length; i++) {\n const run = runs[i];\n if (!run || run.parentNode !== p) continue; // 仅直属 run\n const brs = run.getElementsByTagNameNS(W_NS, 'br');\n for (let j = 0; j < brs.length; j++) {\n const br = brs[j];\n if (!br) continue;\n const type = br.getAttributeNS(W_NS, 'type');\n if (type === 'page') return true;\n }\n }\n return false;\n}\n\n/**\n * 读取段落**首个直属 run**(`<w:p>/<w:r>`)的字体颜色(`w:rPr/w:color@w:val`)。\n * task-20260615-redhead-fingerprint:红头/标题等视觉显著段的颜色常在 run 级而非段标级。\n *\n * 只看首个直属 run(`run.parentNode === p`),与 getParagraphText / detectManualPageBreak 的\n * \"仅直属 run\"语义一致——不下钻表格/文本框/超链接内的 run。首 run 无 color 则返回 undefined。\n */\nfunction detectFirstRunColor(p: Element): string | undefined {\n const runs = p.getElementsByTagNameNS(W_NS, 'r');\n for (let i = 0; i < runs.length; i++) {\n const run = runs[i];\n if (!run || run.parentNode !== p) continue; // 仅直属 run\n const rPr = firstDirectChild(run, W_NS, 'rPr');\n if (!rPr) return undefined; // 首个直属 run 无 rPr → 无 run 级颜色\n const color = firstDirectChild(rPr, W_NS, 'color');\n if (!color) return undefined;\n const val = color.getAttributeNS(W_NS, 'val');\n return val ?? undefined;\n }\n return undefined;\n}\n\n/** 读取 Element 上指定 w: 属性并解析为数字,不是有效整数则返回 undefined。 */\nfunction numAttrTwips(el: Element, local: string): number | undefined {\n const raw = el.getAttributeNS(W_NS, local);\n if (raw === null) return undefined;\n const n = parseInt(raw, 10);\n return Number.isFinite(n) ? n : undefined;\n}\n","/**\n * #190 占位图共享资源 —— PNG 字节 + 注入帮助。\n *\n * 设计:\n * - 全包共享一份 `word/media/placeholder.png`(200x150 灰度,内含中部 X 标记)\n * - 每个承载 drawing 的 part(`word/document.xml` / `word/header*.xml` 等)各自的\n * `.rels` 里**最多注入一条** image relationship → placeholder.png,后续替换复用\n * - [Content_Types].xml 首次时补 `<Default Extension=\"png\" ContentType=\"image/png\"/>`\n * (已有相同 Default 或同名 Override 时跳过)\n *\n * 视觉表达:PNG 内是灰底 + 边框 + 中部 X 标记(国际通用\"占位/图像缺失\"图标语义)。\n * 中文\"[图片占位]\"标注由替换 executor 写入 `wp:docPr/@descr`(screen reader / hover\n * tooltip 可见),避免在 PNG 内手工渲染中文字形。\n *\n * 字节来源:`scripts/gen-placeholder-png.mjs` 一次性生成,baked 进 placeholder-image-bytes.ts。\n */\nimport { parseXmlDocument } from 'slimdom';\nimport type { Package } from '../../pack/index.js';\nimport {\n createBinaryPart,\n createXmlPart,\n deletePart,\n getPart,\n hasPart,\n isBinaryPart,\n isXmlPart,\n listParts,\n markDirty,\n setPart,\n} from '../../pack/index.js';\nimport { PLACEHOLDER_PNG_BYTES } from './placeholder-image-bytes.js';\n\n// ─────────── 常量 ───────────\n\n/** 共享占位图在包内的路径 */\nexport const PLACEHOLDER_MEDIA_PATH = 'word/media/placeholder.png';\n/** PNG MIME content type */\nexport const PNG_CONTENT_TYPE = 'image/png';\n/** OOXML image relationship type(BLIP r:embed 的解析目标)*/\nexport const IMAGE_RELATIONSHIP_TYPE =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';\n\n/** 中文标注文字 —— 由 executor 写到 wp:docPr/@descr / @title */\nexport const PLACEHOLDER_DESCR = '[图片占位]';\n\nconst CONTENT_TYPES_PATH = '[Content_Types].xml';\nconst CONTENT_TYPES_NS = 'http://schemas.openxmlformats.org/package/2006/content-types';\nconst RELS_NS = 'http://schemas.openxmlformats.org/package/2006/relationships';\n\n// ─────────── 公开 API ───────────\n\n/**\n * 确保 `word/media/placeholder.png` 存在;不存在时创建。\n *\n * 幂等:多次调用安全,首次创建后续 short-circuit。\n *\n * @returns 是否本次刚创建(供调用方决定是否标记 [Content_Types] 等 dirty)\n */\nexport function ensurePlaceholderMediaPart(pkg: Package): boolean {\n if (hasPart(pkg, PLACEHOLDER_MEDIA_PATH)) return false;\n const part = createBinaryPart({\n path: PLACEHOLDER_MEDIA_PATH,\n contentType: PNG_CONTENT_TYPE,\n bytes: PLACEHOLDER_PNG_BYTES,\n // 不传 originalBytes 让其默认 = bytes;但显式 markDirty 让 toZip 触发实际写入\n });\n setPart(pkg, part);\n // createBinaryPart 默认 _dirty=false(装载即终态语义);新建场景必须 markDirty 才会被 toZip 序列化\n markDirty(part);\n return true;\n}\n\n/**\n * 确保 [Content_Types].xml 中 PNG 类型可解析。\n *\n * 优先策略:补 `<Default Extension=\"png\" ContentType=\"image/png\"/>`。\n * - 若已有同 Extension 的 Default(无论 ContentType 是否一致)→ 不动\n * - 若有 `<Override PartName=\"/word/media/placeholder.png\" .../>` → 也算覆盖,不补 Default\n *\n * @returns 是否本次刚补\n */\nexport function ensurePngContentType(pkg: Package): boolean {\n const ctPart = getPart(pkg, CONTENT_TYPES_PATH);\n if (!ctPart || !isXmlPart(ctPart) || !ctPart.xmlDom) return false;\n\n const doc = ctPart.xmlDom;\n\n // 已有 Default Extension=\"png\" → skip\n const defaults = doc.getElementsByTagNameNS(CONTENT_TYPES_NS, 'Default');\n for (let i = 0; i < defaults.length; i++) {\n if (defaults[i]!.getAttribute('Extension')?.toLowerCase() === 'png') return false;\n }\n\n // 已有 Override 指向 placeholder.png → skip\n const overrides = doc.getElementsByTagNameNS(CONTENT_TYPES_NS, 'Override');\n for (let i = 0; i < overrides.length; i++) {\n if (overrides[i]!.getAttribute('PartName') === `/${PLACEHOLDER_MEDIA_PATH}`) return false;\n }\n\n // 补 Default\n const def = doc.createElementNS(CONTENT_TYPES_NS, 'Default');\n def.setAttribute('Extension', 'png');\n def.setAttribute('ContentType', PNG_CONTENT_TYPE);\n doc.documentElement.appendChild(def);\n markDirty(ctPart);\n return true;\n}\n\n/**\n * 确保指定 rels part 内有一条 image relationship 指向 placeholder.png;若已存在直接返回其 rId。\n *\n * @param pkg 包\n * @param relsPartPath 目标 rels part 路径,如 'word/_rels/document.xml.rels' / 'word/_rels/header1.xml.rels'\n * 不存在时会创建空 rels(场景:某 header part 原本没有 .rels)\n * @returns 复用或新分配的 rId(形如 'rId7')\n */\nexport function ensurePlaceholderRelInPart(pkg: Package, relsPartPath: string): string {\n let relsPart = getPart(pkg, relsPartPath);\n if (!relsPart) {\n // 新建空 rels:某些 header / footer 原本无图,装载后第一次注入图片需要建 rels\n const empty = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\\n<Relationships xmlns=\"${RELS_NS}\"/>`;\n const dom = parseXmlDocument(empty) as unknown as Document;\n relsPart = createXmlPart({\n path: relsPartPath,\n contentType: 'application/vnd.openxmlformats-package.relationships+xml',\n xmlDom: dom,\n // 不传 originalBytes → 默认 dirty=true(新建)\n });\n setPart(pkg, relsPart);\n }\n if (!isXmlPart(relsPart) || !relsPart.xmlDom) {\n throw new Error(`ensurePlaceholderRelInPart: '${relsPartPath}' is not an XML part`);\n }\n\n const doc = relsPart.xmlDom;\n const placeholderTarget = computeRelTarget(relsPartPath, PLACEHOLDER_MEDIA_PATH);\n\n // 已有指向 placeholder 的 image rel → 复用\n const rels = doc.getElementsByTagNameNS(RELS_NS, 'Relationship');\n let maxRId = 0;\n for (let i = 0; i < rels.length; i++) {\n const r = rels[i]!;\n const id = r.getAttribute('Id') ?? '';\n const m = /^rId(\\d+)$/.exec(id);\n if (m) {\n const n = parseInt(m[1]!, 10);\n if (n > maxRId) maxRId = n;\n }\n if (\n r.getAttribute('Type') === IMAGE_RELATIONSHIP_TYPE &&\n normalizeTarget(r.getAttribute('Target') ?? '') === placeholderTarget\n ) {\n return id;\n }\n }\n\n // 新增 rel\n const rId = `rId${maxRId + 1}`;\n const rel = doc.createElementNS(RELS_NS, 'Relationship');\n rel.setAttribute('Id', rId);\n rel.setAttribute('Type', IMAGE_RELATIONSHIP_TYPE);\n rel.setAttribute('Target', placeholderTarget);\n doc.documentElement.appendChild(rel);\n markDirty(relsPart);\n return rId;\n}\n\n/**\n * 把 .rels 文件内 `<Relationship>` 的 Target(相对路径)解析为包内绝对 part 路径。\n *\n * 规则:相对于宿主 part 的目录解析。\n * - 'word/_rels/document.xml.rels' 内 Target='media/x.png' → 'word/media/x.png'\n * - Target='/word/media/x.png'(以 '/' 开头的\"包根绝对路径\")→ 'word/media/x.png'\n * - 顶层 '_rels/.rels' base 为包根\n */\nexport function resolveRelTarget(relsPartPath: string, target: string): string {\n let t = target.trim();\n if (t.startsWith('/')) return t.slice(1);\n while (t.startsWith('./')) t = t.slice(2);\n const base = hostDirOfRelsPart(relsPartPath);\n if (base === '') return t;\n // 处理 '../' 回溯(尾随段累计)\n let pathParts = base.split('/').filter(Boolean);\n while (t.startsWith('../')) {\n pathParts = pathParts.slice(0, -1);\n t = t.slice(3);\n }\n return pathParts.length === 0 ? t : `${pathParts.join('/')}/${t}`;\n}\n\n/**\n * 统计 media part 路径在全包所有 .rels 中的引用数。\n *\n * 用于孤儿 media 清理:执行器删除一条 rels 条目后,该 Relationship.Target 对应的\n * media path 可能仍被其它 .rels 引用(同图被多 part / 多 drawing 共享场景),\n * 仅在全包归零时才可真正删除 binary part。\n */\nexport function countMediaReferences(pkg: Package, mediaPath: string): number {\n let count = 0;\n for (const part of listParts(pkg)) {\n if (!isXmlPart(part) || !part.xmlDom) continue;\n if (!part.path.endsWith('.rels')) continue;\n const rels = part.xmlDom.getElementsByTagNameNS(RELS_NS, 'Relationship');\n for (let i = 0; i < rels.length; i++) {\n const target = rels[i]!.getAttribute('Target');\n if (!target) continue;\n if (resolveRelTarget(part.path, target) === mediaPath) count++;\n }\n }\n return count;\n}\n\n/**\n * 把候选孤儿 media 路径过一遍,只在全包真正归零时才 deletePart。\n *\n * @returns 实际删除的 path 列表(便于日志 / 测试断言)\n */\nexport function sweepOrphanMediaParts(pkg: Package, candidates: Iterable<string>): string[] {\n const removed: string[] = [];\n for (const path of candidates) {\n const part = getPart(pkg, path);\n if (!part) continue;\n if (!isBinaryPart(part)) continue;\n if (countMediaReferences(pkg, path) > 0) continue;\n deletePart(pkg, path);\n removed.push(path);\n }\n return removed;\n}\n\n// ─────────── 内部 ───────────\n\n/**\n * 计算 rels 文件里 Relationship/@Target 应写的相对路径。\n *\n * OOXML rel Target 默认相对于宿主 part 的目录解析:\n * - 'word/document.xml' 的 rels → base 'word/'\n * - 'word/_rels/document.xml.rels' 内 Target='media/x.png' → 'word/media/x.png'\n *\n * 我们的 placeholder 在 'word/media/placeholder.png',对所有 'word/*.xml.rels' 来说\n * 都是 'media/placeholder.png'。对顶层 '_rels/.rels'(基 = 包根)需要 'word/media/placeholder.png'\n * —— 但 #190 的 drawing 不可能在顶层 rels 里出现,只保留兼容。\n */\nfunction computeRelTarget(relsPartPath: string, targetPartPath: string): string {\n // 把 rels part 路径转成 host part 路径,再取目录做 base\n // 'word/_rels/document.xml.rels' → host = 'word/document.xml' → base = 'word/'\n // '_rels/.rels' → host = '.rels' → base = ''\n const hostDir = hostDirOfRelsPart(relsPartPath);\n if (hostDir === '') return targetPartPath;\n if (targetPartPath.startsWith(`${hostDir}/`)) {\n return targetPartPath.slice(hostDir.length + 1);\n }\n // base 与目标不同前缀(罕见),退化为以 '../' 回溯。这里 #190 实际只走 word/ 前缀,\n // 简化处理:回 '/' 绝对路径形态(OOXML 允许)。\n return `/${targetPartPath}`;\n}\n\nfunction hostDirOfRelsPart(relsPath: string): string {\n // 找到 '/_rels/' 段:其前部 = host 所在目录\n const idx = relsPath.lastIndexOf('/_rels/');\n if (idx < 0) {\n // 如 '_rels/.rels' 形态:host 在包根\n return '';\n }\n return relsPath.slice(0, idx);\n}\n\n/** 标准化 Target 路径用于比较(去开头 './',统一斜杠,去重复斜杠)*/\nfunction normalizeTarget(t: string): string {\n let s = t.trim();\n while (s.startsWith('./')) s = s.slice(2);\n return s.replace(/\\\\/g, '/');\n}\n","/**\n * Section(节)查询(#211 / docs/03.1 §2)。\n *\n * 节边界由\"承载 `<w:sectPr>` 的段\"标定:\n * - 主 body 的最后一个 `<w:sectPr>` 是 body 直接子元素(不属于任何段),是最后一节\n * - 其余节的 sectPr 嵌在 `<w:p>/<w:pPr>/<w:sectPr>`,该段是节的最后一段\n *\n * 算法:遍历 body 直属节点(doc-order),收集 `<w:p>`,遇含 sectPr 的段闭合一节\n * (lastParaId = 该段 paraId);body 末尾的散 sectPr 闭合最后一节。\n *\n * 节信息字段(消费方主要诉求来自 skeleton #195 封面检测):\n * - sectionIndex / firstParaId / lastParaId:边界\n * - type:'nextPage' / 'continuous' / 'evenPage' / 'oddPage' / 'nextColumn'(缺省 = nextPage,OOXML 默认)\n * - headerRefs / footerRefs:resolve 到 partName,跨节比对页眉页脚是否相同\n * - titlePage:'首页不同' 设置(封面强信号)\n */\nimport type { Package } from '../pack/index.js';\nimport { listParts } from '../pack/index.js';\nimport { W_NS, W14_NS, R_NS } from './namespaces.js';\nimport { resolveRelTarget } from './plan-executors/placeholder-image.js';\nimport { listStories } from './stories.js';\n\nexport type SectionType = 'nextPage' | 'continuous' | 'evenPage' | 'oddPage' | 'nextColumn';\nexport type HeaderFooterType = 'default' | 'first' | 'even';\n\nexport interface HeaderFooterRef {\n type: HeaderFooterType;\n /** rels 目标 part 路径(如 'word/header1.xml') */\n partName: string;\n}\n\nexport interface SectionInfo {\n sectionIndex: number;\n firstParaId: string;\n /** 节末段的 paraId(承载 sectPr 的段;最后一节为 body 末尾散 sectPr 时回退到 body 最后段)*/\n lastParaId: string;\n type?: SectionType;\n headerRefs?: HeaderFooterRef[];\n footerRefs?: HeaderFooterRef[];\n /** w:sectPr/w:titlePg 存在 = 首页不同(封面强信号)*/\n titlePage?: boolean;\n}\n\nexport function listSections(pkg: Package): SectionInfo[] {\n const stories = listStories(pkg, { kinds: ['body'] });\n if (stories.length === 0) return [];\n const body = stories[0]!.root;\n const partPath = stories[0]!.partPath;\n const relsPath = relsPathFor(partPath);\n\n const out: SectionInfo[] = [];\n let sectionFirstParaId: string | undefined;\n let lastParaIdInBody: string | undefined;\n let sectionIndex = 0;\n\n // 遍历 body 直属子节点\n for (let i = 0; i < body.childNodes.length; i++) {\n const node = body.childNodes[i];\n if (!node || node.nodeType !== 1) continue;\n const el = node as Element;\n if (el.namespaceURI !== W_NS) continue;\n\n if (el.localName === 'p') {\n const paraId = el.getAttributeNS(W14_NS, 'paraId')?.toUpperCase() ?? '';\n if (paraId.length === 0) continue;\n if (sectionFirstParaId === undefined) sectionFirstParaId = paraId;\n lastParaIdInBody = paraId;\n\n // 该段是否承载 sectPr?\n const pPr = firstDirectChild(el, W_NS, 'pPr');\n const inlineSectPr = pPr ? firstDirectChild(pPr, W_NS, 'sectPr') : null;\n if (inlineSectPr) {\n out.push(\n buildSection(sectionIndex++, sectionFirstParaId, paraId, inlineSectPr, pkg, relsPath),\n );\n sectionFirstParaId = undefined;\n }\n } else if (el.localName === 'sectPr') {\n // body 末尾散 sectPr:最后一节,lastParaId 用 body 最后段\n if (sectionFirstParaId !== undefined && lastParaIdInBody !== undefined) {\n out.push(\n buildSection(sectionIndex++, sectionFirstParaId, lastParaIdInBody, el, pkg, relsPath),\n );\n sectionFirstParaId = undefined;\n }\n }\n // 其他节点(tbl 等)按 doc-order 跳过,不影响节边界\n }\n\n return out;\n}\n\n// ─────────── 内部 ───────────\n\nfunction buildSection(\n sectionIndex: number,\n firstParaId: string,\n lastParaId: string,\n sectPr: Element,\n pkg: Package,\n relsPath: string | undefined,\n): SectionInfo {\n const info: SectionInfo = { sectionIndex, firstParaId, lastParaId };\n\n // type\n const typeEl = firstDirectChild(sectPr, W_NS, 'type');\n if (typeEl) {\n const v = typeEl.getAttributeNS(W_NS, 'val');\n if (\n v === 'nextPage' ||\n v === 'continuous' ||\n v === 'evenPage' ||\n v === 'oddPage' ||\n v === 'nextColumn'\n ) {\n info.type = v;\n }\n }\n\n // header/footer references\n const headerRefs = collectRefs(sectPr, 'headerReference', pkg, relsPath);\n if (headerRefs.length > 0) info.headerRefs = headerRefs;\n const footerRefs = collectRefs(sectPr, 'footerReference', pkg, relsPath);\n if (footerRefs.length > 0) info.footerRefs = footerRefs;\n\n // titlePg\n if (firstDirectChild(sectPr, W_NS, 'titlePg')) info.titlePage = true;\n\n return info;\n}\n\nfunction collectRefs(\n sectPr: Element,\n localName: 'headerReference' | 'footerReference',\n pkg: Package,\n relsPath: string | undefined,\n): HeaderFooterRef[] {\n const refs: HeaderFooterRef[] = [];\n if (!relsPath) return refs;\n const relMap = loadRels(pkg, relsPath);\n if (!relMap) return refs;\n const nodes = sectPr.getElementsByTagNameNS(W_NS, localName);\n for (let i = 0; i < nodes.length; i++) {\n const el = nodes[i];\n if (!el || el.parentNode !== sectPr) continue;\n const typeAttr = el.getAttributeNS(W_NS, 'type');\n const rId = el.getAttributeNS(R_NS, 'id');\n if (!rId) continue;\n const target = relMap.get(rId);\n if (!target) continue;\n const partName = resolveRelTarget(relsPath, target);\n const type: HeaderFooterType =\n typeAttr === 'first' || typeAttr === 'even' ? typeAttr : 'default';\n refs.push({ type, partName });\n }\n return refs;\n}\n\nconst RELS_NS = 'http://schemas.openxmlformats.org/package/2006/relationships';\n\nfunction loadRels(pkg: Package, relsPath: string): Map<string, string> | null {\n for (const part of listParts(pkg)) {\n if (part.path !== relsPath) continue;\n if (!part.xmlDom) return null;\n const map = new Map<string, string>();\n const nodes = part.xmlDom.getElementsByTagNameNS(RELS_NS, 'Relationship');\n for (let i = 0; i < nodes.length; i++) {\n const el = nodes[i];\n if (!el) continue;\n const id = el.getAttribute('Id');\n const target = el.getAttribute('Target');\n if (id && target) map.set(id, target);\n }\n return map;\n }\n return null;\n}\n\nfunction relsPathFor(partPath: string): string | undefined {\n const lastSlash = partPath.lastIndexOf('/');\n const dir = lastSlash >= 0 ? partPath.slice(0, lastSlash) : '';\n const file = partPath.slice(lastSlash + 1);\n return dir.length > 0 ? `${dir}/_rels/${file}.rels` : `_rels/${file}.rels`;\n}\n\nfunction firstDirectChild(el: Element, ns: string, local: string): Element | null {\n for (let i = 0; i < el.childNodes.length; i++) {\n const c = el.childNodes[i];\n if (!c || c.nodeType !== 1) continue;\n const ce = c as Element;\n if (ce.namespaceURI === ns && ce.localName === local) return ce;\n }\n return null;\n}\n","/**\n * insertImageAfter executor —— task-insert-image。\n *\n * 在 anchorParaId 段之后新插一个 <w:p>,段内含 inline 图片 <w:drawing>。\n *\n * 写链(8 步):\n * 1. anchor 定位(paraIdx → PARA_NOT_FOUND)\n * 2. 分配 media 文件名(image1..imageN.ext,无撞)\n * 3. 新建 binary part(createBinaryPart + setPart + markDirty)\n * 4. 加 image rel(rels 扫 max(rIdN)+1)\n * 5. 登记 content-type(ensureImageContentType)\n * 6. 构造 <w:drawing> DOM(inline picture 模板)\n * 7. 包 <w:r> + 新 <w:p>(含 w14:paraId 主动分配)\n * 8. markDirty\n *\n * 跨端:只用 Uint8Array,绝不碰 node fs/Buffer。\n */\nimport { parseXmlDocument } from 'slimdom';\nimport {\n createBinaryPart,\n createXmlPart,\n getPart,\n hasPart,\n isXmlPart,\n listParts,\n markDirty,\n setPart,\n type Package,\n} from '../../pack/index.js';\nimport { generateParaId } from '../para-id.js';\nimport { A_NS, PIC_NS, R_NS, W_NS, W14_NS, WP_NS } from '../namespaces.js';\nimport type { Op } from '../plan.js';\nimport { findStoryFor } from '../stories.js';\nimport { WordError } from '../word-error.js';\nimport type { ExecutorContext } from './executor-context.js';\n\nconst RELS_NS = 'http://schemas.openxmlformats.org/package/2006/relationships';\nconst CONTENT_TYPES_PATH = '[Content_Types].xml';\nconst CONTENT_TYPES_NS = 'http://schemas.openxmlformats.org/package/2006/content-types';\nconst IMAGE_RELATIONSHIP_TYPE =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';\n\n/**\n * contentType → 文件扩展名。\n *\n * 含位图 5 种 + 矢量图 2 种(EMF/WMF)。\n *\n * EMF/WMF 是 Word 原生支持的矢量图格式(粘贴的 Office 图表/示意图、老文档插图常见),\n * 在 OOXML 里和位图一样走 a:blip/@r:embed 引用机制 —— blip 对图片格式一视同仁,\n * 矢量图 part 的扩展名是 .emf/.wmf、content-type 是 image/x-emf/image/x-wmf。\n * typeset 透传源文档已有的 EMF/WMF 图时(getImageBytes 读出原始字节 + contentType,\n * insertImageAfter 重新引用),白名单必须含这两种,否则真实文档(含矢量图)排版崩溃。\n */\nexport const CONTENT_TYPE_TO_EXT: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpeg',\n 'image/gif': 'gif',\n 'image/bmp': 'bmp',\n 'image/tiff': 'tiff',\n 'image/x-emf': 'emf',\n 'image/x-wmf': 'wmf',\n};\n\n// ─────────── executor ───────────\n\nexport function executeInsertImageAfter(\n ctx: ExecutorContext,\n op: Extract<Op, { type: 'insertImageAfter' }>,\n): void {\n if (ctx.options.trackChanges) {\n throw new WordError(\n 'UNSUPPORTED_OOXML_FEATURE',\n 'insertImageAfter with trackChanges=true not yet implemented',\n ctx.docId,\n { opType: 'insertImageAfter' },\n );\n }\n\n // contentType 校验\n const ext = CONTENT_TYPE_TO_EXT[op.contentType];\n if (!ext) {\n throw new WordError(\n 'UNSUPPORTED_OOXML_FEATURE',\n `insertImageAfter: unsupported contentType '${op.contentType}'. Supported: image/png, image/jpeg, image/gif, image/bmp, image/tiff, image/x-emf, image/x-wmf`,\n ctx.docId,\n { opType: 'insertImageAfter', contentType: op.contentType },\n );\n }\n\n // 1. anchor 定位\n const anchor = ctx.indices.paraIdx.get(op.anchorParaId.toUpperCase());\n if (!anchor) {\n throw new WordError(\n 'PARA_NOT_FOUND',\n `paragraph with paraId='${op.anchorParaId}' not found (op: insertImageAfter)`,\n ctx.docId,\n { paraId: op.anchorParaId, opName: 'insertImageAfter' },\n );\n }\n const story = findStoryFor(ctx.pkg, anchor);\n if (!story) {\n throw new WordError(\n 'PACKAGE_CORRUPT',\n `anchor paragraph '${op.anchorParaId}' not found in any story`,\n ctx.docId,\n { paraId: op.anchorParaId, opName: 'insertImageAfter' },\n );\n }\n const hostPartPath = story.partPath;\n const relsPath = relsPathFor(hostPartPath);\n\n // 2. 分配 media 文件名(image1..imageN.ext)\n const mediaPath = allocateMediaPath(ctx.pkg, ext);\n\n // 3. 新建 binary part\n const mediaPart = createBinaryPart({\n path: mediaPath,\n contentType: op.contentType,\n bytes: op.bytes,\n });\n setPart(ctx.pkg, mediaPart);\n markDirty(mediaPart);\n\n // 4. 加 image rel(rels 扫 max(rIdN)+1)\n const rId = addImageRel(ctx.pkg, relsPath, mediaPath);\n\n // 5. 登记 content-type\n ensureImageContentType(ctx.pkg, ext, op.contentType);\n\n // 6. 构造 <w:drawing> DOM\n const hostPart = getPart(ctx.pkg, hostPartPath);\n if (!hostPart || !isXmlPart(hostPart) || !hostPart.xmlDom) {\n throw new WordError(\n 'PACKAGE_CORRUPT',\n `host part '${hostPartPath}' missing or not XML`,\n ctx.docId,\n { opName: 'insertImageAfter' },\n );\n }\n const doc = hostPart.xmlDom;\n\n const docPrId = allocateDocPrId(doc);\n const drawing = buildInlineDrawing(doc, rId, docPrId, op.widthEmu, op.heightEmu);\n\n // 7. 新 <w:p>(含主动分配 w14:paraId)\n const seen = new Set(ctx.indices.paraIdx.keys());\n const newParaId = generateParaId(seen);\n\n const newPara = doc.createElementNS(W_NS, 'w:p');\n newPara.setAttributeNS(W14_NS, 'w14:paraId', newParaId);\n const run = doc.createElementNS(W_NS, 'w:r');\n run.appendChild(drawing);\n newPara.appendChild(run);\n\n // 插到 anchor 之后(insert-before anchor.nextSibling)\n const parent = anchor.parentNode;\n if (!parent) {\n throw new WordError(\n 'PACKAGE_CORRUPT',\n `anchor paragraph '${op.anchorParaId}' has no parentNode`,\n ctx.docId,\n { paraId: op.anchorParaId, opName: 'insertImageAfter' },\n );\n }\n parent.insertBefore(newPara, anchor.nextSibling);\n\n // 同步 paraIdx(保证同一 plan 内后续 op 可查到新段)\n ctx.indices.paraIdx.set(newParaId, newPara);\n\n // 8. markDirty\n markDirty(hostPart);\n const relsPart = getPart(ctx.pkg, relsPath);\n if (relsPart) markDirty(relsPart);\n const ctPart = getPart(ctx.pkg, CONTENT_TYPES_PATH);\n if (ctPart) markDirty(ctPart);\n}\n\n// ─────────── helpers ───────────\n\n/**\n * 分配唯一 media 文件名:扫 word/media/imageN.ext,取最大 N+1。\n * hasPart 二次校验防撞(理论不撞,保险用)。\n */\nexport function allocateMediaPath(pkg: Package, ext: string): string {\n let maxN = 0;\n for (const part of listParts(pkg)) {\n const m = /^word\\/media\\/image(\\d+)\\./i.exec(part.path);\n if (m) {\n const n = parseInt(m[1]!, 10);\n if (n > maxN) maxN = n;\n }\n }\n let candidate = `word/media/image${maxN + 1}.${ext}`;\n // 二次校验防撞(例如 image1.png 已存在但 image1.jpeg 也占了 N=1)\n while (hasPart(pkg, candidate)) {\n maxN++;\n candidate = `word/media/image${maxN + 1}.${ext}`;\n }\n return candidate;\n}\n\n/**\n * 在 relsPartPath 里新增一条 image relationship 指向 mediaPath,返回分配的 rId。\n * 若 rels part 不存在,先创建空 rels。\n */\nexport function addImageRelForHost(pkg: Package, relsPartPath: string, mediaPath: string): string {\n return addImageRel(pkg, relsPartPath, mediaPath);\n}\n\nfunction addImageRel(pkg: Package, relsPartPath: string, mediaPath: string): string {\n let relsPart = getPart(pkg, relsPartPath);\n if (!relsPart) {\n const empty = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\\n<Relationships xmlns=\"${RELS_NS}\"/>`;\n const dom = parseXmlDocument(empty) as unknown as Document;\n relsPart = createXmlPart({\n path: relsPartPath,\n contentType: 'application/vnd.openxmlformats-package.relationships+xml',\n xmlDom: dom,\n });\n setPart(pkg, relsPart);\n }\n if (!isXmlPart(relsPart) || !relsPart.xmlDom) {\n throw new Error(`addImageRel: '${relsPartPath}' is not an XML part`);\n }\n\n const doc = relsPart.xmlDom;\n const rels = doc.getElementsByTagNameNS(RELS_NS, 'Relationship');\n let maxRId = 0;\n for (let i = 0; i < rels.length; i++) {\n const id = rels[i]!.getAttribute('Id') ?? '';\n const m = /^rId(\\d+)$/.exec(id);\n if (m) {\n const n = parseInt(m[1]!, 10);\n if (n > maxRId) maxRId = n;\n }\n }\n\n const rId = `rId${maxRId + 1}`;\n // Target 相对于宿主 part 目录:word/document.xml → word/media/imageN.ext → media/imageN.ext\n const target = computeRelTarget(relsPartPath, mediaPath);\n const rel = doc.createElementNS(RELS_NS, 'Relationship');\n rel.setAttribute('Id', rId);\n rel.setAttribute('Type', IMAGE_RELATIONSHIP_TYPE);\n rel.setAttribute('Target', target);\n doc.documentElement.appendChild(rel);\n markDirty(relsPart);\n return rId;\n}\n\n/**\n * 登记 content-type Default:补 `<Default Extension=ext ContentType=ct/>`。\n * 已有同 Extension 的 Default → 跳过。\n */\nexport function ensureImageContentType(pkg: Package, ext: string, contentType: string): void {\n const ctPart = getPart(pkg, CONTENT_TYPES_PATH);\n if (!ctPart || !isXmlPart(ctPart) || !ctPart.xmlDom) return;\n const doc = ctPart.xmlDom;\n\n const defaults = doc.getElementsByTagNameNS(CONTENT_TYPES_NS, 'Default');\n for (let i = 0; i < defaults.length; i++) {\n if (defaults[i]!.getAttribute('Extension')?.toLowerCase() === ext.toLowerCase()) return;\n }\n\n const def = doc.createElementNS(CONTENT_TYPES_NS, 'Default');\n def.setAttribute('Extension', ext);\n def.setAttribute('ContentType', contentType);\n doc.documentElement.appendChild(def);\n markDirty(ctPart);\n}\n\n/**\n * 扫 document.xmlDom 里所有 wp:docPr/@id,取 max+1。\n */\nfunction allocateDocPrId(doc: Document): number {\n const docPrs = doc.getElementsByTagNameNS(WP_NS, 'docPr');\n let maxId = 0;\n for (let i = 0; i < docPrs.length; i++) {\n const raw = docPrs[i]!.getAttribute('id');\n if (!raw) continue;\n const n = parseInt(raw, 10);\n if (!isNaN(n) && n > maxId) maxId = n;\n }\n return maxId + 1;\n}\n\n/**\n * 构造 inline picture <w:drawing> DOM。\n *\n * 结构:\n * w:drawing\n * wp:inline(distT/B/L/R=0)\n * wp:extent(@cx @cy)\n * wp:docPr(@id @name)\n * wp:cNvGraphicFramePr > a:graphicFrameLocks(noChangeAspect=1)\n * a:graphic > a:graphicData(uri=PIC_NS)\n * pic:pic\n * pic:nvPicPr > pic:cNvPr(@id @name) + pic:cNvPicPr > a:picLocks(noChangeAspect=1)\n * pic:blipFill > a:blip(@r:embed) + a:stretch > a:fillRect\n * pic:spPr > a:xfrm > (a:off x=0 y=0 + a:ext @cx @cy) + a:prstGeom(prst=rect) > a:avLst\n */\nfunction buildInlineDrawing(\n doc: Document,\n rId: string,\n docPrId: number,\n widthEmu: number,\n heightEmu: number,\n): Element {\n const imgName = `image${docPrId}`;\n\n function ns(nsUri: string, local: string): Element {\n // Use prefixed form for non-W_NS elements to match real OOXML output\n const prefix = nsPrefixOf(nsUri);\n return doc.createElementNS(nsUri, prefix ? `${prefix}:${local}` : local);\n }\n\n // w:drawing\n const drawing = doc.createElementNS(W_NS, 'w:drawing');\n\n // wp:inline\n const inline = ns(WP_NS, 'inline');\n inline.setAttribute('distT', '0');\n inline.setAttribute('distB', '0');\n inline.setAttribute('distL', '0');\n inline.setAttribute('distR', '0');\n drawing.appendChild(inline);\n\n // wp:extent\n const extent = ns(WP_NS, 'extent');\n extent.setAttribute('cx', String(widthEmu));\n extent.setAttribute('cy', String(heightEmu));\n inline.appendChild(extent);\n\n // wp:effectExtent\n const effectExtent = ns(WP_NS, 'effectExtent');\n effectExtent.setAttribute('l', '0');\n effectExtent.setAttribute('t', '0');\n effectExtent.setAttribute('r', '0');\n effectExtent.setAttribute('b', '0');\n inline.appendChild(effectExtent);\n\n // wp:docPr\n const docPr = ns(WP_NS, 'docPr');\n docPr.setAttribute('id', String(docPrId));\n docPr.setAttribute('name', imgName);\n inline.appendChild(docPr);\n\n // wp:cNvGraphicFramePr\n const cNvGFP = ns(WP_NS, 'cNvGraphicFramePr');\n const gfLocks = doc.createElementNS(A_NS, 'a:graphicFrameLocks');\n gfLocks.setAttribute('noChangeAspect', '1');\n cNvGFP.appendChild(gfLocks);\n inline.appendChild(cNvGFP);\n\n // a:graphic > a:graphicData\n const graphic = doc.createElementNS(A_NS, 'a:graphic');\n const graphicData = doc.createElementNS(A_NS, 'a:graphicData');\n graphicData.setAttribute('uri', PIC_NS);\n graphic.appendChild(graphicData);\n inline.appendChild(graphic);\n\n // pic:pic\n const pic = doc.createElementNS(PIC_NS, 'pic:pic');\n graphicData.appendChild(pic);\n\n // pic:nvPicPr\n const nvPicPr = doc.createElementNS(PIC_NS, 'pic:nvPicPr');\n pic.appendChild(nvPicPr);\n\n const cNvPr = doc.createElementNS(PIC_NS, 'pic:cNvPr');\n cNvPr.setAttribute('id', String(docPrId));\n cNvPr.setAttribute('name', imgName);\n nvPicPr.appendChild(cNvPr);\n\n const cNvPicPr = doc.createElementNS(PIC_NS, 'pic:cNvPicPr');\n const picLocks = doc.createElementNS(A_NS, 'a:picLocks');\n picLocks.setAttribute('noChangeAspect', '1');\n cNvPicPr.appendChild(picLocks);\n nvPicPr.appendChild(cNvPicPr);\n\n // pic:blipFill\n const blipFill = doc.createElementNS(PIC_NS, 'pic:blipFill');\n pic.appendChild(blipFill);\n\n const blip = doc.createElementNS(A_NS, 'a:blip');\n blip.setAttributeNS(R_NS, 'r:embed', rId);\n blipFill.appendChild(blip);\n\n const stretch = doc.createElementNS(A_NS, 'a:stretch');\n const fillRect = doc.createElementNS(A_NS, 'a:fillRect');\n stretch.appendChild(fillRect);\n blipFill.appendChild(stretch);\n\n // pic:spPr\n const spPr = doc.createElementNS(PIC_NS, 'pic:spPr');\n pic.appendChild(spPr);\n\n const xfrm = doc.createElementNS(A_NS, 'a:xfrm');\n const off = doc.createElementNS(A_NS, 'a:off');\n off.setAttribute('x', '0');\n off.setAttribute('y', '0');\n xfrm.appendChild(off);\n const extEl = doc.createElementNS(A_NS, 'a:ext');\n extEl.setAttribute('cx', String(widthEmu));\n extEl.setAttribute('cy', String(heightEmu));\n xfrm.appendChild(extEl);\n spPr.appendChild(xfrm);\n\n const prstGeom = doc.createElementNS(A_NS, 'a:prstGeom');\n prstGeom.setAttribute('prst', 'rect');\n const avLst = doc.createElementNS(A_NS, 'a:avLst');\n prstGeom.appendChild(avLst);\n spPr.appendChild(prstGeom);\n\n return drawing;\n}\n\n/** namespace URI → prefix map(用于创建有正确前缀的元素,与真实 OOXML 匹配)*/\nfunction nsPrefixOf(nsUri: string): string {\n if (nsUri === WP_NS) return 'wp';\n if (nsUri === A_NS) return 'a';\n if (nsUri === PIC_NS) return 'pic';\n if (nsUri === R_NS) return 'r';\n return '';\n}\n\n/**\n * 把 rels part 路径转成宿主 part 所在目录,用于计算相对 Target。\n * 'word/_rels/document.xml.rels' → base='word'\n */\nfunction computeRelTarget(relsPartPath: string, targetPartPath: string): string {\n const idx = relsPartPath.lastIndexOf('/_rels/');\n if (idx < 0) return targetPartPath;\n const hostDir = relsPartPath.slice(0, idx);\n if (targetPartPath.startsWith(`${hostDir}/`)) {\n return targetPartPath.slice(hostDir.length + 1);\n }\n return `/${targetPartPath}`;\n}\n\n/**\n * 把 part 路径转换为对应 .rels part 路径。\n * 'word/document.xml' → 'word/_rels/document.xml.rels'\n */\nfunction relsPathFor(partPath: string): string {\n const idx = partPath.lastIndexOf('/');\n if (idx < 0) return `_rels/${partPath}.rels`;\n const dir = partPath.slice(0, idx);\n const name = partPath.slice(idx + 1);\n return `${dir}/_rels/${name}.rels`;\n}\n\n// ─────────── getImageBytes(内核侧同步实现,供 kernel.ts 调用)───────────\n\nexport interface ImageBytesResult {\n bytes: Uint8Array;\n contentType: string;\n extentEMU: { cx: number; cy: number };\n}\n\n/**\n * 读取 drawingId 对应 picture 的原始图片字节。\n * drawingId 不存在 / 非 picture / media 找不到 → 返回 null(不抛)。\n */\nexport function getImageBytesSync(\n pkg: Package,\n indices: { drawingIdx: Map<string, Element> },\n drawingId: string,\n): ImageBytesResult | null {\n // 1. drawingId → <w:drawing>\n const dr = indices.drawingIdx.get(drawingId);\n if (!dr) return null;\n\n // 2. <w:drawing> → a:blip/@r:embed\n const blips = dr.getElementsByTagNameNS(A_NS, 'blip');\n if (blips.length === 0) return null;\n const blip = blips[0]!;\n const rId = blip.getAttributeNS(R_NS, 'embed');\n if (!rId) return null;\n\n // 3. <w:drawing> → 宿主 part path\n const story = findStoryFor(pkg, dr);\n if (!story) return null;\n const hostPartPath = story.partPath;\n\n // 4. partPath → rels path\n const relsPath = relsPathFor(hostPartPath);\n\n // 5. rId → media path via rels\n const relsPart = getPart(pkg, relsPath);\n if (!relsPart || !isXmlPart(relsPart) || !relsPart.xmlDom) return null;\n const rels = relsPart.xmlDom.getElementsByTagNameNS(RELS_NS, 'Relationship');\n let mediaPath: string | null = null;\n for (let i = 0; i < rels.length; i++) {\n const r = rels[i]!;\n if (r.getAttribute('Id') === rId) {\n const target = r.getAttribute('Target') ?? '';\n // resolve relative target → absolute part path\n mediaPath = resolveTarget(relsPath, target);\n break;\n }\n }\n if (!mediaPath) return null;\n\n // 6. media path → bytes\n const mediaPart = getPart(pkg, mediaPath);\n if (!mediaPart) return null;\n // isBinaryPart check: has bytes field\n const bytes = (mediaPart as unknown as { bytes?: Uint8Array }).bytes;\n if (!bytes) return null;\n const contentType = mediaPart.contentType;\n\n // extentEMU: from wp:extent inside wp:inline / wp:anchor\n const extentEMU = readDrawingExtent(dr);\n\n return { bytes, contentType, extentEMU };\n}\n\n/** wp:extent @cx/@cy from wp:inline or wp:anchor child */\nfunction readDrawingExtent(drawing: Element): { cx: number; cy: number } {\n for (let i = 0; i < drawing.childNodes.length; i++) {\n const c = drawing.childNodes[i] as Element;\n if (!c || c.nodeType !== 1) continue;\n if (c.namespaceURI !== WP_NS) continue;\n if (c.localName !== 'inline' && c.localName !== 'anchor') continue;\n // find wp:extent child\n for (let j = 0; j < c.childNodes.length; j++) {\n const ext = c.childNodes[j] as Element;\n if (!ext || ext.nodeType !== 1) continue;\n if (ext.namespaceURI === WP_NS && ext.localName === 'extent') {\n const cx = parseInt(ext.getAttribute('cx') ?? '0', 10);\n const cy = parseInt(ext.getAttribute('cy') ?? '0', 10);\n return { cx: isNaN(cx) ? 0 : cx, cy: isNaN(cy) ? 0 : cy };\n }\n }\n }\n return { cx: 0, cy: 0 };\n}\n\n/** Resolve rels Target → absolute package path (mirrors resolveRelTarget in placeholder-image.ts) */\nfunction resolveTarget(relsPartPath: string, target: string): string {\n let t = target.trim();\n if (t.startsWith('/')) return t.slice(1);\n while (t.startsWith('./')) t = t.slice(2);\n const idx = relsPartPath.lastIndexOf('/_rels/');\n if (idx < 0) return t;\n const hostDir = relsPartPath.slice(0, idx);\n // handle '../' traversal\n let parts = hostDir.split('/').filter(Boolean);\n while (t.startsWith('../')) {\n parts = parts.slice(0, -1);\n t = t.slice(3);\n }\n return parts.length === 0 ? t : `${parts.join('/')}/${t}`;\n}\n\n// Re-export for kernel.ts convenience\nexport { relsPathFor as relsPathForImage };\n// Re-export for insert-table-ops.ts(跨文档搬表时算 host rels 路径)。\nexport { relsPathFor as relsPathForHost };\n"],"mappings":"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","names":["snapshot","snapshot","findStoryForEl","snapshot","numAttr","firstDirectChild","firstDirectChild","import_slimdom","firstDirectChild","import_slimdom","RELS_NS","relsPathFor","RELS_NS"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { C as CommentInfo, D as DrawingInfo, H as HeaderFooterRef, x as HeaderFooterType, I as ImageBytesResult, P as PackageIndices, O as ParagraphFormat, b as ParagraphInfo, R as RevisionInfo, j as SectionInfo, V as SectionType, S as StoryId, l as TableCellInfo, T as TableInfo, a1 as W14_NS, a6 as W_NS, a9 as buildIndices, aa as buildParagraphInfo, ay as getImageBytesSync, aj as getStoryParagraphs, ap as listDrawings, as as listSections, at as listStories, av as listTables } from './view-DX2YAG5A.cjs';
|
|
2
|
+
import './types-BVkkdcfh.cjs';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { C as CommentInfo, D as DrawingInfo, H as HeaderFooterRef, x as HeaderFooterType, I as ImageBytesResult, P as PackageIndices, O as ParagraphFormat, b as ParagraphInfo, R as RevisionInfo, j as SectionInfo, V as SectionType, S as StoryId, l as TableCellInfo, T as TableInfo, a1 as W14_NS, a6 as W_NS, a9 as buildIndices, aa as buildParagraphInfo, ay as getImageBytesSync, aj as getStoryParagraphs, ap as listDrawings, as as listSections, at as listStories, av as listTables } from './view-DAkeR0RW.js';
|
|
2
|
+
import './types-BVkkdcfh.js';
|