@stll/anonymize-docx 0.0.1-placeholder.0 → 2.0.2
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/ATTRIBUTION.md +8 -0
- package/LICENSE +202 -0
- package/README.md +146 -2
- package/dist/index.d.mts +239 -0
- package/dist/index.mjs +868 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["WORDPROCESSING_NAMESPACES"],"sources":["../src/types.ts","../src/extract.ts","../src/location.ts","../src/rewrite.ts","../src/coverage.ts","../src/restore.ts","../src/anonymize.ts"],"sourcesContent":["import type {\n NativeCallerDetection,\n NativeOperatorConfig,\n NativeSessionBlockRedactionPlan,\n NativeSessionCallerRedactionPlanOptions,\n} from \"@stll/anonymize\";\n\nexport const DOCX_PART_TYPES = {\n comments: \"comments\",\n endnotes: \"endnotes\",\n footer: \"footer\",\n footnotes: \"footnotes\",\n header: \"header\",\n mainDocument: \"main-document\",\n} as const;\n\nexport type DocxPartType =\n (typeof DOCX_PART_TYPES)[keyof typeof DOCX_PART_TYPES];\n\nexport type DocxPart = {\n type: DocxPartType;\n path: string;\n};\n\ntype DocxBaseBlockLocation = {\n part: DocxPart;\n blockIndex: number;\n xmlPath: readonly number[];\n};\n\nexport type DocxBlockLocation =\n | (DocxBaseBlockLocation & {\n type: \"paragraph\";\n })\n | (DocxBaseBlockLocation & {\n type: \"table-cell-paragraph\";\n tablePath: readonly number[];\n rowPath: readonly number[];\n cellPath: readonly number[];\n })\n | (DocxBaseBlockLocation & {\n type: \"text-box-paragraph\";\n textBoxPath: readonly number[];\n });\n\nexport type DocxInlineContext =\n | {\n type: \"hyperlink\";\n relationshipId: string | null;\n anchor: string | null;\n }\n | {\n type: \"revision\";\n revision: \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\";\n };\n\nexport type DocxTextSegment = {\n start: number;\n end: number;\n source: \"break\" | \"tab\" | \"text\";\n contexts: readonly DocxInlineContext[];\n xmlPath: readonly number[];\n};\n\nexport type DocxTextBlock = {\n text: string;\n location: DocxBlockLocation;\n segments: readonly DocxTextSegment[];\n};\n\nexport type DocxCoverageItem =\n | {\n status: \"extracted\";\n part: DocxPart;\n blockCount: number;\n }\n | {\n status: \"unsupported\";\n path: string;\n contentType: string;\n reason: string;\n };\n\nexport type DocxCoverage = {\n parts: readonly DocxCoverageItem[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxExtraction = {\n contractVersion: 1;\n blocks: readonly DocxTextBlock[];\n coverage: DocxCoverage;\n};\n\nexport type DocxTextReplacement = {\n start: number;\n end: number;\n replacement: string;\n};\n\nexport type DocxBlockRewrite = {\n location: DocxBlockLocation;\n expectedText: string;\n replacements: readonly DocxTextReplacement[];\n};\n\nexport type DocxRewriteResult = {\n document: Uint8Array;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n};\n\nexport const DOCX_COVERAGE_MODES = {\n allowPartial: \"allow-partial\",\n requireFull: \"require-full\",\n} as const;\n\nexport type DocxCoverageMode =\n (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n\nexport type DocxCoveragePolicy =\n | { mode: typeof DOCX_COVERAGE_MODES.requireFull }\n | { mode: typeof DOCX_COVERAGE_MODES.allowPartial };\n\nexport type DocxAnonymizationPolicy = {\n coverage: DocxCoveragePolicy;\n operators?: NativeOperatorConfig;\n};\n\nexport type DocxCallerDetection = NativeCallerDetection;\n\nexport type DocxBlockCallerDetections = {\n location: DocxBlockLocation;\n expectedText: string;\n detections: readonly DocxCallerDetection[];\n};\n\nexport type DocxSessionRedactionPlan = {\n blocks: readonly NativeSessionBlockRedactionPlan[];\n commit: () => void;\n};\n\nexport type DocxAnonymizationSession = {\n sessionId: () => string;\n planTextBatchWithCallerDetections: (\n options: NativeSessionCallerRedactionPlanOptions,\n ) => DocxSessionRedactionPlan;\n};\n\nexport type AnonymizeDocxOptions = {\n document: Uint8Array;\n session: DocxAnonymizationSession;\n expectedSessionId: string;\n policy: DocxAnonymizationPolicy;\n callerDetections?: readonly DocxBlockCallerDetections[];\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxCoverageSummary = {\n extractedPartCount: number;\n unsupportedPartCount: number;\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxWorkflowCoverage =\n | { status: \"full\"; counts: DocxCoverageSummary }\n | { status: \"partial\"; counts: DocxCoverageSummary };\n\nexport type DocxAnonymizationSummary = {\n contractVersion: 1;\n sessionId: string;\n blockCount: number;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n entityCount: number;\n callerDetectionCount: number;\n retainedCallerDetectionCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport type DocxAnonymizationResult = {\n document: Uint8Array;\n summary: DocxAnonymizationSummary;\n};\n\nexport const DOCX_ANONYMIZATION_ERROR_CODES = {\n incompleteCoverage: \"incomplete-coverage\",\n invalidCallerDetections: \"invalid-caller-detections\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxAnonymizationErrorCode =\n (typeof DOCX_ANONYMIZATION_ERROR_CODES)[keyof typeof DOCX_ANONYMIZATION_ERROR_CODES];\n\nexport type DocxRestorationSession = {\n sessionId: () => string;\n restoreText: (text: string, observedAtEpochSeconds?: number) => string;\n};\n\nexport type RestoreDocxTextOptions = {\n document: Uint8Array;\n session: DocxRestorationSession;\n expectedSessionId: string;\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxRestorationResult = {\n document: Uint8Array;\n sessionId: string;\n restoredBlockCount: number;\n restoredPlaceholderCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport const DOCX_RESTORATION_ERROR_CODES = {\n invalidPlaceholder: \"invalid-placeholder\",\n invalidSession: \"invalid-session\",\n restorationLimitExceeded: \"restoration-limit-exceeded\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxRestorationErrorCode =\n (typeof DOCX_RESTORATION_ERROR_CODES)[keyof typeof DOCX_RESTORATION_ERROR_CODES];\n\nexport const DOCX_REWRITE_ERROR_CODES = {\n invalidReplacement: \"invalid-replacement\",\n rewriteLimitExceeded: \"rewrite-limit-exceeded\",\n staleExtraction: \"stale-extraction\",\n unsupportedReplacement: \"unsupported-replacement\",\n} as const;\n\nexport type DocxRewriteErrorCode =\n (typeof DOCX_REWRITE_ERROR_CODES)[keyof typeof DOCX_REWRITE_ERROR_CODES];\n\nexport const DOCX_EXTRACTION_ERROR_CODES = {\n archiveLimitExceeded: \"archive-limit-exceeded\",\n invalidArchive: \"invalid-archive\",\n invalidPackage: \"invalid-package\",\n invalidXml: \"invalid-xml\",\n unsafeEntryPath: \"unsafe-entry-path\",\n uncompressedLimitExceeded: \"uncompressed-limit-exceeded\",\n} as const;\n\nexport type DocxExtractionErrorCode =\n (typeof DOCX_EXTRACTION_ERROR_CODES)[keyof typeof DOCX_EXTRACTION_ERROR_CODES];\n","import { unzipSync, type UnzipFileInfo } from \"fflate\";\nimport { SaxesParser, type SaxesTagNS } from \"saxes\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n DOCX_PART_TYPES,\n type DocxCoverageItem,\n type DocxExtraction,\n type DocxExtractionErrorCode,\n type DocxPart,\n type DocxPartType,\n type DocxTextBlock,\n type DocxTextSegment,\n type DocxInlineContext,\n} from \"./types\";\n\nexport const DOCX_EXTRACTION_CONTRACT_VERSION = 1 as const;\nexport const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;\nexport const DOCX_ENTRY_MAX_BYTES = 16 * 1024 * 1024;\nexport const DOCX_UNCOMPRESSED_MAX_BYTES = 128 * 1024 * 1024;\nexport const DOCX_XML_MAX_DEPTH = 256;\nconst DOCX_MAX_ENTRIES = 4096;\nconst DOCX_MAX_TEXT_BLOCKS = 100_000;\nconst DOCX_MAX_TEXT_SEGMENTS = 1_000_000;\n\nconst CONTENT_TYPES_PATH = \"[Content_Types].xml\";\nconst ROOT_RELATIONSHIPS_PATH = \"_rels/.rels\";\nconst CONTENT_TYPES_NAMESPACE =\n \"http://schemas.openxmlformats.org/package/2006/content-types\";\nconst PACKAGE_RELATIONSHIP_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/package/relationships\",\n \"http://schemas.openxmlformats.org/package/2006/relationships\",\n]);\nconst WORDPROCESSING_CONTENT_TYPE_PREFIX =\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.\";\nconst SUPPORTED_CONTENT_TYPE_SUFFIXES: Readonly<Record<string, DocxPartType>> =\n {\n \"comments+xml\": DOCX_PART_TYPES.comments,\n \"document.main+xml\": DOCX_PART_TYPES.mainDocument,\n \"endnotes+xml\": DOCX_PART_TYPES.endnotes,\n \"footer+xml\": DOCX_PART_TYPES.footer,\n \"footnotes+xml\": DOCX_PART_TYPES.footnotes,\n \"header+xml\": DOCX_PART_TYPES.header,\n };\nconst WORDPROCESSING_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/wordprocessingml/main\",\n \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n]);\nconst RELATIONSHIP_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/officeDocument/relationships\",\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n]);\nconst OFFICE_DOCUMENT_RELATIONSHIP_TYPES = new Set(\n [...RELATIONSHIP_NAMESPACES].map(\n (namespace) => `${namespace}/officeDocument`,\n ),\n);\nconst MARKUP_COMPATIBILITY_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/markup-compatibility/main\",\n \"http://schemas.openxmlformats.org/markup-compatibility/2006\",\n]);\n\nexport class DocxExtractionError extends Error {\n readonly code: DocxExtractionErrorCode;\n\n constructor(code: DocxExtractionErrorCode, message: string) {\n super(message);\n this.name = \"DocxExtractionError\";\n this.code = code;\n }\n}\n\ntype ContentTypePart = {\n path: string;\n contentType: string;\n};\n\ntype ElementFrame = {\n tag: SaxesTagNS;\n path: readonly number[];\n nextChildIndex: number;\n};\n\ntype MutableBlock = {\n text: string;\n segments: DocxTextSegment[];\n location: DocxTextBlock[\"location\"];\n};\n\ntype PartExtraction = {\n blocks: DocxTextBlock[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\ntype PartTextBudget = {\n segmentCount: number;\n};\n\nconst invalidPackage = (message: string): DocxExtractionError =>\n new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.invalidPackage, message);\n\nconst assertXmlDepth = (depth: number): void => {\n if (depth < DOCX_XML_MAX_DEPTH) {\n return;\n }\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX XML must not exceed ${DOCX_XML_MAX_DEPTH} nested elements`,\n );\n};\n\nconst safeEntryPath = (name: string): boolean =>\n name.length > 0 &&\n !name.startsWith(\"/\") &&\n !name.includes(\"\\\\\") &&\n !name.split(\"/\").includes(\"..\") &&\n !name.includes(\"\\0\");\n\ntype ArchiveBudget = {\n entryCount: number;\n uncompressedBytes: number;\n};\n\ntype ArchiveFilterOptions = {\n budget: ArchiveBudget;\n file: UnzipFileInfo;\n includeAllEntries: boolean;\n};\n\nconst archiveFilter = ({\n budget,\n file,\n includeAllEntries,\n}: ArchiveFilterOptions): boolean => {\n budget.entryCount += 1;\n if (budget.entryCount > DOCX_MAX_ENTRIES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must contain at most ${DOCX_MAX_ENTRIES} entries`,\n );\n }\n if (!safeEntryPath(file.name)) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.unsafeEntryPath,\n \"DOCX archive contains an unsafe entry path\",\n );\n }\n if (file.originalSize > DOCX_ENTRY_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX entries must not exceed ${DOCX_ENTRY_MAX_BYTES} bytes`,\n );\n }\n budget.uncompressedBytes += file.originalSize;\n if (budget.uncompressedBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} uncompressed bytes`,\n );\n }\n return (\n includeAllEntries ||\n file.name === CONTENT_TYPES_PATH ||\n file.name === ROOT_RELATIONSHIPS_PATH ||\n (file.name.startsWith(\"word/\") && file.name.endsWith(\".xml\"))\n );\n};\n\nexport const unzipDocxArchive = (\n archive: Uint8Array,\n includeAllEntries = false,\n): Record<string, Uint8Array> => {\n if (archive.byteLength > DOCX_ARCHIVE_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.archiveLimitExceeded,\n `DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n );\n }\n const budget: ArchiveBudget = { entryCount: 0, uncompressedBytes: 0 };\n try {\n return unzipSync(archive, {\n filter: (file) => archiveFilter({ budget, file, includeAllEntries }),\n });\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidArchive,\n \"Input is not a valid bounded DOCX ZIP archive\",\n );\n }\n};\n\nconst decodeXml = (bytes: Uint8Array, path: string): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n `DOCX XML part is not valid UTF-8: ${path}`,\n );\n }\n};\n\nconst attributeByLocalName = (\n tag: SaxesTagNS,\n localName: string,\n namespaces?: ReadonlySet<string>,\n): string | null => {\n for (const attribute of Object.values(tag.attributes)) {\n if (\n attribute.local === localName &&\n (namespaces === undefined || namespaces.has(attribute.uri))\n ) {\n return attribute.value;\n }\n }\n return null;\n};\n\nconst parseContentTypes = (xml: string): ContentTypePart[] => {\n const parts: ContentTypePart[] = [];\n const paths = new Set<string>();\n const parser = new SaxesParser({ xmlns: true });\n let parseError: Error | null = null;\n let depth = 0;\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(depth);\n depth += 1;\n if (tag.local !== \"Override\" || tag.uri !== CONTENT_TYPES_NAMESPACE) {\n return;\n }\n const rawPath = attributeByLocalName(tag, \"PartName\");\n const contentType = attributeByLocalName(tag, \"ContentType\");\n if (rawPath === null || contentType === null) {\n throw invalidPackage(\"DOCX content-type override is incomplete\");\n }\n const path = rawPath.startsWith(\"/\") ? rawPath.slice(1) : rawPath;\n if (!safeEntryPath(path)) {\n throw invalidPackage(\"DOCX content-type override has an unsafe path\");\n }\n if (paths.has(path)) {\n throw invalidPackage(\n \"DOCX content-type overrides must have unique paths\",\n );\n }\n paths.add(path);\n parts.push({ path, contentType });\n });\n parser.on(\"closetag\", () => {\n depth -= 1;\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n \"DOCX content types are not valid XML\",\n );\n }\n return parts;\n};\n\nconst parseMainDocumentTarget = (xml: string): string => {\n const targets: string[] = [];\n const parser = new SaxesParser({ xmlns: true });\n let parseError: Error | null = null;\n let depth = 0;\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(depth);\n depth += 1;\n if (\n tag.local !== \"Relationship\" ||\n !PACKAGE_RELATIONSHIP_NAMESPACES.has(tag.uri)\n ) {\n return;\n }\n const type = attributeByLocalName(tag, \"Type\");\n if (type === null || !OFFICE_DOCUMENT_RELATIONSHIP_TYPES.has(type)) {\n return;\n }\n const targetMode = attributeByLocalName(tag, \"TargetMode\");\n const rawTarget = attributeByLocalName(tag, \"Target\");\n if (targetMode === \"External\" || rawTarget === null) {\n throw invalidPackage(\"DOCX main-document relationship must be internal\");\n }\n const target = rawTarget.startsWith(\"/\") ? rawTarget.slice(1) : rawTarget;\n if (!safeEntryPath(target) || target.includes(\":\")) {\n throw invalidPackage(\n \"DOCX main-document relationship has an unsafe target\",\n );\n }\n targets.push(target);\n });\n parser.on(\"closetag\", () => {\n depth -= 1;\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n \"DOCX root relationships are not valid XML\",\n );\n }\n if (targets.length !== 1) {\n throw invalidPackage(\n \"DOCX archive must contain exactly one main-document relationship\",\n );\n }\n const target = targets.at(0);\n if (target === undefined) {\n throw invalidPackage(\"DOCX main-document relationship is unavailable\");\n }\n return target;\n};\n\nconst classifyPart = ({\n contentType,\n path,\n}: ContentTypePart): DocxPart | null => {\n if (!contentType.startsWith(WORDPROCESSING_CONTENT_TYPE_PREFIX)) {\n return null;\n }\n const suffix = contentType.slice(WORDPROCESSING_CONTENT_TYPE_PREFIX.length);\n const type = SUPPORTED_CONTENT_TYPE_SUFFIXES[suffix];\n return type === undefined ? null : { type, path };\n};\n\nconst isWordTag = (tag: SaxesTagNS, local: string): boolean =>\n tag.local === local && WORDPROCESSING_NAMESPACES.has(tag.uri);\n\nconst frameByLocalName = (\n stack: readonly ElementFrame[],\n local: string,\n): ElementFrame | null => {\n for (let index = stack.length - 1; index >= 0; index -= 1) {\n const frame = stack.at(index);\n if (frame !== undefined && isWordTag(frame.tag, local)) {\n return frame;\n }\n }\n return null;\n};\n\nconst blockLocation = (\n part: DocxPart,\n blockIndex: number,\n paragraphPath: readonly number[],\n stack: readonly ElementFrame[],\n): DocxTextBlock[\"location\"] => {\n const textBox = frameByLocalName(stack, \"txbxContent\");\n if (textBox !== null) {\n return {\n type: \"text-box-paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n textBoxPath: textBox.path,\n };\n }\n const cell = frameByLocalName(stack, \"tc\");\n const row = frameByLocalName(stack, \"tr\");\n const table = frameByLocalName(stack, \"tbl\");\n if (cell !== null && row !== null && table !== null) {\n return {\n type: \"table-cell-paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n tablePath: table.path,\n rowPath: row.path,\n cellPath: cell.path,\n };\n }\n return {\n type: \"paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n };\n};\n\nconst revisionForTag = (\n tag: SaxesTagNS,\n): Extract<DocxInlineContext, { type: \"revision\" }> | null => {\n if (!WORDPROCESSING_NAMESPACES.has(tag.uri)) {\n return null;\n }\n const revisions: Readonly<\n Record<string, \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\">\n > = {\n del: \"deletion\",\n ins: \"insertion\",\n moveFrom: \"move-from\",\n moveTo: \"move-to\",\n };\n const revision = revisions[tag.local];\n return revision === undefined ? null : { type: \"revision\", revision };\n};\n\nconst inlineContexts = (\n stack: readonly ElementFrame[],\n): DocxInlineContext[] => {\n const contexts: DocxInlineContext[] = [];\n for (const { tag } of stack) {\n if (isWordTag(tag, \"hyperlink\")) {\n contexts.push({\n type: \"hyperlink\",\n relationshipId: attributeByLocalName(\n tag,\n \"id\",\n RELATIONSHIP_NAMESPACES,\n ),\n anchor: attributeByLocalName(tag, \"anchor\", WORDPROCESSING_NAMESPACES),\n });\n }\n const revision = revisionForTag(tag);\n if (revision !== null) {\n contexts.push(revision);\n }\n }\n return contexts;\n};\n\nconst appendSegment = (\n block: MutableBlock,\n budget: PartTextBudget,\n value: string,\n source: DocxTextSegment[\"source\"],\n path: readonly number[],\n stack: readonly ElementFrame[],\n): void => {\n if (value.length === 0) {\n return;\n }\n if (budget.segmentCount >= DOCX_MAX_TEXT_SEGMENTS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX parts must not contain more than ${DOCX_MAX_TEXT_SEGMENTS} text segments`,\n );\n }\n budget.segmentCount += 1;\n const start = block.text.length;\n block.text += value;\n block.segments.push({\n start,\n end: block.text.length,\n source,\n contexts: inlineContexts(stack),\n xmlPath: path,\n });\n};\n\nconst extractPart = (part: DocxPart, xml: string): PartExtraction => {\n const blocks: DocxTextBlock[] = [];\n const stack: ElementFrame[] = [];\n const blockStack: MutableBlock[] = [];\n let nextBlockIndex = 0;\n let currentText = \"\";\n let currentTextPath: readonly number[] | null = null;\n let parseError: Error | null = null;\n let unsupportedSymbolCount = 0;\n let unsupportedFieldInstructionCount = 0;\n let unsupportedAlternateContentCount = 0;\n const textBudget: PartTextBudget = { segmentCount: 0 };\n\n const parser = new SaxesParser({ xmlns: true });\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(stack.length);\n const parent = stack.at(-1);\n const childIndex = parent?.nextChildIndex ?? 0;\n if (parent !== undefined) {\n parent.nextChildIndex += 1;\n }\n const path = [...(parent?.path ?? []), childIndex];\n if (isWordTag(tag, \"p\")) {\n if (nextBlockIndex >= DOCX_MAX_TEXT_BLOCKS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX parts must not contain more than ${DOCX_MAX_TEXT_BLOCKS} text blocks`,\n );\n }\n blockStack.push({\n text: \"\",\n segments: [],\n location: blockLocation(part, nextBlockIndex, path, stack),\n });\n nextBlockIndex += 1;\n }\n stack.push({ tag, path, nextChildIndex: 0 });\n if (isWordTag(tag, \"t\") || isWordTag(tag, \"delText\")) {\n currentText = \"\";\n currentTextPath = path;\n }\n const currentBlock = blockStack.at(-1);\n if (currentBlock !== undefined && isWordTag(tag, \"tab\")) {\n appendSegment(currentBlock, textBudget, \"\\t\", \"tab\", path, stack);\n }\n if (\n currentBlock !== undefined &&\n (isWordTag(tag, \"br\") || isWordTag(tag, \"cr\"))\n ) {\n appendSegment(currentBlock, textBudget, \"\\n\", \"break\", path, stack);\n }\n if (isWordTag(tag, \"sym\")) {\n unsupportedSymbolCount += 1;\n }\n if (isWordTag(tag, \"instrText\") || isWordTag(tag, \"fldSimple\")) {\n unsupportedFieldInstructionCount += 1;\n }\n if (\n tag.local === \"AlternateContent\" &&\n MARKUP_COMPATIBILITY_NAMESPACES.has(tag.uri)\n ) {\n unsupportedAlternateContentCount += 1;\n }\n });\n parser.on(\"text\", (text) => {\n if (currentTextPath !== null) {\n currentText += text;\n }\n });\n parser.on(\"cdata\", (text) => {\n if (currentTextPath !== null) {\n currentText += text;\n }\n });\n parser.on(\"closetag\", (tag) => {\n const frame = stack.at(-1);\n if (frame === undefined || frame.tag !== tag) {\n throw invalidPackage(\"DOCX XML element stack is inconsistent\");\n }\n if (\n currentTextPath !== null &&\n (isWordTag(tag, \"t\") || isWordTag(tag, \"delText\"))\n ) {\n const currentBlock = blockStack.at(-1);\n if (currentBlock === undefined) {\n if (currentText.length > 0) {\n throw invalidPackage(\"DOCX text is outside a paragraph\");\n }\n } else {\n appendSegment(\n currentBlock,\n textBudget,\n currentText,\n \"text\",\n currentTextPath,\n stack,\n );\n }\n currentText = \"\";\n currentTextPath = null;\n }\n if (isWordTag(tag, \"p\")) {\n const completedBlock = blockStack.pop();\n if (completedBlock === undefined) {\n throw invalidPackage(\"DOCX paragraph state is unavailable\");\n }\n blocks.push(completedBlock);\n }\n stack.pop();\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n `DOCX part is not valid XML: ${part.path}`,\n );\n }\n\n blocks.sort(\n (left, right) => left.location.blockIndex - right.location.blockIndex,\n );\n\n let hyperlinkTextSegmentCount = 0;\n let revisionTextSegmentCount = 0;\n for (const { segments } of blocks) {\n for (const { contexts } of segments) {\n if (contexts.some((context) => context.type === \"hyperlink\")) {\n hyperlinkTextSegmentCount += 1;\n }\n if (contexts.some((context) => context.type === \"revision\")) {\n revisionTextSegmentCount += 1;\n }\n }\n }\n return {\n blocks,\n hyperlinkTextSegmentCount,\n revisionTextSegmentCount,\n unsupportedAlternateContentCount,\n unsupportedSymbolCount,\n unsupportedFieldInstructionCount,\n };\n};\n\nexport const extractDocxText = (archive: Uint8Array): DocxExtraction => {\n const entries = unzipDocxArchive(archive);\n const contentTypesBytes = entries[CONTENT_TYPES_PATH];\n if (contentTypesBytes === undefined) {\n throw invalidPackage(\"DOCX archive is missing [Content_Types].xml\");\n }\n const contentTypes = parseContentTypes(\n decodeXml(contentTypesBytes, CONTENT_TYPES_PATH),\n );\n const rootRelationshipsBytes = entries[ROOT_RELATIONSHIPS_PATH];\n if (rootRelationshipsBytes === undefined) {\n throw invalidPackage(\"DOCX archive is missing _rels/.rels\");\n }\n const mainDocumentTarget = parseMainDocumentTarget(\n decodeXml(rootRelationshipsBytes, ROOT_RELATIONSHIPS_PATH),\n );\n const supportedParts = contentTypes\n .map(classifyPart)\n .filter((part): part is DocxPart => part !== null);\n if (\n supportedParts.filter((part) => part.type === DOCX_PART_TYPES.mainDocument)\n .length !== 1\n ) {\n throw invalidPackage(\"DOCX archive must contain exactly one main document\");\n }\n const mainDocument = supportedParts.find(\n (part) => part.type === DOCX_PART_TYPES.mainDocument,\n );\n if (mainDocument?.path !== mainDocumentTarget) {\n throw invalidPackage(\n \"DOCX main-document relationship and content type do not agree\",\n );\n }\n\n const blocks: DocxTextBlock[] = [];\n const coverageParts: DocxCoverageItem[] = [];\n let hyperlinkTextSegmentCount = 0;\n let revisionTextSegmentCount = 0;\n let unsupportedSymbolCount = 0;\n let unsupportedFieldInstructionCount = 0;\n let unsupportedAlternateContentCount = 0;\n let textSegmentCount = 0;\n for (const part of supportedParts) {\n const bytes = entries[part.path];\n if (bytes === undefined) {\n throw invalidPackage(\n `DOCX archive is missing declared part: ${part.path}`,\n );\n }\n const extracted = extractPart(part, decodeXml(bytes, part.path));\n if (blocks.length + extracted.blocks.length > DOCX_MAX_TEXT_BLOCKS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not contain more than ${DOCX_MAX_TEXT_BLOCKS} text blocks`,\n );\n }\n const extractedSegmentCount = extracted.blocks.reduce(\n (count, block) => count + block.segments.length,\n 0,\n );\n if (textSegmentCount + extractedSegmentCount > DOCX_MAX_TEXT_SEGMENTS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not contain more than ${DOCX_MAX_TEXT_SEGMENTS} text segments`,\n );\n }\n textSegmentCount += extractedSegmentCount;\n blocks.push(...extracted.blocks);\n coverageParts.push({\n status: \"extracted\",\n part,\n blockCount: extracted.blocks.length,\n });\n hyperlinkTextSegmentCount += extracted.hyperlinkTextSegmentCount;\n revisionTextSegmentCount += extracted.revisionTextSegmentCount;\n unsupportedSymbolCount += extracted.unsupportedSymbolCount;\n unsupportedFieldInstructionCount +=\n extracted.unsupportedFieldInstructionCount;\n unsupportedAlternateContentCount +=\n extracted.unsupportedAlternateContentCount;\n }\n\n for (const { contentType, path } of contentTypes) {\n if (\n contentType.startsWith(WORDPROCESSING_CONTENT_TYPE_PREFIX) &&\n classifyPart({ contentType, path }) === null\n ) {\n coverageParts.push({\n status: \"unsupported\",\n path,\n contentType,\n reason: \"WordprocessingML part type is not extracted\",\n });\n }\n }\n\n return {\n contractVersion: DOCX_EXTRACTION_CONTRACT_VERSION,\n blocks,\n coverage: {\n parts: coverageParts,\n hyperlinkTextSegmentCount,\n revisionTextSegmentCount,\n unsupportedAlternateContentCount,\n unsupportedSymbolCount,\n unsupportedFieldInstructionCount,\n },\n };\n};\n","import type { DocxBlockLocation } from \"./types\";\n\nconst arraysEqual = (\n left: readonly number[],\n right: readonly number[],\n): boolean =>\n left.length === right.length &&\n left.every((value, index) => value === right.at(index));\n\nexport const docxLocationsEqual = (\n left: DocxBlockLocation,\n right: DocxBlockLocation,\n): boolean => {\n if (\n left.type !== right.type ||\n left.part.type !== right.part.type ||\n left.part.path !== right.part.path ||\n left.blockIndex !== right.blockIndex ||\n !arraysEqual(left.xmlPath, right.xmlPath)\n ) {\n return false;\n }\n if (left.type === \"paragraph\" && right.type === \"paragraph\") {\n return true;\n }\n if (\n left.type === \"table-cell-paragraph\" &&\n right.type === \"table-cell-paragraph\"\n ) {\n return (\n arraysEqual(left.tablePath, right.tablePath) &&\n arraysEqual(left.rowPath, right.rowPath) &&\n arraysEqual(left.cellPath, right.cellPath)\n );\n }\n if (\n left.type === \"text-box-paragraph\" &&\n right.type === \"text-box-paragraph\"\n ) {\n return arraysEqual(left.textBoxPath, right.textBoxPath);\n }\n return false;\n};\n\nexport const docxLocationKey = ({\n blockIndex,\n part,\n}: DocxBlockLocation): string => `${part.path}\\0${blockIndex}`;\n","import { strToU8, zipSync } from \"fflate\";\nimport { SaxesParser, type SaxesTagNS } from \"saxes\";\n\nimport {\n DOCX_ARCHIVE_MAX_BYTES,\n DOCX_ENTRY_MAX_BYTES,\n DOCX_UNCOMPRESSED_MAX_BYTES,\n DOCX_XML_MAX_DEPTH,\n extractDocxText,\n unzipDocxArchive,\n} from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport {\n DOCX_REWRITE_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRewriteErrorCode,\n type DocxRewriteResult,\n type DocxTextBlock,\n type DocxTextReplacement,\n type DocxTextSegment,\n} from \"./types\";\n\nconst WORDPROCESSING_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/wordprocessingml/main\",\n \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n]);\nconst XML_NAMESPACE = \"http://www.w3.org/XML/1998/namespace\";\nconst DOCX_MAX_REPLACEMENTS = 1_000_000;\nconst SIGNATURE_PART_PREFIX = \"_xmlsignatures/\";\n\nexport class DocxRewriteError extends Error {\n readonly code: DocxRewriteErrorCode;\n\n constructor(code: DocxRewriteErrorCode, message: string) {\n super(message);\n this.name = \"DocxRewriteError\";\n this.code = code;\n }\n}\n\ntype ElementFrame = {\n path: readonly number[];\n nextChildIndex: number;\n};\n\ntype TextNodeUpdate = {\n path: readonly number[];\n value: string;\n};\n\ntype XmlPatch = {\n start: number;\n end: number;\n value: string;\n};\n\nconst rewriteError = (\n code: DocxRewriteErrorCode,\n message: string,\n): DocxRewriteError => new DocxRewriteError(code, message);\n\nconst pathKey = (path: readonly number[]): string => path.join(\".\");\n\nconst isValidXmlText = (value: string): boolean => {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (\n codePoint === undefined ||\n (codePoint !== 0x09 &&\n codePoint !== 0x0a &&\n codePoint !== 0x0d &&\n (codePoint < 0x20 ||\n (codePoint > 0xd7ff && codePoint < 0xe000) ||\n (codePoint > 0xfffd && codePoint < 0x10_000) ||\n codePoint > 0x10_ffff))\n ) {\n return false;\n }\n }\n return true;\n};\n\nconst isUtf16Boundary = (value: string, offset: number): boolean => {\n if (offset === 0 || offset === value.length) {\n return true;\n }\n const previous = value.charCodeAt(offset - 1);\n const next = value.charCodeAt(offset);\n return !(\n previous >= 0xd800 &&\n previous <= 0xdbff &&\n next >= 0xdc00 &&\n next <= 0xdfff\n );\n};\n\nconst validateReplacement = (\n replacement: DocxTextReplacement,\n blockText: string,\n): void => {\n if (\n !Number.isSafeInteger(replacement.start) ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.start < 0 ||\n replacement.start >= replacement.end ||\n replacement.end > blockText.length ||\n !isUtf16Boundary(blockText, replacement.start) ||\n !isUtf16Boundary(blockText, replacement.end)\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement spans must be nonempty bounded integer ranges at UTF-16 boundaries\",\n );\n }\n if (!isValidXmlText(replacement.replacement)) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement text must contain only valid XML characters\",\n );\n }\n if (strToU8(replacement.replacement).byteLength > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX replacement text must not exceed ${DOCX_ENTRY_MAX_BYTES} UTF-8 bytes`,\n );\n }\n};\n\nconst coveredTextSegments = (\n block: DocxTextBlock,\n replacement: DocxTextReplacement,\n): readonly DocxTextSegment[] => {\n const segments = block.segments.filter(\n ({ end, start }) => start < replacement.end && end > replacement.start,\n );\n let cursor = replacement.start;\n for (const segment of segments) {\n if (\n segment.source !== \"text\" ||\n segment.start > cursor ||\n segment.contexts.some((context) => context.type === \"revision\")\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacements must stay within contiguous non-revision text segments\",\n );\n }\n cursor = Math.min(replacement.end, segment.end);\n }\n if (segments.length === 0 || cursor !== replacement.end) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacements must stay within contiguous non-revision text segments\",\n );\n }\n return segments;\n};\n\nconst planBlockUpdates = (\n block: DocxTextBlock,\n rewrite: DocxBlockRewrite,\n): TextNodeUpdate[] => {\n const replacements = [...rewrite.replacements].sort(\n (left, right) => left.start - right.start,\n );\n for (const [index, replacement] of replacements.entries()) {\n validateReplacement(replacement, block.text);\n const previous = index === 0 ? undefined : replacements.at(index - 1);\n if (previous !== undefined && previous.end > replacement.start) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement spans must not overlap\",\n );\n }\n }\n\n const values = new Map<string, TextNodeUpdate>();\n const originalValues = new Map<string, string>();\n for (const segment of block.segments) {\n if (segment.source !== \"text\") {\n continue;\n }\n values.set(pathKey(segment.xmlPath), {\n path: segment.xmlPath,\n value: block.text.slice(segment.start, segment.end),\n });\n originalValues.set(\n pathKey(segment.xmlPath),\n block.text.slice(segment.start, segment.end),\n );\n }\n\n for (const replacement of replacements.toReversed()) {\n const segments = coveredTextSegments(block, replacement);\n const first = segments.at(0);\n const last = segments.at(-1);\n if (first === undefined || last === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacement text segments are unavailable\",\n );\n }\n const firstUpdate = values.get(pathKey(first.xmlPath));\n const lastUpdate = values.get(pathKey(last.xmlPath));\n if (firstUpdate === undefined || lastUpdate === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacement text nodes are unavailable\",\n );\n }\n const firstStart = replacement.start - first.start;\n const lastEnd = replacement.end - last.start;\n if (first === last) {\n firstUpdate.value =\n firstUpdate.value.slice(0, firstStart) +\n replacement.replacement +\n firstUpdate.value.slice(lastEnd);\n continue;\n }\n firstUpdate.value =\n firstUpdate.value.slice(0, firstStart) + replacement.replacement;\n for (const segment of segments.slice(1, -1)) {\n const update = values.get(pathKey(segment.xmlPath));\n if (update !== undefined) {\n update.value = \"\";\n }\n }\n lastUpdate.value = lastUpdate.value.slice(lastEnd);\n }\n return [...values.entries()]\n .filter(([key, update]) => update.value !== originalValues.get(key))\n .map(([, update]) => update);\n};\n\nconst escapeXmlText = (value: string): string =>\n value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n\nconst requiresPreservedSpace = (value: string): boolean =>\n /^\\s|\\s$/u.test(value);\n\nconst isWordTextTag = (tag: SaxesTagNS): boolean =>\n WORDPROCESSING_NAMESPACES.has(tag.uri) &&\n (tag.local === \"t\" || tag.local === \"delText\");\n\nconst hasPreservedSpace = (tag: SaxesTagNS): boolean =>\n Object.values(tag.attributes).some(\n (attribute) =>\n attribute.uri === XML_NAMESPACE &&\n attribute.local === \"space\" &&\n attribute.value === \"preserve\",\n );\n\ntype FindClosingTagStartOptions = {\n xml: string;\n contentStart: number;\n parserPosition: number;\n};\n\nconst findClosingTagStart = ({\n xml,\n contentStart,\n parserPosition,\n}: FindClosingTagStartOptions): number => {\n for (let index = parserPosition - 1; index >= contentStart; index -= 1) {\n if (xml[index] === \"<\" && xml[index + 1] === \"/\") {\n return index;\n }\n }\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX text-node closing tag changed after extraction\",\n );\n};\n\nconst rewritePartXml = (\n xml: string,\n updates: readonly TextNodeUpdate[],\n): string => {\n const updatesByPath = new Map(\n updates.map((update) => [pathKey(update.path), update]),\n );\n const foundPaths = new Set<string>();\n const patches: XmlPatch[] = [];\n const stack: ElementFrame[] = [];\n let activeText:\n | { key: string; contentStart: number; tag: SaxesTagNS }\n | undefined;\n let parseError: Error | null = null;\n const parser = new SaxesParser({ xmlns: true });\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"opentag\", (tag) => {\n if (stack.length >= DOCX_XML_MAX_DEPTH) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX XML must not exceed ${DOCX_XML_MAX_DEPTH} nested elements`,\n );\n }\n const parent = stack.at(-1);\n const childIndex = parent?.nextChildIndex ?? 0;\n if (parent !== undefined) {\n parent.nextChildIndex += 1;\n }\n const path = [...(parent?.path ?? []), childIndex];\n stack.push({ path, nextChildIndex: 0 });\n const key = pathKey(path);\n if (isWordTextTag(tag) && updatesByPath.has(key)) {\n if (tag.isSelfClosing) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX self-closing text nodes cannot receive replacements\",\n );\n }\n activeText = { key, contentStart: parser.position, tag };\n }\n });\n parser.on(\"closetag\", (tag) => {\n if (activeText?.tag === tag) {\n const update = updatesByPath.get(activeText.key);\n if (update !== undefined) {\n const contentEnd = findClosingTagStart({\n xml,\n contentStart: activeText.contentStart,\n parserPosition: parser.position,\n });\n patches.push({\n start: activeText.contentStart,\n end: contentEnd,\n value: escapeXmlText(update.value),\n });\n if (requiresPreservedSpace(update.value) && !hasPreservedSpace(tag)) {\n patches.push({\n start: activeText.contentStart - 1,\n end: activeText.contentStart - 1,\n value: ' xml:space=\"preserve\"',\n });\n }\n foundPaths.add(activeText.key);\n }\n activeText = undefined;\n }\n stack.pop();\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxRewriteError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX source XML changed after extraction\",\n );\n }\n if (foundPaths.size !== updatesByPath.size) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX text-node locations changed after extraction\",\n );\n }\n let rewritten = xml;\n for (const patch of patches.toSorted(\n (left, right) => right.start - left.start,\n )) {\n rewritten =\n rewritten.slice(0, patch.start) +\n patch.value +\n rewritten.slice(patch.end);\n }\n return rewritten;\n};\n\nconst assertArchiveBudgets = (entries: Record<string, Uint8Array>): void => {\n let totalBytes = 0;\n for (const bytes of Object.values(entries)) {\n if (bytes.byteLength > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX entries must not exceed ${DOCX_ENTRY_MAX_BYTES} bytes`,\n );\n }\n totalBytes += bytes.byteLength;\n }\n if (totalBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} uncompressed bytes`,\n );\n }\n};\n\nexport const rewriteDocxText = (\n archive: Uint8Array,\n rewrites: readonly DocxBlockRewrite[],\n): DocxRewriteResult => {\n const extraction = extractDocxText(archive);\n if (rewrites.length === 0) {\n return {\n document: archive.slice(),\n rewrittenBlockCount: 0,\n appliedReplacementCount: 0,\n };\n }\n const blocksByLocation = new Map(\n extraction.blocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const updatesByPart = new Map<string, Map<string, TextNodeUpdate>>();\n const rewrittenLocations = new Set<string>();\n let appliedReplacementCount = 0;\n\n for (const rewrite of rewrites) {\n const key = docxLocationKey(rewrite.location);\n if (rewrittenLocations.has(key)) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"Each DOCX block may appear in a rewrite plan only once\",\n );\n }\n rewrittenLocations.add(key);\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, rewrite.location) ||\n block.text !== rewrite.expectedText\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX block location or expected text no longer matches\",\n );\n }\n if (rewrite.replacements.length === 0) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite plans must contain at least one replacement\",\n );\n }\n if (\n appliedReplacementCount + rewrite.replacements.length >\n DOCX_MAX_REPLACEMENTS\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_MAX_REPLACEMENTS} replacements`,\n );\n }\n const partUpdates =\n updatesByPart.get(block.location.part.path) ?? new Map();\n for (const update of planBlockUpdates(block, rewrite)) {\n partUpdates.set(pathKey(update.path), update);\n }\n updatesByPart.set(block.location.part.path, partUpdates);\n appliedReplacementCount += rewrite.replacements.length;\n }\n\n const entries = unzipDocxArchive(archive, true);\n if (\n Object.keys(entries).some((path) =>\n path.toLowerCase().startsWith(SIGNATURE_PART_PREFIX),\n )\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"Digitally signed DOCX packages must be re-signed before rewriting\",\n );\n }\n for (const [partPath, updates] of updatesByPart) {\n const partBytes = entries[partPath];\n if (partBytes === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX source part changed after extraction\",\n );\n }\n const xml = new TextDecoder(\"utf-8\", { fatal: true }).decode(partBytes);\n entries[partPath] = strToU8(rewritePartXml(xml, [...updates.values()]));\n }\n assertArchiveBudgets(entries);\n const document = zipSync(entries);\n if (document.byteLength > DOCX_ARCHIVE_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n );\n }\n return {\n document,\n rewrittenBlockCount: rewrites.length,\n appliedReplacementCount,\n };\n};\n","import type { DocxCoverage, DocxWorkflowCoverage } from \"./types\";\n\nconst hasPartialCoverage = (coverage: DocxCoverage): boolean =>\n coverage.parts.some(({ status }) => status === \"unsupported\") ||\n coverage.hyperlinkTextSegmentCount > 0 ||\n coverage.revisionTextSegmentCount > 0 ||\n coverage.unsupportedAlternateContentCount > 0 ||\n coverage.unsupportedSymbolCount > 0 ||\n coverage.unsupportedFieldInstructionCount > 0;\n\nexport const docxWorkflowCoverage = (\n coverage: DocxCoverage,\n): DocxWorkflowCoverage => {\n const counts = {\n extractedPartCount: coverage.parts.filter(\n ({ status }) => status === \"extracted\",\n ).length,\n unsupportedPartCount: coverage.parts.filter(\n ({ status }) => status === \"unsupported\",\n ).length,\n hyperlinkTextSegmentCount: coverage.hyperlinkTextSegmentCount,\n revisionTextSegmentCount: coverage.revisionTextSegmentCount,\n unsupportedAlternateContentCount: coverage.unsupportedAlternateContentCount,\n unsupportedSymbolCount: coverage.unsupportedSymbolCount,\n unsupportedFieldInstructionCount: coverage.unsupportedFieldInstructionCount,\n };\n return hasPartialCoverage(coverage)\n ? { status: \"partial\", counts }\n : { status: \"full\", counts };\n};\n","import { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_RESTORATION_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRestorationErrorCode,\n type DocxRestorationResult,\n type DocxTextReplacement,\n type RestoreDocxTextOptions,\n} from \"./types\";\n\nconst DOCX_RESTORE_MAX_PLACEHOLDER_UTF16 = 512;\nconst DOCX_RESTORE_MAX_CANDIDATES = 1_000_000;\n\nexport class DocxRestorationError extends Error {\n readonly code: DocxRestorationErrorCode;\n\n constructor(code: DocxRestorationErrorCode, message: string) {\n super(message);\n this.name = \"DocxRestorationError\";\n this.code = code;\n }\n}\n\nconst restorationError = (\n code: DocxRestorationErrorCode,\n message: string,\n): DocxRestorationError => new DocxRestorationError(code, message);\n\nconst encodedSessionNamespace = (sessionId: string): string =>\n sessionId.replaceAll(\"_\", \"%5F\");\n\nconst isOwnedPlaceholderCandidate = (\n value: string,\n encodedSessionId: string,\n): boolean => {\n const inner = value.endsWith(\"]\") ? value.slice(0, -1) : value;\n const countSeparator = inner.lastIndexOf(\"_\");\n if (countSeparator <= 0) {\n return false;\n }\n const prefix = inner.slice(0, countSeparator);\n const namespaceSeparator = prefix.lastIndexOf(\"_\");\n if (namespaceSeparator <= 0) {\n return false;\n }\n return prefix.slice(namespaceSeparator + 1) === encodedSessionId;\n};\n\ntype PlanBlockRestorationOptions = {\n text: string;\n encodedSessionId: string;\n restoreCandidate: (candidate: string) => string;\n budget: { candidateCount: number };\n};\n\nconst planBlockRestoration = ({\n text,\n encodedSessionId,\n restoreCandidate,\n budget,\n}: PlanBlockRestorationOptions): DocxTextReplacement[] => {\n const replacements: DocxTextReplacement[] = [];\n let start: number | undefined;\n for (let cursor = 0; cursor < text.length; cursor += 1) {\n const character = text.at(cursor);\n if (character === \"[\") {\n if (\n start !== undefined &&\n isOwnedPlaceholderCandidate(\n text.slice(start + 1, cursor),\n encodedSessionId,\n )\n ) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an incomplete placeholder for the expected session\",\n );\n }\n start = cursor;\n continue;\n }\n if (character !== \"]\" || start === undefined) {\n continue;\n }\n const candidateEnd = cursor + 1;\n const candidate = text.slice(start, candidateEnd);\n budget.candidateCount += 1;\n if (budget.candidateCount > DOCX_RESTORE_MAX_CANDIDATES) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.restorationLimitExceeded,\n `DOCX restoration must not inspect more than ${DOCX_RESTORE_MAX_CANDIDATES} placeholder candidates`,\n );\n }\n const isOwned = isOwnedPlaceholderCandidate(\n candidate.slice(1),\n encodedSessionId,\n );\n if (candidate.length > DOCX_RESTORE_MAX_PLACEHOLDER_UTF16) {\n if (isOwned) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX session placeholder exceeds the maximum length\",\n );\n }\n start = undefined;\n continue;\n }\n if (!isOwned) {\n start = undefined;\n continue;\n }\n const replacement = restoreCandidate(candidate);\n if (replacement !== candidate) {\n replacements.push({ start, end: candidateEnd, replacement });\n } else {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an unknown placeholder for the expected session\",\n );\n }\n start = undefined;\n }\n if (\n start !== undefined &&\n isOwnedPlaceholderCandidate(text.slice(start + 1), encodedSessionId)\n ) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an incomplete placeholder for the expected session\",\n );\n }\n return replacements;\n};\n\nexport const restoreDocxText = ({\n document,\n session,\n expectedSessionId,\n observedAtEpochSeconds,\n}: RestoreDocxTextOptions): DocxRestorationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.sessionMismatch,\n \"DOCX restoration session does not match the expected session id\",\n );\n }\n\n const assertSessionAvailable = (): void => {\n if (session.restoreText(\"\", observedAtEpochSeconds) !== \"\") {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"DOCX restoration session must preserve text without placeholders\",\n );\n }\n };\n assertSessionAvailable();\n const restoredCandidates = new Map<string, string>();\n const restoreCandidate = (candidate: string): string => {\n const cached = restoredCandidates.get(candidate);\n if (cached !== undefined) {\n return cached;\n }\n const restored = session.restoreText(candidate, observedAtEpochSeconds);\n restoredCandidates.set(candidate, restored);\n return restored;\n };\n const encodedSessionId = encodedSessionNamespace(sessionId);\n const extraction = extractDocxText(document);\n const rewrites: DocxBlockRewrite[] = [];\n const budget = { candidateCount: 0 };\n let restoredPlaceholderCount = 0;\n for (const block of extraction.blocks) {\n const replacements = planBlockRestoration({\n text: block.text,\n encodedSessionId,\n restoreCandidate,\n budget,\n });\n if (replacements.length === 0) {\n continue;\n }\n restoredPlaceholderCount += replacements.length;\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements,\n });\n }\n assertSessionAvailable();\n const restored = rewriteDocxText(document, rewrites);\n return {\n document: restored.document,\n sessionId,\n restoredBlockCount: restored.rewrittenBlockCount,\n restoredPlaceholderCount,\n coverage: docxWorkflowCoverage(extraction.coverage),\n };\n};\n","import { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_ANONYMIZATION_ERROR_CODES,\n DOCX_COVERAGE_MODES,\n type AnonymizeDocxOptions,\n type DocxAnonymizationErrorCode,\n type DocxAnonymizationResult,\n type DocxBlockCallerDetections,\n type DocxBlockRewrite,\n} from \"./types\";\n\nexport const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS = 1_000_000;\n\nexport class DocxAnonymizationError extends Error {\n readonly code: DocxAnonymizationErrorCode;\n\n constructor(code: DocxAnonymizationErrorCode, message: string) {\n super(message);\n this.name = \"DocxAnonymizationError\";\n this.code = code;\n }\n}\n\nconst anonymizationError = (\n code: DocxAnonymizationErrorCode,\n message: string,\n): DocxAnonymizationError => new DocxAnonymizationError(code, message);\n\ntype DetectionPlan = {\n detectionsByLocation: ReadonlyMap<string, DocxBlockCallerDetections>;\n callerDetectionCount: number;\n};\n\nconst planCallerDetections = (\n extractionBlocks: ReturnType<typeof extractDocxText>[\"blocks\"],\n inputs: readonly DocxBlockCallerDetections[],\n): DetectionPlan => {\n const blocksByLocation = new Map(\n extractionBlocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const detectionsByLocation = new Map<string, DocxBlockCallerDetections>();\n let callerDetectionCount = 0;\n for (const input of inputs) {\n const key = docxLocationKey(input.location);\n if (detectionsByLocation.has(key)) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"Each DOCX block may have only one caller-detection input\",\n );\n }\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, input.location) ||\n block.text !== input.expectedText\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX caller-detection location or expected text no longer matches\",\n );\n }\n if (\n input.detections.length >\n DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`,\n );\n }\n detectionsByLocation.set(key, input);\n callerDetectionCount += input.detections.length;\n }\n return { detectionsByLocation, callerDetectionCount };\n};\n\nexport const anonymizeDocx = ({\n document,\n session,\n expectedSessionId,\n policy,\n callerDetections = [],\n observedAtEpochSeconds,\n}: AnonymizeDocxOptions): DocxAnonymizationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.sessionMismatch,\n \"DOCX anonymization session does not match the expected session\",\n );\n }\n\n const extraction = extractDocxText(document);\n const coverage = docxWorkflowCoverage(extraction.coverage);\n if (\n coverage.status === \"partial\" &&\n policy.coverage.mode === DOCX_COVERAGE_MODES.requireFull\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.incompleteCoverage,\n \"DOCX contains content outside the fully supported anonymization coverage\",\n );\n }\n\n const { detectionsByLocation, callerDetectionCount } = planCallerDetections(\n extraction.blocks,\n callerDetections,\n );\n const plan = session.planTextBatchWithCallerDetections({\n inputs: extraction.blocks.map((block) => ({\n fullText: block.text,\n detections:\n detectionsByLocation.get(docxLocationKey(block.location))?.detections ??\n [],\n })),\n ...(policy.operators === undefined ? {} : { operators: policy.operators }),\n ...(observedAtEpochSeconds === undefined ? {} : { observedAtEpochSeconds }),\n });\n if (plan.blocks.length !== extraction.blocks.length) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan does not match the extracted block count\",\n );\n }\n\n const rewrites: DocxBlockRewrite[] = [];\n let entityCount = 0;\n let retainedCallerDetectionCount = 0;\n for (const [index, block] of extraction.blocks.entries()) {\n const blockPlan = plan.blocks.at(index);\n if (blockPlan === undefined) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan is missing an extracted block\",\n );\n }\n entityCount += blockPlan.entityCount;\n retainedCallerDetectionCount += blockPlan.callerEntityCount;\n if (blockPlan.replacements.length === 0) {\n continue;\n }\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements: blockPlan.replacements,\n });\n }\n\n const rewritten = rewriteDocxText(document, rewrites);\n plan.commit();\n return {\n document: rewritten.document,\n summary: {\n contractVersion: 1,\n sessionId,\n blockCount: extraction.blocks.length,\n rewrittenBlockCount: rewritten.rewrittenBlockCount,\n appliedReplacementCount: rewritten.appliedReplacementCount,\n entityCount,\n callerDetectionCount,\n retainedCallerDetectionCount,\n coverage,\n },\n };\n};\n"],"mappings":";;;AAOA,MAAa,kBAAkB;CAC7B,UAAU;CACV,UAAU;CACV,QAAQ;CACR,WAAW;CACX,QAAQ;CACR,cAAc;AAChB;AAsGA,MAAa,sBAAsB;CACjC,cAAc;CACd,aAAa;AACf;AA0EA,MAAa,iCAAiC;CAC5C,oBAAoB;CACpB,yBAAyB;CACzB,iBAAiB;AACnB;AAyBA,MAAa,+BAA+B;CAC1C,oBAAoB;CACpB,gBAAgB;CAChB,0BAA0B;CAC1B,iBAAiB;AACnB;AAKA,MAAa,2BAA2B;CACtC,oBAAoB;CACpB,sBAAsB;CACtB,iBAAiB;CACjB,wBAAwB;AAC1B;AAKA,MAAa,8BAA8B;CACzC,sBAAsB;CACtB,gBAAgB;CAChB,gBAAgB;CAChB,YAAY;CACZ,iBAAiB;CACjB,2BAA2B;AAC7B;;;ACzOA,MAAa,mCAAmC;AAChD,MAAa,yBAAyB,KAAK,OAAO;AAClD,MAAa,uBAAuB,KAAK,OAAO;AAChD,MAAa,8BAA8B,MAAM,OAAO;AACxD,MAAa,qBAAqB;AAClC,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAC7B,MAAM,yBAAyB;AAE/B,MAAM,qBAAqB;AAC3B,MAAM,0BAA0B;AAChC,MAAM,0BACJ;AACF,MAAM,kDAAkC,IAAI,IAAI,CAC9C,oDACA,8DACF,CAAC;AACD,MAAM,qCACJ;AACF,MAAM,kCACJ;CACE,gBAAgB,gBAAgB;CAChC,qBAAqB,gBAAgB;CACrC,gBAAgB,gBAAgB;CAChC,cAAc,gBAAgB;CAC9B,iBAAiB,gBAAgB;CACjC,cAAc,gBAAgB;AAChC;AACF,MAAMA,8CAA4B,IAAI,IAAI,CACxC,oDACA,8DACF,CAAC;AACD,MAAM,0CAA0B,IAAI,IAAI,CACtC,2DACA,qEACF,CAAC;AACD,MAAM,qCAAqC,IAAI,IAC7C,CAAC,GAAG,uBAAuB,CAAC,CAAC,KAC1B,cAAc,GAAG,UAAU,gBAC9B,CACF;AACA,MAAM,kDAAkC,IAAI,IAAI,CAC9C,wDACA,6DACF,CAAC;AAED,IAAa,sBAAb,cAAyC,MAAM;CAC7C;CAEA,YAAY,MAA+B,SAAiB;EAC1D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAgCA,MAAM,kBAAkB,YACtB,IAAI,oBAAoB,4BAA4B,gBAAgB,OAAO;AAE7E,MAAM,kBAAkB,UAAwB;CAC9C,IAAI,QAAA,KACF;CAEF,MAAM,IAAI,oBACR,4BAA4B,2BAC5B,8CACF;AACF;AAEA,MAAM,iBAAiB,SACrB,KAAK,SAAS,KACd,CAAC,KAAK,WAAW,GAAG,KACpB,CAAC,KAAK,SAAS,IAAI,KACnB,CAAC,KAAK,MAAM,GAAG,CAAC,CAAC,SAAS,IAAI,KAC9B,CAAC,KAAK,SAAS,IAAI;AAarB,MAAM,iBAAiB,EACrB,QACA,MACA,wBACmC;CACnC,OAAO,cAAc;CACrB,IAAI,OAAO,aAAa,kBACtB,MAAM,IAAI,oBACR,4BAA4B,2BAC5B,sCAAsC,iBAAiB,SACzD;CAEF,IAAI,CAAC,cAAc,KAAK,IAAI,GAC1B,MAAM,IAAI,oBACR,4BAA4B,iBAC5B,4CACF;CAEF,IAAI,KAAK,eAAA,UACP,MAAM,IAAI,oBACR,4BAA4B,2BAC5B,gCAAgC,qBAAqB,OACvD;CAEF,OAAO,qBAAqB,KAAK;CACjC,IAAI,OAAO,oBAAA,WACT,MAAM,IAAI,oBACR,4BAA4B,2BAC5B,iCAAiC,4BAA4B,oBAC/D;CAEF,OACE,qBACA,KAAK,SAAS,sBACd,KAAK,SAAS,2BACb,KAAK,KAAK,WAAW,OAAO,KAAK,KAAK,KAAK,SAAS,MAAM;AAE/D;AAEA,MAAa,oBACX,SACA,oBAAoB,UACW;CAC/B,IAAI,QAAQ,aAAA,UACV,MAAM,IAAI,oBACR,4BAA4B,sBAC5B,iCAAiC,uBAAuB,OAC1D;CAEF,MAAM,SAAwB;EAAE,YAAY;EAAG,mBAAmB;CAAE;CACpE,IAAI;EACF,OAAO,UAAU,SAAS,EACxB,SAAS,SAAS,cAAc;GAAE;GAAQ;GAAM;EAAkB,CAAC,EACrE,CAAC;CACH,SAAS,OAAO;EACd,IAAI,iBAAiB,qBACnB,MAAM;EAER,MAAM,IAAI,oBACR,4BAA4B,gBAC5B,+CACF;CACF;AACF;AAEA,MAAM,aAAa,OAAmB,SAAyB;CAC7D,IAAI;EACF,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK;CAC/D,QAAQ;EACN,MAAM,IAAI,oBACR,4BAA4B,YAC5B,qCAAqC,MACvC;CACF;AACF;AAEA,MAAM,wBACJ,KACA,WACA,eACkB;CAClB,KAAK,MAAM,aAAa,OAAO,OAAO,IAAI,UAAU,GAClD,IACE,UAAU,UAAU,cACnB,eAAe,KAAA,KAAa,WAAW,IAAI,UAAU,GAAG,IAEzD,OAAO,UAAU;CAGrB,OAAO;AACT;AAEA,MAAM,qBAAqB,QAAmC;CAC5D,MAAM,QAA2B,CAAC;CAClC,MAAM,wBAAQ,IAAI,IAAY;CAC9B,MAAM,SAAS,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;CAC9C,IAAI,aAA2B;CAC/B,IAAI,QAAQ;CACZ,OAAO,GAAG,UAAU,UAAU;EAC5B,aAAa;CACf,CAAC;CACD,OAAO,GAAG,iBAAiB;EACzB,MAAM,eACJ,uDACF;CACF,CAAC;CACD,OAAO,GAAG,YAAY,QAAQ;EAC5B,eAAe,KAAK;EACpB,SAAS;EACT,IAAI,IAAI,UAAU,cAAc,IAAI,QAAQ,yBAC1C;EAEF,MAAM,UAAU,qBAAqB,KAAK,UAAU;EACpD,MAAM,cAAc,qBAAqB,KAAK,aAAa;EAC3D,IAAI,YAAY,QAAQ,gBAAgB,MACtC,MAAM,eAAe,0CAA0C;EAEjE,MAAM,OAAO,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;EAC1D,IAAI,CAAC,cAAc,IAAI,GACrB,MAAM,eAAe,+CAA+C;EAEtE,IAAI,MAAM,IAAI,IAAI,GAChB,MAAM,eACJ,oDACF;EAEF,MAAM,IAAI,IAAI;EACd,MAAM,KAAK;GAAE;GAAM;EAAY,CAAC;CAClC,CAAC;CACD,OAAO,GAAG,kBAAkB;EAC1B,SAAS;CACX,CAAC;CACD,IAAI;EACF,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;CAC1B,SAAS,OAAO;EACd,IAAI,iBAAiB,qBACnB,MAAM;EAER,aAAa,iBAAiB,QAAQ,wBAAQ,IAAI,MAAM,aAAa;CACvE;CACA,IAAI,eAAe,MACjB,MAAM,IAAI,oBACR,4BAA4B,YAC5B,sCACF;CAEF,OAAO;AACT;AAEA,MAAM,2BAA2B,QAAwB;CACvD,MAAM,UAAoB,CAAC;CAC3B,MAAM,SAAS,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;CAC9C,IAAI,aAA2B;CAC/B,IAAI,QAAQ;CACZ,OAAO,GAAG,UAAU,UAAU;EAC5B,aAAa;CACf,CAAC;CACD,OAAO,GAAG,iBAAiB;EACzB,MAAM,eACJ,uDACF;CACF,CAAC;CACD,OAAO,GAAG,YAAY,QAAQ;EAC5B,eAAe,KAAK;EACpB,SAAS;EACT,IACE,IAAI,UAAU,kBACd,CAAC,gCAAgC,IAAI,IAAI,GAAG,GAE5C;EAEF,MAAM,OAAO,qBAAqB,KAAK,MAAM;EAC7C,IAAI,SAAS,QAAQ,CAAC,mCAAmC,IAAI,IAAI,GAC/D;EAEF,MAAM,aAAa,qBAAqB,KAAK,YAAY;EACzD,MAAM,YAAY,qBAAqB,KAAK,QAAQ;EACpD,IAAI,eAAe,cAAc,cAAc,MAC7C,MAAM,eAAe,kDAAkD;EAEzE,MAAM,SAAS,UAAU,WAAW,GAAG,IAAI,UAAU,MAAM,CAAC,IAAI;EAChE,IAAI,CAAC,cAAc,MAAM,KAAK,OAAO,SAAS,GAAG,GAC/C,MAAM,eACJ,sDACF;EAEF,QAAQ,KAAK,MAAM;CACrB,CAAC;CACD,OAAO,GAAG,kBAAkB;EAC1B,SAAS;CACX,CAAC;CACD,IAAI;EACF,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;CAC1B,SAAS,OAAO;EACd,IAAI,iBAAiB,qBACnB,MAAM;EAER,aAAa,iBAAiB,QAAQ,wBAAQ,IAAI,MAAM,aAAa;CACvE;CACA,IAAI,eAAe,MACjB,MAAM,IAAI,oBACR,4BAA4B,YAC5B,2CACF;CAEF,IAAI,QAAQ,WAAW,GACrB,MAAM,eACJ,kEACF;CAEF,MAAM,SAAS,QAAQ,GAAG,CAAC;CAC3B,IAAI,WAAW,KAAA,GACb,MAAM,eAAe,gDAAgD;CAEvE,OAAO;AACT;AAEA,MAAM,gBAAgB,EACpB,aACA,WACsC;CACtC,IAAI,CAAC,YAAY,WAAW,kCAAkC,GAC5D,OAAO;CAET,MAAM,SAAS,YAAY,MAAM,EAAyC;CAC1E,MAAM,OAAO,gCAAgC;CAC7C,OAAO,SAAS,KAAA,IAAY,OAAO;EAAE;EAAM;CAAK;AAClD;AAEA,MAAM,aAAa,KAAiB,UAClC,IAAI,UAAU,SAASA,4BAA0B,IAAI,IAAI,GAAG;AAE9D,MAAM,oBACJ,OACA,UACwB;CACxB,KAAK,IAAI,QAAQ,MAAM,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG;EACzD,MAAM,QAAQ,MAAM,GAAG,KAAK;EAC5B,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,KAAK,KAAK,GACnD,OAAO;CAEX;CACA,OAAO;AACT;AAEA,MAAM,iBACJ,MACA,YACA,eACA,UAC8B;CAC9B,MAAM,UAAU,iBAAiB,OAAO,aAAa;CACrD,IAAI,YAAY,MACd,OAAO;EACL,MAAM;EACN;EACA;EACA,SAAS;EACT,aAAa,QAAQ;CACvB;CAEF,MAAM,OAAO,iBAAiB,OAAO,IAAI;CACzC,MAAM,MAAM,iBAAiB,OAAO,IAAI;CACxC,MAAM,QAAQ,iBAAiB,OAAO,KAAK;CAC3C,IAAI,SAAS,QAAQ,QAAQ,QAAQ,UAAU,MAC7C,OAAO;EACL,MAAM;EACN;EACA;EACA,SAAS;EACT,WAAW,MAAM;EACjB,SAAS,IAAI;EACb,UAAU,KAAK;CACjB;CAEF,OAAO;EACL,MAAM;EACN;EACA;EACA,SAAS;CACX;AACF;AAEA,MAAM,kBACJ,QAC4D;CAC5D,IAAI,CAACA,4BAA0B,IAAI,IAAI,GAAG,GACxC,OAAO;CAUT,MAAM,WAAW;EALf,KAAK;EACL,KAAK;EACL,UAAU;EACV,QAAQ;CAEe,EAAE,IAAI;CAC/B,OAAO,aAAa,KAAA,IAAY,OAAO;EAAE,MAAM;EAAY;CAAS;AACtE;AAEA,MAAM,kBACJ,UACwB;CACxB,MAAM,WAAgC,CAAC;CACvC,KAAK,MAAM,EAAE,SAAS,OAAO;EAC3B,IAAI,UAAU,KAAK,WAAW,GAC5B,SAAS,KAAK;GACZ,MAAM;GACN,gBAAgB,qBACd,KACA,MACA,uBACF;GACA,QAAQ,qBAAqB,KAAK,UAAUA,2BAAyB;EACvE,CAAC;EAEH,MAAM,WAAW,eAAe,GAAG;EACnC,IAAI,aAAa,MACf,SAAS,KAAK,QAAQ;CAE1B;CACA,OAAO;AACT;AAEA,MAAM,iBACJ,OACA,QACA,OACA,QACA,MACA,UACS;CACT,IAAI,MAAM,WAAW,GACnB;CAEF,IAAI,OAAO,gBAAgB,wBACzB,MAAM,IAAI,oBACR,4BAA4B,2BAC5B,yCAAyC,uBAAuB,eAClE;CAEF,OAAO,gBAAgB;CACvB,MAAM,QAAQ,MAAM,KAAK;CACzB,MAAM,QAAQ;CACd,MAAM,SAAS,KAAK;EAClB;EACA,KAAK,MAAM,KAAK;EAChB;EACA,UAAU,eAAe,KAAK;EAC9B,SAAS;CACX,CAAC;AACH;AAEA,MAAM,eAAe,MAAgB,QAAgC;CACnE,MAAM,SAA0B,CAAC;CACjC,MAAM,QAAwB,CAAC;CAC/B,MAAM,aAA6B,CAAC;CACpC,IAAI,iBAAiB;CACrB,IAAI,cAAc;CAClB,IAAI,kBAA4C;CAChD,IAAI,aAA2B;CAC/B,IAAI,yBAAyB;CAC7B,IAAI,mCAAmC;CACvC,IAAI,mCAAmC;CACvC,MAAM,aAA6B,EAAE,cAAc,EAAE;CAErD,MAAM,SAAS,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;CAC9C,OAAO,GAAG,UAAU,UAAU;EAC5B,aAAa;CACf,CAAC;CACD,OAAO,GAAG,iBAAiB;EACzB,MAAM,eACJ,uDACF;CACF,CAAC;CACD,OAAO,GAAG,YAAY,QAAQ;EAC5B,eAAe,MAAM,MAAM;EAC3B,MAAM,SAAS,MAAM,GAAG,EAAE;EAC1B,MAAM,aAAa,QAAQ,kBAAkB;EAC7C,IAAI,WAAW,KAAA,GACb,OAAO,kBAAkB;EAE3B,MAAM,OAAO,CAAC,GAAI,QAAQ,QAAQ,CAAC,GAAI,UAAU;EACjD,IAAI,UAAU,KAAK,GAAG,GAAG;GACvB,IAAI,kBAAkB,sBACpB,MAAM,IAAI,oBACR,4BAA4B,2BAC5B,yCAAyC,qBAAqB,aAChE;GAEF,WAAW,KAAK;IACd,MAAM;IACN,UAAU,CAAC;IACX,UAAU,cAAc,MAAM,gBAAgB,MAAM,KAAK;GAC3D,CAAC;GACD,kBAAkB;EACpB;EACA,MAAM,KAAK;GAAE;GAAK;GAAM,gBAAgB;EAAE,CAAC;EAC3C,IAAI,UAAU,KAAK,GAAG,KAAK,UAAU,KAAK,SAAS,GAAG;GACpD,cAAc;GACd,kBAAkB;EACpB;EACA,MAAM,eAAe,WAAW,GAAG,EAAE;EACrC,IAAI,iBAAiB,KAAA,KAAa,UAAU,KAAK,KAAK,GACpD,cAAc,cAAc,YAAY,KAAM,OAAO,MAAM,KAAK;EAElE,IACE,iBAAiB,KAAA,MAChB,UAAU,KAAK,IAAI,KAAK,UAAU,KAAK,IAAI,IAE5C,cAAc,cAAc,YAAY,MAAM,SAAS,MAAM,KAAK;EAEpE,IAAI,UAAU,KAAK,KAAK,GACtB,0BAA0B;EAE5B,IAAI,UAAU,KAAK,WAAW,KAAK,UAAU,KAAK,WAAW,GAC3D,oCAAoC;EAEtC,IACE,IAAI,UAAU,sBACd,gCAAgC,IAAI,IAAI,GAAG,GAE3C,oCAAoC;CAExC,CAAC;CACD,OAAO,GAAG,SAAS,SAAS;EAC1B,IAAI,oBAAoB,MACtB,eAAe;CAEnB,CAAC;CACD,OAAO,GAAG,UAAU,SAAS;EAC3B,IAAI,oBAAoB,MACtB,eAAe;CAEnB,CAAC;CACD,OAAO,GAAG,aAAa,QAAQ;EAC7B,MAAM,QAAQ,MAAM,GAAG,EAAE;EACzB,IAAI,UAAU,KAAA,KAAa,MAAM,QAAQ,KACvC,MAAM,eAAe,wCAAwC;EAE/D,IACE,oBAAoB,SACnB,UAAU,KAAK,GAAG,KAAK,UAAU,KAAK,SAAS,IAChD;GACA,MAAM,eAAe,WAAW,GAAG,EAAE;GACrC,IAAI,iBAAiB,KAAA;QACf,YAAY,SAAS,GACvB,MAAM,eAAe,kCAAkC;GAAA,OAGzD,cACE,cACA,YACA,aACA,QACA,iBACA,KACF;GAEF,cAAc;GACd,kBAAkB;EACpB;EACA,IAAI,UAAU,KAAK,GAAG,GAAG;GACvB,MAAM,iBAAiB,WAAW,IAAI;GACtC,IAAI,mBAAmB,KAAA,GACrB,MAAM,eAAe,qCAAqC;GAE5D,OAAO,KAAK,cAAc;EAC5B;EACA,MAAM,IAAI;CACZ,CAAC;CACD,IAAI;EACF,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;CAC1B,SAAS,OAAO;EACd,IAAI,iBAAiB,qBACnB,MAAM;EAER,aAAa,iBAAiB,QAAQ,wBAAQ,IAAI,MAAM,aAAa;CACvE;CACA,IAAI,eAAe,MACjB,MAAM,IAAI,oBACR,4BAA4B,YAC5B,+BAA+B,KAAK,MACtC;CAGF,OAAO,MACJ,MAAM,UAAU,KAAK,SAAS,aAAa,MAAM,SAAS,UAC7D;CAEA,IAAI,4BAA4B;CAChC,IAAI,2BAA2B;CAC/B,KAAK,MAAM,EAAE,cAAc,QACzB,KAAK,MAAM,EAAE,cAAc,UAAU;EACnC,IAAI,SAAS,MAAM,YAAY,QAAQ,SAAS,WAAW,GACzD,6BAA6B;EAE/B,IAAI,SAAS,MAAM,YAAY,QAAQ,SAAS,UAAU,GACxD,4BAA4B;CAEhC;CAEF,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;CACF;AACF;AAEA,MAAa,mBAAmB,YAAwC;CACtE,MAAM,UAAU,iBAAiB,OAAO;CACxC,MAAM,oBAAoB,QAAQ;CAClC,IAAI,sBAAsB,KAAA,GACxB,MAAM,eAAe,6CAA6C;CAEpE,MAAM,eAAe,kBACnB,UAAU,mBAAmB,kBAAkB,CACjD;CACA,MAAM,yBAAyB,QAAQ;CACvC,IAAI,2BAA2B,KAAA,GAC7B,MAAM,eAAe,qCAAqC;CAE5D,MAAM,qBAAqB,wBACzB,UAAU,wBAAwB,uBAAuB,CAC3D;CACA,MAAM,iBAAiB,aACpB,IAAI,YAAY,CAAC,CACjB,QAAQ,SAA2B,SAAS,IAAI;CACnD,IACE,eAAe,QAAQ,SAAS,KAAK,SAAS,gBAAgB,YAAY,CAAC,CACxE,WAAW,GAEd,MAAM,eAAe,qDAAqD;CAK5E,IAHqB,eAAe,MACjC,SAAS,KAAK,SAAS,gBAAgB,YAE3B,CAAC,EAAE,SAAS,oBACzB,MAAM,eACJ,+DACF;CAGF,MAAM,SAA0B,CAAC;CACjC,MAAM,gBAAoC,CAAC;CAC3C,IAAI,4BAA4B;CAChC,IAAI,2BAA2B;CAC/B,IAAI,yBAAyB;CAC7B,IAAI,mCAAmC;CACvC,IAAI,mCAAmC;CACvC,IAAI,mBAAmB;CACvB,KAAK,MAAM,QAAQ,gBAAgB;EACjC,MAAM,QAAQ,QAAQ,KAAK;EAC3B,IAAI,UAAU,KAAA,GACZ,MAAM,eACJ,0CAA0C,KAAK,MACjD;EAEF,MAAM,YAAY,YAAY,MAAM,UAAU,OAAO,KAAK,IAAI,CAAC;EAC/D,IAAI,OAAO,SAAS,UAAU,OAAO,SAAS,sBAC5C,MAAM,IAAI,oBACR,4BAA4B,2BAC5B,4CAA4C,qBAAqB,aACnE;EAEF,MAAM,wBAAwB,UAAU,OAAO,QAC5C,OAAO,UAAU,QAAQ,MAAM,SAAS,QACzC,CACF;EACA,IAAI,mBAAmB,wBAAwB,wBAC7C,MAAM,IAAI,oBACR,4BAA4B,2BAC5B,4CAA4C,uBAAuB,eACrE;EAEF,oBAAoB;EACpB,OAAO,KAAK,GAAG,UAAU,MAAM;EAC/B,cAAc,KAAK;GACjB,QAAQ;GACR;GACA,YAAY,UAAU,OAAO;EAC/B,CAAC;EACD,6BAA6B,UAAU;EACvC,4BAA4B,UAAU;EACtC,0BAA0B,UAAU;EACpC,oCACE,UAAU;EACZ,oCACE,UAAU;CACd;CAEA,KAAK,MAAM,EAAE,aAAa,UAAU,cAClC,IACE,YAAY,WAAW,kCAAkC,KACzD,aAAa;EAAE;EAAa;CAAK,CAAC,MAAM,MAExC,cAAc,KAAK;EACjB,QAAQ;EACR;EACA;EACA,QAAQ;CACV,CAAC;CAIL,OAAO;EACL,iBAAA;EACA;EACA,UAAU;GACR,OAAO;GACP;GACA;GACA;GACA;GACA;EACF;CACF;AACF;;;AChvBA,MAAM,eACJ,MACA,UAEA,KAAK,WAAW,MAAM,UACtB,KAAK,OAAO,OAAO,UAAU,UAAU,MAAM,GAAG,KAAK,CAAC;AAExD,MAAa,sBACX,MACA,UACY;CACZ,IACE,KAAK,SAAS,MAAM,QACpB,KAAK,KAAK,SAAS,MAAM,KAAK,QAC9B,KAAK,KAAK,SAAS,MAAM,KAAK,QAC9B,KAAK,eAAe,MAAM,cAC1B,CAAC,YAAY,KAAK,SAAS,MAAM,OAAO,GAExC,OAAO;CAET,IAAI,KAAK,SAAS,eAAe,MAAM,SAAS,aAC9C,OAAO;CAET,IACE,KAAK,SAAS,0BACd,MAAM,SAAS,wBAEf,OACE,YAAY,KAAK,WAAW,MAAM,SAAS,KAC3C,YAAY,KAAK,SAAS,MAAM,OAAO,KACvC,YAAY,KAAK,UAAU,MAAM,QAAQ;CAG7C,IACE,KAAK,SAAS,wBACd,MAAM,SAAS,sBAEf,OAAO,YAAY,KAAK,aAAa,MAAM,WAAW;CAExD,OAAO;AACT;AAEA,MAAa,mBAAmB,EAC9B,YACA,WAC+B,GAAG,KAAK,KAAK,IAAI;;;ACzBlD,MAAM,4CAA4B,IAAI,IAAI,CACxC,oDACA,8DACF,CAAC;AACD,MAAM,gBAAgB;AACtB,MAAM,wBAAwB;AAC9B,MAAM,wBAAwB;AAE9B,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CAEA,YAAY,MAA4B,SAAiB;EACvD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAkBA,MAAM,gBACJ,MACA,YACqB,IAAI,iBAAiB,MAAM,OAAO;AAEzD,MAAM,WAAW,SAAoC,KAAK,KAAK,GAAG;AAElE,MAAM,kBAAkB,UAA2B;CACjD,KAAK,MAAM,aAAa,OAAO;EAC7B,MAAM,YAAY,UAAU,YAAY,CAAC;EACzC,IACE,cAAc,KAAA,KACb,cAAc,KACb,cAAc,MACd,cAAc,OACb,YAAY,MACV,YAAY,SAAU,YAAY,SAClC,YAAY,SAAU,YAAY,SACnC,YAAY,UAEhB,OAAO;CAEX;CACA,OAAO;AACT;AAEA,MAAM,mBAAmB,OAAe,WAA4B;CAClE,IAAI,WAAW,KAAK,WAAW,MAAM,QACnC,OAAO;CAET,MAAM,WAAW,MAAM,WAAW,SAAS,CAAC;CAC5C,MAAM,OAAO,MAAM,WAAW,MAAM;CACpC,OAAO,EACL,YAAY,SACZ,YAAY,SACZ,QAAQ,SACR,QAAQ;AAEZ;AAEA,MAAM,uBACJ,aACA,cACS;CACT,IACE,CAAC,OAAO,cAAc,YAAY,KAAK,KACvC,CAAC,OAAO,cAAc,YAAY,GAAG,KACrC,YAAY,QAAQ,KACpB,YAAY,SAAS,YAAY,OACjC,YAAY,MAAM,UAAU,UAC5B,CAAC,gBAAgB,WAAW,YAAY,KAAK,KAC7C,CAAC,gBAAgB,WAAW,YAAY,GAAG,GAE3C,MAAM,aACJ,yBAAyB,oBACzB,qFACF;CAEF,IAAI,CAAC,eAAe,YAAY,WAAW,GACzC,MAAM,aACJ,yBAAyB,oBACzB,8DACF;CAEF,IAAI,QAAQ,YAAY,WAAW,CAAC,CAAC,aAAA,UACnC,MAAM,aACJ,yBAAyB,sBACzB,yCAAyC,qBAAqB,aAChE;AAEJ;AAEA,MAAM,uBACJ,OACA,gBAC+B;CAC/B,MAAM,WAAW,MAAM,SAAS,QAC7B,EAAE,KAAK,YAAY,QAAQ,YAAY,OAAO,MAAM,YAAY,KACnE;CACA,IAAI,SAAS,YAAY;CACzB,KAAK,MAAM,WAAW,UAAU;EAC9B,IACE,QAAQ,WAAW,UACnB,QAAQ,QAAQ,UAChB,QAAQ,SAAS,MAAM,YAAY,QAAQ,SAAS,UAAU,GAE9D,MAAM,aACJ,yBAAyB,wBACzB,0EACF;EAEF,SAAS,KAAK,IAAI,YAAY,KAAK,QAAQ,GAAG;CAChD;CACA,IAAI,SAAS,WAAW,KAAK,WAAW,YAAY,KAClD,MAAM,aACJ,yBAAyB,wBACzB,0EACF;CAEF,OAAO;AACT;AAEA,MAAM,oBACJ,OACA,YACqB;CACrB,MAAM,eAAe,CAAC,GAAG,QAAQ,YAAY,CAAC,CAAC,MAC5C,MAAM,UAAU,KAAK,QAAQ,MAAM,KACtC;CACA,KAAK,MAAM,CAAC,OAAO,gBAAgB,aAAa,QAAQ,GAAG;EACzD,oBAAoB,aAAa,MAAM,IAAI;EAC3C,MAAM,WAAW,UAAU,IAAI,KAAA,IAAY,aAAa,GAAG,QAAQ,CAAC;EACpE,IAAI,aAAa,KAAA,KAAa,SAAS,MAAM,YAAY,OACvD,MAAM,aACJ,yBAAyB,oBACzB,yCACF;CAEJ;CAEA,MAAM,yBAAS,IAAI,IAA4B;CAC/C,MAAM,iCAAiB,IAAI,IAAoB;CAC/C,KAAK,MAAM,WAAW,MAAM,UAAU;EACpC,IAAI,QAAQ,WAAW,QACrB;EAEF,OAAO,IAAI,QAAQ,QAAQ,OAAO,GAAG;GACnC,MAAM,QAAQ;GACd,OAAO,MAAM,KAAK,MAAM,QAAQ,OAAO,QAAQ,GAAG;EACpD,CAAC;EACD,eAAe,IACb,QAAQ,QAAQ,OAAO,GACvB,MAAM,KAAK,MAAM,QAAQ,OAAO,QAAQ,GAAG,CAC7C;CACF;CAEA,KAAK,MAAM,eAAe,aAAa,WAAW,GAAG;EACnD,MAAM,WAAW,oBAAoB,OAAO,WAAW;EACvD,MAAM,QAAQ,SAAS,GAAG,CAAC;EAC3B,MAAM,OAAO,SAAS,GAAG,EAAE;EAC3B,IAAI,UAAU,KAAA,KAAa,SAAS,KAAA,GAClC,MAAM,aACJ,yBAAyB,wBACzB,gDACF;EAEF,MAAM,cAAc,OAAO,IAAI,QAAQ,MAAM,OAAO,CAAC;EACrD,MAAM,aAAa,OAAO,IAAI,QAAQ,KAAK,OAAO,CAAC;EACnD,IAAI,gBAAgB,KAAA,KAAa,eAAe,KAAA,GAC9C,MAAM,aACJ,yBAAyB,wBACzB,6CACF;EAEF,MAAM,aAAa,YAAY,QAAQ,MAAM;EAC7C,MAAM,UAAU,YAAY,MAAM,KAAK;EACvC,IAAI,UAAU,MAAM;GAClB,YAAY,QACV,YAAY,MAAM,MAAM,GAAG,UAAU,IACrC,YAAY,cACZ,YAAY,MAAM,MAAM,OAAO;GACjC;EACF;EACA,YAAY,QACV,YAAY,MAAM,MAAM,GAAG,UAAU,IAAI,YAAY;EACvD,KAAK,MAAM,WAAW,SAAS,MAAM,GAAG,EAAE,GAAG;GAC3C,MAAM,SAAS,OAAO,IAAI,QAAQ,QAAQ,OAAO,CAAC;GAClD,IAAI,WAAW,KAAA,GACb,OAAO,QAAQ;EAEnB;EACA,WAAW,QAAQ,WAAW,MAAM,MAAM,OAAO;CACnD;CACA,OAAO,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CACzB,QAAQ,CAAC,KAAK,YAAY,OAAO,UAAU,eAAe,IAAI,GAAG,CAAC,CAAC,CACnE,KAAK,GAAG,YAAY,MAAM;AAC/B;AAEA,MAAM,iBAAiB,UACrB,MACG,WAAW,KAAK,OAAO,CAAC,CACxB,WAAW,KAAK,MAAM,CAAC,CACvB,WAAW,KAAK,MAAM;AAE3B,MAAM,0BAA0B,UAC9B,WAAW,KAAK,KAAK;AAEvB,MAAM,iBAAiB,QACrB,0BAA0B,IAAI,IAAI,GAAG,MACpC,IAAI,UAAU,OAAO,IAAI,UAAU;AAEtC,MAAM,qBAAqB,QACzB,OAAO,OAAO,IAAI,UAAU,CAAC,CAAC,MAC3B,cACC,UAAU,QAAQ,iBAClB,UAAU,UAAU,WACpB,UAAU,UAAU,UACxB;AAQF,MAAM,uBAAuB,EAC3B,KACA,cACA,qBACwC;CACxC,KAAK,IAAI,QAAQ,iBAAiB,GAAG,SAAS,cAAc,SAAS,GACnE,IAAI,IAAI,WAAW,OAAO,IAAI,QAAQ,OAAO,KAC3C,OAAO;CAGX,MAAM,aACJ,yBAAyB,iBACzB,qDACF;AACF;AAEA,MAAM,kBACJ,KACA,YACW;CACX,MAAM,gBAAgB,IAAI,IACxB,QAAQ,KAAK,WAAW,CAAC,QAAQ,OAAO,IAAI,GAAG,MAAM,CAAC,CACxD;CACA,MAAM,6BAAa,IAAI,IAAY;CACnC,MAAM,UAAsB,CAAC;CAC7B,MAAM,QAAwB,CAAC;CAC/B,IAAI;CAGJ,IAAI,aAA2B;CAC/B,MAAM,SAAS,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;CAC9C,OAAO,GAAG,UAAU,UAAU;EAC5B,aAAa;CACf,CAAC;CACD,OAAO,GAAG,YAAY,QAAQ;EAC5B,IAAI,MAAM,UAAA,KACR,MAAM,aACJ,yBAAyB,sBACzB,8CACF;EAEF,MAAM,SAAS,MAAM,GAAG,EAAE;EAC1B,MAAM,aAAa,QAAQ,kBAAkB;EAC7C,IAAI,WAAW,KAAA,GACb,OAAO,kBAAkB;EAE3B,MAAM,OAAO,CAAC,GAAI,QAAQ,QAAQ,CAAC,GAAI,UAAU;EACjD,MAAM,KAAK;GAAE;GAAM,gBAAgB;EAAE,CAAC;EACtC,MAAM,MAAM,QAAQ,IAAI;EACxB,IAAI,cAAc,GAAG,KAAK,cAAc,IAAI,GAAG,GAAG;GAChD,IAAI,IAAI,eACN,MAAM,aACJ,yBAAyB,wBACzB,0DACF;GAEF,aAAa;IAAE;IAAK,cAAc,OAAO;IAAU;GAAI;EACzD;CACF,CAAC;CACD,OAAO,GAAG,aAAa,QAAQ;EAC7B,IAAI,YAAY,QAAQ,KAAK;GAC3B,MAAM,SAAS,cAAc,IAAI,WAAW,GAAG;GAC/C,IAAI,WAAW,KAAA,GAAW;IACxB,MAAM,aAAa,oBAAoB;KACrC;KACA,cAAc,WAAW;KACzB,gBAAgB,OAAO;IACzB,CAAC;IACD,QAAQ,KAAK;KACX,OAAO,WAAW;KAClB,KAAK;KACL,OAAO,cAAc,OAAO,KAAK;IACnC,CAAC;IACD,IAAI,uBAAuB,OAAO,KAAK,KAAK,CAAC,kBAAkB,GAAG,GAChE,QAAQ,KAAK;KACX,OAAO,WAAW,eAAe;KACjC,KAAK,WAAW,eAAe;KAC/B,OAAO;IACT,CAAC;IAEH,WAAW,IAAI,WAAW,GAAG;GAC/B;GACA,aAAa,KAAA;EACf;EACA,MAAM,IAAI;CACZ,CAAC;CACD,IAAI;EACF,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;CAC1B,SAAS,OAAO;EACd,IAAI,iBAAiB,kBACnB,MAAM;EAER,aAAa,iBAAiB,QAAQ,wBAAQ,IAAI,MAAM,aAAa;CACvE;CACA,IAAI,eAAe,MACjB,MAAM,aACJ,yBAAyB,wBACzB,0CACF;CAEF,IAAI,WAAW,SAAS,cAAc,MACpC,MAAM,aACJ,yBAAyB,iBACzB,mDACF;CAEF,IAAI,YAAY;CAChB,KAAK,MAAM,SAAS,QAAQ,UACzB,MAAM,UAAU,MAAM,QAAQ,KAAK,KACtC,GACE,YACE,UAAU,MAAM,GAAG,MAAM,KAAK,IAC9B,MAAM,QACN,UAAU,MAAM,MAAM,GAAG;CAE7B,OAAO;AACT;AAEA,MAAM,wBAAwB,YAA8C;CAC1E,IAAI,aAAa;CACjB,KAAK,MAAM,SAAS,OAAO,OAAO,OAAO,GAAG;EAC1C,IAAI,MAAM,aAAA,UACR,MAAM,aACJ,yBAAyB,sBACzB,0CAA0C,qBAAqB,OACjE;EAEF,cAAc,MAAM;CACtB;CACA,IAAI,aAAA,WACF,MAAM,aACJ,yBAAyB,sBACzB,2CAA2C,4BAA4B,oBACzE;AAEJ;AAEA,MAAa,mBACX,SACA,aACsB;CACtB,MAAM,aAAa,gBAAgB,OAAO;CAC1C,IAAI,SAAS,WAAW,GACtB,OAAO;EACL,UAAU,QAAQ,MAAM;EACxB,qBAAqB;EACrB,yBAAyB;CAC3B;CAEF,MAAM,mBAAmB,IAAI,IAC3B,WAAW,OAAO,KAAK,UAAU,CAAC,gBAAgB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAC3E;CACA,MAAM,gCAAgB,IAAI,IAAyC;CACnE,MAAM,qCAAqB,IAAI,IAAY;CAC3C,IAAI,0BAA0B;CAE9B,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,MAAM,gBAAgB,QAAQ,QAAQ;EAC5C,IAAI,mBAAmB,IAAI,GAAG,GAC5B,MAAM,aACJ,yBAAyB,oBACzB,wDACF;EAEF,mBAAmB,IAAI,GAAG;EAC1B,MAAM,QAAQ,iBAAiB,IAAI,GAAG;EACtC,IACE,UAAU,KAAA,KACV,CAAC,mBAAmB,MAAM,UAAU,QAAQ,QAAQ,KACpD,MAAM,SAAS,QAAQ,cAEvB,MAAM,aACJ,yBAAyB,iBACzB,wDACF;EAEF,IAAI,QAAQ,aAAa,WAAW,GAClC,MAAM,aACJ,yBAAyB,oBACzB,gEACF;EAEF,IACE,0BAA0B,QAAQ,aAAa,SAC/C,uBAEA,MAAM,aACJ,yBAAyB,sBACzB,4CAA4C,sBAAsB,cACpE;EAEF,MAAM,cACJ,cAAc,IAAI,MAAM,SAAS,KAAK,IAAI,qBAAK,IAAI,IAAI;EACzD,KAAK,MAAM,UAAU,iBAAiB,OAAO,OAAO,GAClD,YAAY,IAAI,QAAQ,OAAO,IAAI,GAAG,MAAM;EAE9C,cAAc,IAAI,MAAM,SAAS,KAAK,MAAM,WAAW;EACvD,2BAA2B,QAAQ,aAAa;CAClD;CAEA,MAAM,UAAU,iBAAiB,SAAS,IAAI;CAC9C,IACE,OAAO,KAAK,OAAO,CAAC,CAAC,MAAM,SACzB,KAAK,YAAY,CAAC,CAAC,WAAW,qBAAqB,CACrD,GAEA,MAAM,aACJ,yBAAyB,wBACzB,mEACF;CAEF,KAAK,MAAM,CAAC,UAAU,YAAY,eAAe;EAC/C,MAAM,YAAY,QAAQ;EAC1B,IAAI,cAAc,KAAA,GAChB,MAAM,aACJ,yBAAyB,iBACzB,2CACF;EAEF,MAAM,MAAM,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,SAAS;EACtE,QAAQ,YAAY,QAAQ,eAAe,KAAK,CAAC,GAAG,QAAQ,OAAO,CAAC,CAAC,CAAC;CACxE;CACA,qBAAqB,OAAO;CAC5B,MAAM,WAAW,QAAQ,OAAO;CAChC,IAAI,SAAS,aAAA,UACX,MAAM,aACJ,yBAAyB,sBACzB,2CAA2C,uBAAuB,OACpE;CAEF,OAAO;EACL;EACA,qBAAqB,SAAS;EAC9B;CACF;AACF;;;AC9eA,MAAM,sBAAsB,aAC1B,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,aAAa,KAC5D,SAAS,4BAA4B,KACrC,SAAS,2BAA2B,KACpC,SAAS,mCAAmC,KAC5C,SAAS,yBAAyB,KAClC,SAAS,mCAAmC;AAE9C,MAAa,wBACX,aACyB;CACzB,MAAM,SAAS;EACb,oBAAoB,SAAS,MAAM,QAChC,EAAE,aAAa,WAAW,WAC7B,CAAC,CAAC;EACF,sBAAsB,SAAS,MAAM,QAClC,EAAE,aAAa,WAAW,aAC7B,CAAC,CAAC;EACF,2BAA2B,SAAS;EACpC,0BAA0B,SAAS;EACnC,kCAAkC,SAAS;EAC3C,wBAAwB,SAAS;EACjC,kCAAkC,SAAS;CAC7C;CACA,OAAO,mBAAmB,QAAQ,IAC9B;EAAE,QAAQ;EAAW;CAAO,IAC5B;EAAE,QAAQ;EAAQ;CAAO;AAC/B;;;ACjBA,MAAM,qCAAqC;AAC3C,MAAM,8BAA8B;AAEpC,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAEA,YAAY,MAAgC,SAAiB;EAC3D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,MAAM,oBACJ,MACA,YACyB,IAAI,qBAAqB,MAAM,OAAO;AAEjE,MAAM,2BAA2B,cAC/B,UAAU,WAAW,KAAK,KAAK;AAEjC,MAAM,+BACJ,OACA,qBACY;CACZ,MAAM,QAAQ,MAAM,SAAS,GAAG,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI;CACzD,MAAM,iBAAiB,MAAM,YAAY,GAAG;CAC5C,IAAI,kBAAkB,GACpB,OAAO;CAET,MAAM,SAAS,MAAM,MAAM,GAAG,cAAc;CAC5C,MAAM,qBAAqB,OAAO,YAAY,GAAG;CACjD,IAAI,sBAAsB,GACxB,OAAO;CAET,OAAO,OAAO,MAAM,qBAAqB,CAAC,MAAM;AAClD;AASA,MAAM,wBAAwB,EAC5B,MACA,kBACA,kBACA,aACwD;CACxD,MAAM,eAAsC,CAAC;CAC7C,IAAI;CACJ,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,QAAQ,UAAU,GAAG;EACtD,MAAM,YAAY,KAAK,GAAG,MAAM;EAChC,IAAI,cAAc,KAAK;GACrB,IACE,UAAU,KAAA,KACV,4BACE,KAAK,MAAM,QAAQ,GAAG,MAAM,GAC5B,gBACF,GAEA,MAAM,iBACJ,6BAA6B,oBAC7B,uEACF;GAEF,QAAQ;GACR;EACF;EACA,IAAI,cAAc,OAAO,UAAU,KAAA,GACjC;EAEF,MAAM,eAAe,SAAS;EAC9B,MAAM,YAAY,KAAK,MAAM,OAAO,YAAY;EAChD,OAAO,kBAAkB;EACzB,IAAI,OAAO,iBAAiB,6BAC1B,MAAM,iBACJ,6BAA6B,0BAC7B,+CAA+C,4BAA4B,wBAC7E;EAEF,MAAM,UAAU,4BACd,UAAU,MAAM,CAAC,GACjB,gBACF;EACA,IAAI,UAAU,SAAS,oCAAoC;GACzD,IAAI,SACF,MAAM,iBACJ,6BAA6B,oBAC7B,qDACF;GAEF,QAAQ,KAAA;GACR;EACF;EACA,IAAI,CAAC,SAAS;GACZ,QAAQ,KAAA;GACR;EACF;EACA,MAAM,cAAc,iBAAiB,SAAS;EAC9C,IAAI,gBAAgB,WAClB,aAAa,KAAK;GAAE;GAAO,KAAK;GAAc;EAAY,CAAC;OAE3D,MAAM,iBACJ,6BAA6B,oBAC7B,oEACF;EAEF,QAAQ,KAAA;CACV;CACA,IACE,UAAU,KAAA,KACV,4BAA4B,KAAK,MAAM,QAAQ,CAAC,GAAG,gBAAgB,GAEnE,MAAM,iBACJ,6BAA6B,oBAC7B,uEACF;CAEF,OAAO;AACT;AAEA,MAAa,mBAAmB,EAC9B,UACA,SACA,mBACA,6BACmD;CACnD,MAAM,YAAY,QAAQ,UAAU;CACpC,IAAI,cAAc,mBAChB,MAAM,iBACJ,6BAA6B,iBAC7B,iEACF;CAGF,MAAM,+BAAqC;EACzC,IAAI,QAAQ,YAAY,IAAI,sBAAsB,MAAM,IACtD,MAAM,iBACJ,6BAA6B,gBAC7B,kEACF;CAEJ;CACA,uBAAuB;CACvB,MAAM,qCAAqB,IAAI,IAAoB;CACnD,MAAM,oBAAoB,cAA8B;EACtD,MAAM,SAAS,mBAAmB,IAAI,SAAS;EAC/C,IAAI,WAAW,KAAA,GACb,OAAO;EAET,MAAM,WAAW,QAAQ,YAAY,WAAW,sBAAsB;EACtE,mBAAmB,IAAI,WAAW,QAAQ;EAC1C,OAAO;CACT;CACA,MAAM,mBAAmB,wBAAwB,SAAS;CAC1D,MAAM,aAAa,gBAAgB,QAAQ;CAC3C,MAAM,WAA+B,CAAC;CACtC,MAAM,SAAS,EAAE,gBAAgB,EAAE;CACnC,IAAI,2BAA2B;CAC/B,KAAK,MAAM,SAAS,WAAW,QAAQ;EACrC,MAAM,eAAe,qBAAqB;GACxC,MAAM,MAAM;GACZ;GACA;GACA;EACF,CAAC;EACD,IAAI,aAAa,WAAW,GAC1B;EAEF,4BAA4B,aAAa;EACzC,SAAS,KAAK;GACZ,UAAU,MAAM;GAChB,cAAc,MAAM;GACpB;EACF,CAAC;CACH;CACA,uBAAuB;CACvB,MAAM,WAAW,gBAAgB,UAAU,QAAQ;CACnD,OAAO;EACL,UAAU,SAAS;EACnB;EACA,oBAAoB,SAAS;EAC7B;EACA,UAAU,qBAAqB,WAAW,QAAQ;CACpD;AACF;;;AC1LA,MAAa,2CAA2C;AAExD,IAAa,yBAAb,cAA4C,MAAM;CAChD;CAEA,YAAY,MAAkC,SAAiB;EAC7D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,MAAM,sBACJ,MACA,YAC2B,IAAI,uBAAuB,MAAM,OAAO;AAOrE,MAAM,wBACJ,kBACA,WACkB;CAClB,MAAM,mBAAmB,IAAI,IAC3B,iBAAiB,KAAK,UAAU,CAAC,gBAAgB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAC1E;CACA,MAAM,uCAAuB,IAAI,IAAuC;CACxE,IAAI,uBAAuB;CAC3B,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,MAAM,gBAAgB,MAAM,QAAQ;EAC1C,IAAI,qBAAqB,IAAI,GAAG,GAC9B,MAAM,mBACJ,+BAA+B,yBAC/B,0DACF;EAEF,MAAM,QAAQ,iBAAiB,IAAI,GAAG;EACtC,IACE,UAAU,KAAA,KACV,CAAC,mBAAmB,MAAM,UAAU,MAAM,QAAQ,KAClD,MAAM,SAAS,MAAM,cAErB,MAAM,mBACJ,+BAA+B,yBAC/B,mEACF;EAEF,IACE,MAAM,WAAW,SAAA,MAC0B,sBAE3C,MAAM,mBACJ,+BAA+B,yBAC/B,6CAA6C,yCAAyC,mBACxF;EAEF,qBAAqB,IAAI,KAAK,KAAK;EACnC,wBAAwB,MAAM,WAAW;CAC3C;CACA,OAAO;EAAE;EAAsB;CAAqB;AACtD;AAEA,MAAa,iBAAiB,EAC5B,UACA,SACA,mBACA,QACA,mBAAmB,CAAC,GACpB,6BACmD;CACnD,MAAM,YAAY,QAAQ,UAAU;CACpC,IAAI,cAAc,mBAChB,MAAM,mBACJ,+BAA+B,iBAC/B,gEACF;CAGF,MAAM,aAAa,gBAAgB,QAAQ;CAC3C,MAAM,WAAW,qBAAqB,WAAW,QAAQ;CACzD,IACE,SAAS,WAAW,aACpB,OAAO,SAAS,SAAS,oBAAoB,aAE7C,MAAM,mBACJ,+BAA+B,oBAC/B,0EACF;CAGF,MAAM,EAAE,sBAAsB,yBAAyB,qBACrD,WAAW,QACX,gBACF;CACA,MAAM,OAAO,QAAQ,kCAAkC;EACrD,QAAQ,WAAW,OAAO,KAAK,WAAW;GACxC,UAAU,MAAM;GAChB,YACE,qBAAqB,IAAI,gBAAgB,MAAM,QAAQ,CAAC,CAAC,EAAE,cAC3D,CAAC;EACL,EAAE;EACF,GAAI,OAAO,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,OAAO,UAAU;EACxE,GAAI,2BAA2B,KAAA,IAAY,CAAC,IAAI,EAAE,uBAAuB;CAC3E,CAAC;CACD,IAAI,KAAK,OAAO,WAAW,WAAW,OAAO,QAC3C,MAAM,mBACJ,+BAA+B,yBAC/B,sEACF;CAGF,MAAM,WAA+B,CAAC;CACtC,IAAI,cAAc;CAClB,IAAI,+BAA+B;CACnC,KAAK,MAAM,CAAC,OAAO,UAAU,WAAW,OAAO,QAAQ,GAAG;EACxD,MAAM,YAAY,KAAK,OAAO,GAAG,KAAK;EACtC,IAAI,cAAc,KAAA,GAChB,MAAM,mBACJ,+BAA+B,yBAC/B,2DACF;EAEF,eAAe,UAAU;EACzB,gCAAgC,UAAU;EAC1C,IAAI,UAAU,aAAa,WAAW,GACpC;EAEF,SAAS,KAAK;GACZ,UAAU,MAAM;GAChB,cAAc,MAAM;GACpB,cAAc,UAAU;EAC1B,CAAC;CACH;CAEA,MAAM,YAAY,gBAAgB,UAAU,QAAQ;CACpD,KAAK,OAAO;CACZ,OAAO;EACL,UAAU,UAAU;EACpB,SAAS;GACP,iBAAiB;GACjB;GACA,YAAY,WAAW,OAAO;GAC9B,qBAAqB,UAAU;GAC/B,yBAAyB,UAAU;GACnC;GACA;GACA;GACA;EACF;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/anonymize-docx",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Structure-aware DOCX text extraction and rewriting for stella anonymization workflows",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.mts",
|
|
9
|
+
"import": "./dist/index.mjs",
|
|
10
|
+
"default": "./dist/index.mjs"
|
|
11
|
+
}
|
|
10
12
|
},
|
|
13
|
+
"files": [
|
|
14
|
+
"ATTRIBUTION.md",
|
|
15
|
+
"dist",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
11
19
|
"publishConfig": {
|
|
12
20
|
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/stella/anonymize.git"
|
|
25
|
+
},
|
|
26
|
+
"license": "Apache-2.0",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsdown",
|
|
29
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
|
|
30
|
+
"test": "bun test",
|
|
31
|
+
"format": "oxfmt ."
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@stll/anonymize": "^2.0.2",
|
|
35
|
+
"fflate": "^0.8.3",
|
|
36
|
+
"saxes": "^6.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^26.1.1",
|
|
40
|
+
"bun-types": "^1.3.14",
|
|
41
|
+
"tsdown": "^0.22.4",
|
|
42
|
+
"typescript": "^6.0.3"
|
|
13
43
|
}
|
|
14
44
|
}
|