@gmickel/gno 1.45.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +46 -0
- package/assets/skill/SKILL.md +7 -6
- package/assets/skill/cli-reference.md +14 -6
- package/assets/skill/mcp-reference.md +4 -1
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
- package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/browser-extension/dist/preview.html +1 -1
- package/browser-extension/dist/service-worker.js +32 -33
- package/bunfig.toml +2 -0
- package/package.json +40 -26
- package/spec/cli.md +30 -11
- package/spec/db/schema.sql +146 -1
- package/spec/mcp.md +26 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +2 -0
- package/src/cli/commands/ask.ts +6 -1
- package/src/cli/commands/daemon.ts +21 -8
- package/src/cli/commands/embed.ts +77 -41
- package/src/cli/commands/mcp/install.ts +20 -0
- package/src/cli/commands/mcp/paths.ts +25 -0
- package/src/cli/commands/mcp/status.ts +6 -0
- package/src/cli/detach.ts +3 -2
- package/src/cli/program.ts +6 -0
- package/src/config/types.ts +3 -3
- package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
- package/src/converters/adapters/officeparser/adapter.ts +1 -2
- package/src/converters/versions.ts +6 -8
- package/src/core/context-evidence.ts +8 -4
- package/src/core/job-manager.ts +95 -13
- package/src/core/network-boundary-inventory.ts +10 -0
- package/src/core/shutdown-budget.ts +45 -0
- package/src/embed/backlog.ts +107 -4
- package/src/embed/batch.ts +42 -2
- package/src/embed/fingerprint.ts +16 -0
- package/src/embed/retry.ts +113 -5
- package/src/embed/variant-backlog.ts +105 -0
- package/src/embed/variant-plan.ts +62 -0
- package/src/embed/variant-retry.ts +113 -0
- package/src/ingestion/graph-reconciliation.ts +327 -0
- package/src/ingestion/sync.ts +9 -272
- package/src/llm/http-inference.ts +6 -0
- package/src/llm/httpEmbedding.ts +37 -6
- package/src/llm/httpGeneration.ts +18 -3
- package/src/llm/httpRerank.ts +23 -5
- package/src/llm/inference-cancellation.ts +168 -0
- package/src/llm/inference-scope.ts +202 -0
- package/src/llm/lazy-ports.ts +115 -0
- package/src/llm/native-worker/client.ts +541 -0
- package/src/llm/native-worker/dispatcher.ts +228 -0
- package/src/llm/native-worker/embedding-identity.ts +33 -0
- package/src/llm/native-worker/entry.ts +173 -0
- package/src/llm/native-worker/errors.ts +32 -0
- package/src/llm/native-worker/evaluation.ts +16 -0
- package/src/llm/native-worker/owned-exit.ts +108 -0
- package/src/llm/native-worker/owner.ts +141 -0
- package/src/llm/native-worker/ports.ts +317 -0
- package/src/llm/native-worker/protocol.ts +442 -0
- package/src/llm/native-worker/runtime-config.ts +92 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
- package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
- package/src/llm/nodeLlamaCpp/generation.ts +34 -5
- package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
- package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
- package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
- package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
- package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
- package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
- package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
- package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
- package/src/llm/types.ts +35 -5
- package/src/mcp/context.ts +27 -0
- package/src/mcp/http-transport.ts +12 -10
- package/src/mcp/server.ts +3 -0
- package/src/mcp/tool-profile.ts +30 -8
- package/src/mcp/tools/context.ts +8 -11
- package/src/mcp/tools/embed.ts +1 -1
- package/src/mcp/tools/index-cmd.ts +1 -1
- package/src/mcp/tools/index.ts +10 -8
- package/src/mcp/tools/query.ts +14 -30
- package/src/mcp/tools/vsearch.ts +1 -1
- package/src/pipeline/answer.ts +23 -3
- package/src/pipeline/claim-verifier.ts +6 -0
- package/src/pipeline/expansion.ts +43 -40
- package/src/pipeline/explain.ts +6 -2
- package/src/pipeline/filters.ts +63 -0
- package/src/pipeline/fusion.ts +29 -9
- package/src/pipeline/graph-retrieval.ts +29 -9
- package/src/pipeline/hybrid.ts +198 -55
- package/src/pipeline/hydration.ts +161 -0
- package/src/pipeline/owner-fusion.ts +87 -0
- package/src/pipeline/rerank.ts +35 -11
- package/src/pipeline/search.ts +13 -2
- package/src/pipeline/types.ts +5 -3
- package/src/pipeline/vsearch.ts +87 -7
- package/src/sdk/client.ts +47 -3
- package/src/sdk/embed.ts +63 -39
- package/src/serve/background-runtime.ts +1 -1
- package/src/serve/context.ts +41 -56
- package/src/serve/embed-scheduler.ts +58 -35
- package/src/serve/public/components/IndexingProgress.tsx +46 -60
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/shiki-language-ids.ts +14 -0
- package/src/serve/resident-admission.ts +36 -36
- package/src/serve/resident-background-work.ts +20 -2
- package/src/serve/resident-request.ts +11 -5
- package/src/serve/resident-runtime.ts +97 -61
- package/src/serve/resident-shutdown.ts +153 -0
- package/src/serve/routes/api.ts +3 -1
- package/src/serve/server.ts +47 -26
- package/src/store/migrations/028-vector-variants.ts +54 -0
- package/src/store/migrations/029-graph-reference-state.ts +77 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +251 -183
- package/src/store/sqlite/eligibility.ts +174 -0
- package/src/store/sqlite/graph-edge-application.ts +66 -0
- package/src/store/sqlite/graph-reference-state.ts +194 -0
- package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
- package/src/store/types.ts +80 -12
- package/src/store/vector/eligibility.ts +36 -0
- package/src/store/vector/freshness.ts +33 -6
- package/src/store/vector/lazy.ts +81 -0
- package/src/store/vector/sqlite-vec.ts +106 -54
- package/src/store/vector/stats.ts +14 -3
- package/src/store/vector/types.ts +35 -2
- package/src/store/vector/variant-search.ts +192 -0
- package/src/store/vector/variants.ts +451 -0
- package/vendor/converters/markitdown-ts/LICENSE +21 -0
- package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
- package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
- package/vendor/converters/markitdown-ts/package.json +77 -0
- package/vendor/converters/officeparser/LICENSE +21 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
- package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
- package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
- package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
- package/vendor/converters/officeparser/dist/cli.js +381 -0
- package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
- package/vendor/converters/officeparser/dist/defaults.js +218 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
- package/vendor/converters/officeparser/dist/index.d.ts +60 -0
- package/vendor/converters/officeparser/dist/index.js +72 -0
- package/vendor/converters/officeparser/dist/index.mjs +18 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
- package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
- package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
- package/vendor/converters/officeparser/dist/types.js +107 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
- package/vendor/converters/officeparser/package.json +147 -0
- package/vendor/converters/upstream-manifest.json +124 -0
- package/vendor/dependency-fixes/README.md +77 -0
- package/vendor/dependency-fixes/vendor-converters.py +83 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip.sha256 +0 -1
- package/browser-extension/dist/chunk-627emwpj.js +0 -75
- /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Word Document (DOCX) Parser
|
|
4
|
+
*
|
|
5
|
+
* **DOCX Format Overview:**
|
|
6
|
+
* DOCX is the default format for Microsoft Word documents since Office 2007.
|
|
7
|
+
* It's based on the Office Open XML (OOXML) standard (ECMA-376, ISO/IEC 29500).
|
|
8
|
+
*
|
|
9
|
+
* **File Structure:**
|
|
10
|
+
* DOCX files are ZIP archives containing:
|
|
11
|
+
* - `word/document.xml` - Main document content
|
|
12
|
+
* - `word/styles.xml` - Style definitions
|
|
13
|
+
* - `word/numbering.xml` - List numbering definitions
|
|
14
|
+
* - `word/footnotes.xml` - Footnotes content
|
|
15
|
+
* - `word/media/*` - Embedded images and media
|
|
16
|
+
* - `docProps/core.xml` - Document metadata
|
|
17
|
+
* - `[Content_Types].xml` - MIME type mappings
|
|
18
|
+
*
|
|
19
|
+
* **XML Structure (word/document.xml):**
|
|
20
|
+
* ```xml
|
|
21
|
+
* <w:document>
|
|
22
|
+
* <w:body>
|
|
23
|
+
* <w:p> <!-- Paragraph -->
|
|
24
|
+
* <w:pPr> <!-- Paragraph properties -->
|
|
25
|
+
* <w:pStyle w:val="Heading1"/>
|
|
26
|
+
* </w:pPr>
|
|
27
|
+
* <w:r> <!-- Run (text with same formatting) -->
|
|
28
|
+
* <w:rPr> <!-- Run properties -->
|
|
29
|
+
* <w:b/> <!-- Bold -->
|
|
30
|
+
* <w:sz w:val="24"/> <!-- Font size (half-points) -->
|
|
31
|
+
* </w:rPr>
|
|
32
|
+
* <w:t>Hello</w:t> <!-- Text -->
|
|
33
|
+
* </w:r>
|
|
34
|
+
* </w:p>
|
|
35
|
+
* </w:body>
|
|
36
|
+
* </w:document>
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* **Key OOXML Elements:**
|
|
40
|
+
* - `<w:p>` - Paragraph
|
|
41
|
+
* - `<w:r>` - Run (contiguous text with same formatting)
|
|
42
|
+
* - `<w:t>` - Text content
|
|
43
|
+
* - `<w:br>` - Line or page break
|
|
44
|
+
* - `<w:b>`, `<w:i>`, `<w:u>` - Bold, italic, underline
|
|
45
|
+
* - `<w:pStyle>` - Paragraph style (for headings)
|
|
46
|
+
* - `<w:numPr>` - List numbering properties
|
|
47
|
+
* - `<w:tbl>` - Table
|
|
48
|
+
* - `<w:drawing>` - Drawing/image
|
|
49
|
+
*
|
|
50
|
+
* **Parsing Approach:**
|
|
51
|
+
* 1. Extract ZIP contents
|
|
52
|
+
* 2. Parse word/document.xml for structure and text
|
|
53
|
+
* 3. Extract formatting from run properties (rPr)
|
|
54
|
+
* 4. Identify headings via paragraph styles
|
|
55
|
+
* 5. Extract footnotes from word/footnotes.xml
|
|
56
|
+
* 6. Process embedded images from word/media/*
|
|
57
|
+
* 7. Parse metadata from docProps/core.xml
|
|
58
|
+
*
|
|
59
|
+
* @module WordParser
|
|
60
|
+
* @see https://www.ecma-international.org/publications-and-standards/standards/ecma-376/ OOXML Standard
|
|
61
|
+
* @see https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/ [MS-DOCX] Specification
|
|
62
|
+
*/
|
|
63
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
64
|
+
exports.parseWord = void 0;
|
|
65
|
+
const types_js_1 = require("../types.js");
|
|
66
|
+
const astUtils_js_1 = require("../utils/astUtils.js");
|
|
67
|
+
const errorUtils_js_1 = require("../utils/errorUtils.js");
|
|
68
|
+
const imageUtils_js_1 = require("../utils/imageUtils.js");
|
|
69
|
+
const mathUtils_js_1 = require("../utils/mathUtils.js");
|
|
70
|
+
const ocrUtils_js_1 = require("../utils/ocrUtils.js");
|
|
71
|
+
const xmlUtils_js_1 = require("../utils/xmlUtils.js");
|
|
72
|
+
const zipUtils_js_1 = require("../utils/zipUtils.js");
|
|
73
|
+
/**
|
|
74
|
+
* Parses a Word document (.docx) and extracts content, formatting, and metadata.
|
|
75
|
+
*
|
|
76
|
+
* The parsing process:
|
|
77
|
+
* 1. Unzip the DOCX file
|
|
78
|
+
* 2. Parse word/document.xml to extract paragraphs and runs
|
|
79
|
+
* 3. Extract text formatting from run properties
|
|
80
|
+
* 4. Identify headings from paragraph styles
|
|
81
|
+
* 5. Process lists from numbering properties
|
|
82
|
+
* 6. Extract images and optionally perform OCR
|
|
83
|
+
* 7. Parse document metadata
|
|
84
|
+
*
|
|
85
|
+
* @param buffer - The DOCX file as a Buffer
|
|
86
|
+
* @param config - Parser configuration options
|
|
87
|
+
* @returns A promise resolving to the parsed AST
|
|
88
|
+
*/
|
|
89
|
+
const parseWord = async (buffer, config) => {
|
|
90
|
+
// Honour cancellation requests immediately — before opening the ZIP archive, loading XML
|
|
91
|
+
// files, or kicking off any OCR work. DOCX files can be large and the inflate + XML-parse
|
|
92
|
+
// steps are synchronous-heavy, so failing fast here avoids wasted CPU time.
|
|
93
|
+
(0, errorUtils_js_1.checkAbortSignal)(config.abortSignal);
|
|
94
|
+
const documentFileRegex = /word\/document[\d+]?.xml/;
|
|
95
|
+
const footnotesFileRegex = /word\/footnotes[\d+]?.xml/;
|
|
96
|
+
const endnotesFileRegex = /word\/endnotes[\d+]?.xml/;
|
|
97
|
+
const commentsFileRegex = /word\/comments[\d+]?.xml/;
|
|
98
|
+
// Headers and footers are the only parts a document can have many of: Word writes up to
|
|
99
|
+
// three per section (default, first page, even pages), so a handful of sections is enough
|
|
100
|
+
// to reach header10.xml. The single-character form the other parts use stops matching at
|
|
101
|
+
// nine, which would drop those later files as silently as not extracting them at all.
|
|
102
|
+
const headerFileRegex = /word\/header\d*\.xml/;
|
|
103
|
+
const footerFileRegex = /word\/footer\d*\.xml/;
|
|
104
|
+
const numberingFileRegex = /word\/numbering[\d+]?.xml/;
|
|
105
|
+
const mediaFileRegex = /(word\/)?media\/.*/;
|
|
106
|
+
const corePropsFileRegex = /docProps\/core[\d+]?.xml/;
|
|
107
|
+
const customPropsFileRegex = /docProps\/custom\.xml/;
|
|
108
|
+
const appPropsFileRegex = /docProps\/app[\d+]?.xml/;
|
|
109
|
+
const relsFileRegex = /word\/_rels\/document[\d+]?.xml\.rels/;
|
|
110
|
+
const stylesFileRegex = /word\/styles[\d+]?.xml/;
|
|
111
|
+
// Helper to extract formatting from run properties XML string
|
|
112
|
+
const extractFormattingFromXml = (rPr) => {
|
|
113
|
+
const formatting = {};
|
|
114
|
+
// Helper to check boolean properties (e.g., <w:b />, <w:i w:val="0" />)
|
|
115
|
+
const getBoolVal = (parent, tagName) => {
|
|
116
|
+
const el = (0, xmlUtils_js_1.getFirstElementByTagName)(parent, tagName);
|
|
117
|
+
if (el) {
|
|
118
|
+
const val = el.getAttribute('w:val');
|
|
119
|
+
// In OOXML, if the element is present without w:val, it's true.
|
|
120
|
+
// If w:val is present, it can be '1', 'true', 'on' for true.
|
|
121
|
+
if (val === null)
|
|
122
|
+
return true;
|
|
123
|
+
return val === '1' || val === 'true' || val === 'on';
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
};
|
|
127
|
+
const bold = getBoolVal(rPr, 'w:b');
|
|
128
|
+
if (bold !== null)
|
|
129
|
+
formatting.bold = bold;
|
|
130
|
+
const italic = getBoolVal(rPr, 'w:i');
|
|
131
|
+
if (italic !== null)
|
|
132
|
+
formatting.italic = italic;
|
|
133
|
+
const u = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, 'w:u');
|
|
134
|
+
if (u) {
|
|
135
|
+
const val = u.getAttribute('w:val');
|
|
136
|
+
// If val is missing, it's a default underline (true).
|
|
137
|
+
// If val is present, it's true unless explicit 'none'.
|
|
138
|
+
if (!val || val !== 'none') {
|
|
139
|
+
formatting.underline = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const strike = getBoolVal(rPr, 'w:strike');
|
|
143
|
+
const dstrike = getBoolVal(rPr, 'w:dstrike');
|
|
144
|
+
if (strike !== null)
|
|
145
|
+
formatting.strikethrough = strike;
|
|
146
|
+
else if (dstrike !== null)
|
|
147
|
+
formatting.strikethrough = dstrike;
|
|
148
|
+
// Font size (w:sz) - stored in half-points
|
|
149
|
+
const sz = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, 'w:sz');
|
|
150
|
+
if (sz) {
|
|
151
|
+
const val = sz.getAttribute('w:val');
|
|
152
|
+
if (val) {
|
|
153
|
+
formatting.size = (parseInt(val, 10) / 2).toString() + 'pt';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Color (w:color)
|
|
157
|
+
const color = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, 'w:color');
|
|
158
|
+
if (color) {
|
|
159
|
+
const val = color.getAttribute('w:val');
|
|
160
|
+
if (val && val !== 'auto') {
|
|
161
|
+
formatting.color = '#' + val;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Background color (w:shd) - shading
|
|
165
|
+
const shd = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, 'w:shd');
|
|
166
|
+
if (shd) {
|
|
167
|
+
const val = shd.getAttribute('w:fill');
|
|
168
|
+
if (val && val !== 'auto') {
|
|
169
|
+
formatting.backgroundColor = '#' + val;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Highlight (w:highlight) - maps to background color in our AST
|
|
173
|
+
const highlight = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, 'w:highlight');
|
|
174
|
+
if (highlight) {
|
|
175
|
+
const val = highlight.getAttribute('w:val');
|
|
176
|
+
if (val && val !== 'none') {
|
|
177
|
+
const colorMap = {
|
|
178
|
+
'yellow': '#FFFF00', 'green': '#00FF00', 'cyan': '#00FFFF', 'magenta': '#FF00FF',
|
|
179
|
+
'blue': '#0000FF', 'red': '#FF0000', 'darkBlue': '#00008B', 'darkCyan': '#008B8B',
|
|
180
|
+
'darkGreen': '#006400', 'darkMagenta': '#8B008B', 'darkRed': '#8B0000',
|
|
181
|
+
'darkYellow': '#808000', 'darkGray': '#A9A9A9', 'lightGray': '#D3D3D3', 'black': '#000000'
|
|
182
|
+
};
|
|
183
|
+
formatting.backgroundColor = colorMap[val] || val;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Font family (w:rFonts)
|
|
187
|
+
const rFonts = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, 'w:rFonts');
|
|
188
|
+
if (rFonts) {
|
|
189
|
+
// Priority: ascii (Western) > hAnsi (High ANSI)
|
|
190
|
+
const font = rFonts.getAttribute('w:ascii') || rFonts.getAttribute('w:hAnsi');
|
|
191
|
+
if (font) {
|
|
192
|
+
formatting.font = font;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Subscript/Superscript (w:vertAlign)
|
|
196
|
+
const vertAlign = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, 'w:vertAlign');
|
|
197
|
+
if (vertAlign) {
|
|
198
|
+
const val = vertAlign.getAttribute('w:val');
|
|
199
|
+
if (val === 'subscript')
|
|
200
|
+
formatting.subscript = true;
|
|
201
|
+
else if (val === 'superscript')
|
|
202
|
+
formatting.superscript = true;
|
|
203
|
+
}
|
|
204
|
+
return formatting;
|
|
205
|
+
};
|
|
206
|
+
// Helper to extract indentation from paragraph properties XML string
|
|
207
|
+
const extractIndentationFromXml = (pPr) => {
|
|
208
|
+
const ind = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "w:ind");
|
|
209
|
+
if (ind) {
|
|
210
|
+
const indentation = {};
|
|
211
|
+
const left = ind.getAttribute("w:left") || ind.getAttribute("w:start");
|
|
212
|
+
const right = ind.getAttribute("w:right") || ind.getAttribute("w:end");
|
|
213
|
+
const firstLine = ind.getAttribute("w:firstLine");
|
|
214
|
+
const hanging = ind.getAttribute("w:hanging");
|
|
215
|
+
if (left)
|
|
216
|
+
indentation.left = parseInt(left, 10);
|
|
217
|
+
if (right)
|
|
218
|
+
indentation.right = parseInt(right, 10);
|
|
219
|
+
if (firstLine)
|
|
220
|
+
indentation.firstLine = parseInt(firstLine, 10);
|
|
221
|
+
if (hanging)
|
|
222
|
+
indentation.hanging = parseInt(hanging, 10);
|
|
223
|
+
return Object.keys(indentation).length > 0 ? indentation : undefined;
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Resolves mc:AlternateContent by preferring mc:Fallback if choice namespace is not recognized,
|
|
229
|
+
* or simply the first available valid child.
|
|
230
|
+
*/
|
|
231
|
+
const resolveAlternateContent = (element) => {
|
|
232
|
+
const choice = (0, xmlUtils_js_1.getFirstElementByTagName)(element, "mc:Choice");
|
|
233
|
+
// In most cases, mc:Choice contains the modern version, but mc:Fallback is safer for legacy compatibility
|
|
234
|
+
// Mammoth often skips Choice if it's not handled. We'll try Choice first.
|
|
235
|
+
if (choice)
|
|
236
|
+
return Array.from(choice.childNodes);
|
|
237
|
+
const fallback = (0, xmlUtils_js_1.getFirstElementByTagName)(element, "mc:Fallback");
|
|
238
|
+
if (fallback)
|
|
239
|
+
return Array.from(fallback.childNodes);
|
|
240
|
+
return Array.from(element.childNodes);
|
|
241
|
+
};
|
|
242
|
+
const files = await (0, zipUtils_js_1.extractFiles)(buffer, x => !!x.match(documentFileRegex) ||
|
|
243
|
+
!!x.match(footnotesFileRegex) ||
|
|
244
|
+
!!x.match(endnotesFileRegex) ||
|
|
245
|
+
!!x.match(numberingFileRegex) ||
|
|
246
|
+
!!x.match(corePropsFileRegex) ||
|
|
247
|
+
!!x.match(customPropsFileRegex) ||
|
|
248
|
+
!!x.match(appPropsFileRegex) ||
|
|
249
|
+
!!x.match(relsFileRegex) ||
|
|
250
|
+
!!x.match(stylesFileRegex) ||
|
|
251
|
+
(!config.ignoreComments && !!x.match(commentsFileRegex)) ||
|
|
252
|
+
(!config.ignoreHeadersAndFooters && (!!x.match(headerFileRegex) || !!x.match(footerFileRegex))) ||
|
|
253
|
+
(!!config.extractAttachments && !!x.match(mediaFileRegex)), config.decompressionLimits, config);
|
|
254
|
+
// A DOCX without its main document part is not a DOCX. Checked with the same regex the
|
|
255
|
+
// parse loop below uses to recognize it, so the two cannot fall out of step.
|
|
256
|
+
(0, zipUtils_js_1.findRequiredPart)(files, path => !!path.match(documentFileRegex), config, { fileType: 'docx', part: 'word/document.xml' });
|
|
257
|
+
// Extract metadata
|
|
258
|
+
const corePropsFile = files.find(f => f.path.match(corePropsFileRegex));
|
|
259
|
+
const metadata = corePropsFile ? (0, xmlUtils_js_1.parseOfficeMetadata)(corePropsFile.content.toString()) : {};
|
|
260
|
+
const customPropsFile = files.find(f => f.path.match(customPropsFileRegex));
|
|
261
|
+
if (customPropsFile) {
|
|
262
|
+
const customProperties = (0, xmlUtils_js_1.parseOOXMLCustomProperties)(customPropsFile.content.toString());
|
|
263
|
+
if (Object.keys(customProperties).length > 0)
|
|
264
|
+
metadata.customProperties = customProperties;
|
|
265
|
+
}
|
|
266
|
+
const appPropsFile = files.find(f => f.path.match(appPropsFileRegex));
|
|
267
|
+
if (appPropsFile) {
|
|
268
|
+
const appProperties = (0, xmlUtils_js_1.parseOOXMLAppProperties)(appPropsFile.content.toString());
|
|
269
|
+
if (Object.keys(appProperties).length > 0) {
|
|
270
|
+
metadata.nativeProperties = appProperties;
|
|
271
|
+
if (appProperties['Pages'] && typeof appProperties['Pages'] === 'number') {
|
|
272
|
+
metadata.pages = appProperties['Pages'];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const footnoteMap = new Map();
|
|
277
|
+
const endnoteMap = new Map();
|
|
278
|
+
const commentMap = new Map();
|
|
279
|
+
const commentMetadataMap = new Map();
|
|
280
|
+
const attachments = [];
|
|
281
|
+
const mediaFiles = files.filter(f => f.path.match(mediaFileRegex));
|
|
282
|
+
// Extract relationships
|
|
283
|
+
const relsFile = files.find(f => f.path.match(relsFileRegex));
|
|
284
|
+
const relsMap = {};
|
|
285
|
+
if (relsFile) {
|
|
286
|
+
const relsXml = (0, xmlUtils_js_1.parseXmlString)(relsFile.content.toString());
|
|
287
|
+
const relationships = (0, xmlUtils_js_1.getElementsByTagName)(relsXml, "Relationship");
|
|
288
|
+
for (const relationship of relationships) {
|
|
289
|
+
const id = relationship.getAttribute("Id");
|
|
290
|
+
const target = relationship.getAttribute("Target");
|
|
291
|
+
if (id && target) {
|
|
292
|
+
relsMap[id] = target;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const numberingFile = files.find(f => f.path.match(numberingFileRegex));
|
|
297
|
+
const numberingMap = {};
|
|
298
|
+
if (numberingFile) {
|
|
299
|
+
const numberingXml = (0, xmlUtils_js_1.parseXmlString)(numberingFile.content.toString());
|
|
300
|
+
const nums = (0, xmlUtils_js_1.getElementsByTagName)(numberingXml, "w:num");
|
|
301
|
+
const abstractNums = (0, xmlUtils_js_1.getElementsByTagName)(numberingXml, "w:abstractNum");
|
|
302
|
+
const abstractNumMap = {};
|
|
303
|
+
for (const abstractNum of abstractNums) {
|
|
304
|
+
const abstractNumId = abstractNum.getAttribute("w:abstractNumId");
|
|
305
|
+
if (abstractNumId) {
|
|
306
|
+
abstractNumMap[abstractNumId] = abstractNum;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
for (const num of nums) {
|
|
310
|
+
const numId = num.getAttribute("w:numId");
|
|
311
|
+
const abstractNumIdNode = (0, xmlUtils_js_1.getFirstElementByTagName)(num, "w:abstractNumId");
|
|
312
|
+
const abstractNumId = abstractNumIdNode?.getAttribute("w:val");
|
|
313
|
+
if (numId && abstractNumId && abstractNumMap[abstractNumId]) {
|
|
314
|
+
numberingMap[numId] = {};
|
|
315
|
+
// Inherit from abstractNum
|
|
316
|
+
const lvls = (0, xmlUtils_js_1.getElementsByTagName)(abstractNumMap[abstractNumId], "w:lvl");
|
|
317
|
+
for (const lvl of lvls) {
|
|
318
|
+
const ilvl = lvl.getAttribute("w:ilvl");
|
|
319
|
+
const numFmtNode = (0, xmlUtils_js_1.getFirstElementByTagName)(lvl, "w:numFmt");
|
|
320
|
+
const lvlTextNode = (0, xmlUtils_js_1.getFirstElementByTagName)(lvl, "w:lvlText");
|
|
321
|
+
const startNode = (0, xmlUtils_js_1.getFirstElementByTagName)(lvl, "w:start");
|
|
322
|
+
if (ilvl) {
|
|
323
|
+
numberingMap[numId][ilvl] = {
|
|
324
|
+
numFmt: numFmtNode?.getAttribute("w:val") || 'decimal',
|
|
325
|
+
lvlText: lvlTextNode?.getAttribute("w:val") || '',
|
|
326
|
+
start: parseInt(startNode?.getAttribute("w:val") || '1', 10)
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Apply instance overrides (w:lvlOverride)
|
|
331
|
+
const overrides = (0, xmlUtils_js_1.getElementsByTagName)(num, "w:lvlOverride");
|
|
332
|
+
for (const override of overrides) {
|
|
333
|
+
const ilvl = override.getAttribute("w:ilvl");
|
|
334
|
+
if (ilvl && numberingMap[numId][ilvl]) {
|
|
335
|
+
const startOverride = (0, xmlUtils_js_1.getFirstElementByTagName)(override, "w:startOverride");
|
|
336
|
+
if (startOverride) {
|
|
337
|
+
numberingMap[numId][ilvl].start = parseInt(startOverride.getAttribute("w:val") || '1', 10);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Parse Styles
|
|
345
|
+
const stylesFile = files.find(f => f.path.match(stylesFileRegex));
|
|
346
|
+
const styleMap = {};
|
|
347
|
+
if (stylesFile) {
|
|
348
|
+
const stylesXml = (0, xmlUtils_js_1.parseXmlString)(stylesFile.content.toString());
|
|
349
|
+
const styles = (0, xmlUtils_js_1.getElementsByTagName)(stylesXml, "w:style");
|
|
350
|
+
for (const style of styles) {
|
|
351
|
+
const styleId = style.getAttribute("w:styleId");
|
|
352
|
+
if (styleId) {
|
|
353
|
+
const rPr = (0, xmlUtils_js_1.getFirstElementByTagName)(style, "w:rPr");
|
|
354
|
+
const pPr = (0, xmlUtils_js_1.getFirstElementByTagName)(style, "w:pPr");
|
|
355
|
+
const formatting = rPr ? extractFormattingFromXml(rPr) : {};
|
|
356
|
+
let alignment = undefined;
|
|
357
|
+
let backgroundColor = undefined;
|
|
358
|
+
let paragraphIndentation = undefined;
|
|
359
|
+
if (pPr) {
|
|
360
|
+
const jc = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "w:jc");
|
|
361
|
+
if (jc) {
|
|
362
|
+
const val = jc.getAttribute("w:val");
|
|
363
|
+
if (val === 'left' || val === 'center' || val === 'right' || val === 'justify') {
|
|
364
|
+
alignment = val;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const shd = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "w:shd");
|
|
368
|
+
if (shd) {
|
|
369
|
+
const fill = shd.getAttribute("w:fill");
|
|
370
|
+
if (fill && fill !== 'auto')
|
|
371
|
+
backgroundColor = '#' + fill;
|
|
372
|
+
}
|
|
373
|
+
const ind = extractIndentationFromXml(pPr);
|
|
374
|
+
if (ind)
|
|
375
|
+
paragraphIndentation = ind;
|
|
376
|
+
}
|
|
377
|
+
styleMap[styleId] = { formatting, alignment, backgroundColor, paragraphIndentation };
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Extract document defaults
|
|
382
|
+
let docDefaults = {};
|
|
383
|
+
if (stylesFile) {
|
|
384
|
+
const stylesXml = (0, xmlUtils_js_1.parseXmlString)(stylesFile.content.toString());
|
|
385
|
+
const docDefaultsNode = (0, xmlUtils_js_1.getFirstElementByTagName)(stylesXml, "w:docDefaults");
|
|
386
|
+
if (docDefaultsNode) {
|
|
387
|
+
const rPrDefaultNode = (0, xmlUtils_js_1.getFirstElementByTagName)(docDefaultsNode, "w:rPrDefault");
|
|
388
|
+
if (rPrDefaultNode) {
|
|
389
|
+
const rPr = (0, xmlUtils_js_1.getFirstElementByTagName)(rPrDefaultNode, "w:rPr");
|
|
390
|
+
if (rPr) {
|
|
391
|
+
docDefaults = extractFormattingFromXml(rPr);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Detect the default paragraph style (for international compatibility)
|
|
397
|
+
let defaultParaStyleId = undefined;
|
|
398
|
+
if (stylesFile) {
|
|
399
|
+
const stylesXml = (0, xmlUtils_js_1.parseXmlString)(stylesFile.content.toString());
|
|
400
|
+
const styles = (0, xmlUtils_js_1.getElementsByTagName)(stylesXml, "w:style");
|
|
401
|
+
// Look for a style with w:type="paragraph" and w:default="1"
|
|
402
|
+
for (const style of styles) {
|
|
403
|
+
const styleType = style.getAttribute("w:type");
|
|
404
|
+
const isDefault = style.getAttribute("w:default");
|
|
405
|
+
const styleId = style.getAttribute("w:styleId");
|
|
406
|
+
if (styleType === "paragraph" && isDefault === "1" && styleId) {
|
|
407
|
+
defaultParaStyleId = styleId;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// Fallback: if no default found, try "Normal"
|
|
412
|
+
if (!defaultParaStyleId && styleMap["Normal"]) {
|
|
413
|
+
defaultParaStyleId = "Normal";
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const content = [];
|
|
417
|
+
const numberingState = {};
|
|
418
|
+
const listCounters = {}; // Track item index per listId/level
|
|
419
|
+
// Helper to parse a paragraph node
|
|
420
|
+
const parseParagraph = (pNode, documentContent, pendingAnchorIds = []) => {
|
|
421
|
+
// Check if it's a list item
|
|
422
|
+
const numPr = (0, xmlUtils_js_1.getFirstElementByTagName)(pNode, "w:numPr");
|
|
423
|
+
const isList = !!numPr;
|
|
424
|
+
// Check if it's a heading
|
|
425
|
+
const pPr = (0, xmlUtils_js_1.getFirstElementByTagName)(pNode, "w:pPr");
|
|
426
|
+
const pStyle = pPr ? (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "w:pStyle") : null;
|
|
427
|
+
const pStyleVal = pStyle?.getAttribute("w:val");
|
|
428
|
+
const isHeading = pStyleVal ? (pStyleVal.startsWith("Heading") || pStyleVal === "Title") : false;
|
|
429
|
+
// Extract Paragraph Style Properties
|
|
430
|
+
const styleProps = pStyleVal && styleMap[pStyleVal] ? styleMap[pStyleVal] : { formatting: {} };
|
|
431
|
+
// Extract Alignment
|
|
432
|
+
let alignment = styleProps.alignment;
|
|
433
|
+
if (pPr) {
|
|
434
|
+
const jc = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "w:jc");
|
|
435
|
+
if (jc) {
|
|
436
|
+
const val = jc.getAttribute("w:val");
|
|
437
|
+
if (val === 'left' || val === 'center' || val === 'right' || val === 'justify') {
|
|
438
|
+
alignment = val;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// Extract Indentation
|
|
443
|
+
let paraIndentation = styleProps.paragraphIndentation;
|
|
444
|
+
if (pPr) {
|
|
445
|
+
const ind = extractIndentationFromXml(pPr);
|
|
446
|
+
if (ind) {
|
|
447
|
+
paraIndentation = { ...paraIndentation, ...ind };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// Extract Paragraph Background
|
|
451
|
+
let paraBackgroundColor = styleProps.backgroundColor;
|
|
452
|
+
if (pPr) {
|
|
453
|
+
const shd = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "w:shd");
|
|
454
|
+
if (shd) {
|
|
455
|
+
const fill = shd.getAttribute("w:fill");
|
|
456
|
+
if (fill && fill !== 'auto') {
|
|
457
|
+
paraBackgroundColor = '#' + fill;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Runs inherit their base formatting from the style chain: the paragraph style (seeded
|
|
462
|
+
// here, and re-applied via the run-style path below), then any character style, then the
|
|
463
|
+
// run's own properties. The paragraph-mark run properties (`<w:pPr><w:rPr>`) format only
|
|
464
|
+
// the paragraph mark glyph itself per OOXML ISO 29500 §17.3.1.29, so they are deliberately
|
|
465
|
+
// NOT folded into the run base - doing so bled the paragraph mark's bold/italic/color/etc.
|
|
466
|
+
// onto every run in the paragraph (issue #109).
|
|
467
|
+
const paragraphRunFormatting = { ...styleProps.formatting };
|
|
468
|
+
// Extract text and children
|
|
469
|
+
let text = '';
|
|
470
|
+
const children = [];
|
|
471
|
+
const notes = [];
|
|
472
|
+
const comments = [];
|
|
473
|
+
// Traverse children of paragraph (runs, hyperlinks, etc.)
|
|
474
|
+
const processChildNode = (node) => {
|
|
475
|
+
if ((0, xmlUtils_js_1.isElement)(node) && (node.nodeName === 'w:r' || node.nodeName === 'm:r')) {
|
|
476
|
+
const runNode = node;
|
|
477
|
+
const rPr = (0, xmlUtils_js_1.getFirstElementByTagName)(runNode, "w:rPr");
|
|
478
|
+
// Formatting
|
|
479
|
+
let formatting = {};
|
|
480
|
+
// Apply paragraph-level formatting
|
|
481
|
+
for (const key in paragraphRunFormatting) {
|
|
482
|
+
formatting[key] = paragraphRunFormatting[key];
|
|
483
|
+
}
|
|
484
|
+
// Check for run style
|
|
485
|
+
const rStyle = rPr ? (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, "w:rStyle") : null;
|
|
486
|
+
const rStyleVal = rStyle ? rStyle.getAttribute("w:val") : pStyleVal;
|
|
487
|
+
if (rStyleVal && styleMap[rStyleVal]) {
|
|
488
|
+
for (const key in styleMap[rStyleVal].formatting) {
|
|
489
|
+
formatting[key] = styleMap[rStyleVal].formatting[key];
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Apply direct run properties
|
|
493
|
+
if (rPr) {
|
|
494
|
+
const directFormatting = extractFormattingFromXml(rPr);
|
|
495
|
+
for (const key in directFormatting) {
|
|
496
|
+
const value = directFormatting[key];
|
|
497
|
+
if (value === false) {
|
|
498
|
+
delete formatting[key];
|
|
499
|
+
}
|
|
500
|
+
else if (value !== undefined) {
|
|
501
|
+
formatting[key] = value;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// Inherit paragraph background
|
|
506
|
+
if (!formatting.backgroundColor && paraBackgroundColor) {
|
|
507
|
+
formatting.backgroundColor = paraBackgroundColor;
|
|
508
|
+
}
|
|
509
|
+
for (const child of runNode.childNodes) {
|
|
510
|
+
if (!(0, xmlUtils_js_1.isElement)(child))
|
|
511
|
+
continue;
|
|
512
|
+
// also handle unprefixed version (mirroring the behaviour of getElementsByTagName)
|
|
513
|
+
// Text content
|
|
514
|
+
if (child.tagName === "w:t" || child.tagName === "t" || child.tagName === "m:t") {
|
|
515
|
+
const tNode = child;
|
|
516
|
+
const tContent = tNode.textContent || '';
|
|
517
|
+
text += tContent;
|
|
518
|
+
const textNode = {
|
|
519
|
+
type: 'text',
|
|
520
|
+
text: tContent,
|
|
521
|
+
formatting: formatting
|
|
522
|
+
};
|
|
523
|
+
if (config.includeRawContent) {
|
|
524
|
+
textNode.rawContent = (0, xmlUtils_js_1.getRawContent)(tNode, documentContent, config);
|
|
525
|
+
}
|
|
526
|
+
// Always set a style: run style > paragraph style > detected default
|
|
527
|
+
// Use detected default style for international compatibility
|
|
528
|
+
const nodeStyle = rStyleVal || pStyleVal || defaultParaStyleId;
|
|
529
|
+
if (nodeStyle) {
|
|
530
|
+
textNode.metadata = { style: nodeStyle };
|
|
531
|
+
}
|
|
532
|
+
children.push(textNode);
|
|
533
|
+
}
|
|
534
|
+
// Break nodes
|
|
535
|
+
else if (config.includeBreakNodes &&
|
|
536
|
+
(child.tagName === "w:br"
|
|
537
|
+
|| child.tagName === "br"
|
|
538
|
+
|| child.tagName === "w:cr"
|
|
539
|
+
|| child.tagName === "cr")) {
|
|
540
|
+
const brNode = child;
|
|
541
|
+
let breakType = 'textWrapping';
|
|
542
|
+
if (child.tagName === "w:cr" || child.tagName === "cr") {
|
|
543
|
+
breakType = 'carriageReturn';
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
const nodeBreakType = brNode.getAttribute("w:type") || brNode.getAttribute("type");
|
|
547
|
+
if (nodeBreakType !== null) {
|
|
548
|
+
breakType = nodeBreakType;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
let breakClear = undefined;
|
|
552
|
+
if (breakType === 'textWrapping' && brNode.getAttribute("w:clear") !== null) {
|
|
553
|
+
breakClear = brNode.getAttribute("w:clear");
|
|
554
|
+
}
|
|
555
|
+
const breakNode = {
|
|
556
|
+
type: 'break',
|
|
557
|
+
metadata: { breakType, clear: breakClear }
|
|
558
|
+
};
|
|
559
|
+
if (config.includeRawContent) {
|
|
560
|
+
breakNode.rawContent = (0, xmlUtils_js_1.getRawContent)(brNode, documentContent, config);
|
|
561
|
+
}
|
|
562
|
+
children.push(breakNode);
|
|
563
|
+
}
|
|
564
|
+
else if (config.includeBreakNodes && (child.tagName === "w:lastRenderedPageBreak" || child.tagName === "lastRenderedPageBreak")) {
|
|
565
|
+
const breakNode = {
|
|
566
|
+
type: 'break',
|
|
567
|
+
metadata: { breakType: 'lastRenderedPage' }
|
|
568
|
+
};
|
|
569
|
+
if (config.includeRawContent) {
|
|
570
|
+
breakNode.rawContent = (0, xmlUtils_js_1.getRawContent)(child, documentContent, config);
|
|
571
|
+
}
|
|
572
|
+
children.push(breakNode);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// Images/Drawings
|
|
576
|
+
if (config.extractAttachments) {
|
|
577
|
+
const drawings = (0, xmlUtils_js_1.getElementsByTagName)(runNode, "w:drawing");
|
|
578
|
+
const picts = (0, xmlUtils_js_1.getElementsByTagName)(runNode, "w:pict");
|
|
579
|
+
const allImages = [...drawings, ...picts];
|
|
580
|
+
for (const imgNode of allImages) {
|
|
581
|
+
const imgXml = (0, xmlUtils_js_1.serializeXml)(imgNode);
|
|
582
|
+
// Extract Alt Text
|
|
583
|
+
let altText = '';
|
|
584
|
+
const docPr = (0, xmlUtils_js_1.getFirstElementByTagName)(imgNode, "wp:docPr");
|
|
585
|
+
if (docPr) {
|
|
586
|
+
altText = docPr.getAttribute("descr") || docPr.getAttribute("title") || '';
|
|
587
|
+
}
|
|
588
|
+
// Extract Relationship ID
|
|
589
|
+
let rId = '';
|
|
590
|
+
const blip = (0, xmlUtils_js_1.getFirstElementByTagName)(imgNode, "a:blip");
|
|
591
|
+
if (blip) {
|
|
592
|
+
rId = blip.getAttribute("r:embed") || '';
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
const imagedata = (0, xmlUtils_js_1.getFirstElementByTagName)(imgNode, "v:imagedata");
|
|
596
|
+
if (imagedata) {
|
|
597
|
+
rId = imagedata.getAttribute("r:id") || '';
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (rId && relsMap[rId]) {
|
|
601
|
+
const target = relsMap[rId];
|
|
602
|
+
const filename = target.split('/').pop();
|
|
603
|
+
if (filename) {
|
|
604
|
+
const imageNode = {
|
|
605
|
+
type: 'image',
|
|
606
|
+
text: '',
|
|
607
|
+
metadata: { attachmentName: filename, altText: altText }
|
|
608
|
+
};
|
|
609
|
+
if (config.includeRawContent) {
|
|
610
|
+
imageNode.rawContent = (0, xmlUtils_js_1.getRawContent)(imgNode, documentContent, config);
|
|
611
|
+
}
|
|
612
|
+
children.push(imageNode);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
const imageNode = {
|
|
617
|
+
type: 'image',
|
|
618
|
+
text: '',
|
|
619
|
+
};
|
|
620
|
+
if (config.includeRawContent) {
|
|
621
|
+
imageNode.rawContent = (0, xmlUtils_js_1.getRawContent)(imgNode, documentContent, config);
|
|
622
|
+
}
|
|
623
|
+
children.push(imageNode);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Footnotes/Endnotes inside runs
|
|
628
|
+
if (!config.ignoreNotes) {
|
|
629
|
+
const footnoteRef = (0, xmlUtils_js_1.getFirstElementByTagName)(runNode, "w:footnoteReference");
|
|
630
|
+
if (footnoteRef) {
|
|
631
|
+
const id = footnoteRef.getAttribute("w:id");
|
|
632
|
+
if (id && footnoteMap.has(id)) {
|
|
633
|
+
const noteNodes = footnoteMap.get(id);
|
|
634
|
+
const noteNode = {
|
|
635
|
+
type: 'note',
|
|
636
|
+
text: noteNodes.map((n) => n.text).join(' '),
|
|
637
|
+
children: noteNodes,
|
|
638
|
+
metadata: { noteType: 'footnote', noteId: id }
|
|
639
|
+
};
|
|
640
|
+
if (children.length > 0) {
|
|
641
|
+
const target = children[children.length - 1];
|
|
642
|
+
if (!target.notes)
|
|
643
|
+
target.notes = [];
|
|
644
|
+
target.notes.push(noteNode);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
notes.push(noteNode);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const endnoteRef = (0, xmlUtils_js_1.getFirstElementByTagName)(runNode, "w:endnoteReference");
|
|
652
|
+
if (endnoteRef) {
|
|
653
|
+
const id = endnoteRef.getAttribute("w:id");
|
|
654
|
+
if (id && endnoteMap.has(id)) {
|
|
655
|
+
const noteNodes = endnoteMap.get(id);
|
|
656
|
+
const noteNode = {
|
|
657
|
+
type: 'note',
|
|
658
|
+
text: noteNodes.map((n) => n.text).join(' '),
|
|
659
|
+
children: noteNodes,
|
|
660
|
+
metadata: { noteType: 'endnote', noteId: id }
|
|
661
|
+
};
|
|
662
|
+
if (children.length > 0) {
|
|
663
|
+
const target = children[children.length - 1];
|
|
664
|
+
if (!target.notes)
|
|
665
|
+
target.notes = [];
|
|
666
|
+
target.notes.push(noteNode);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
notes.push(noteNode);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// Comments inside runs
|
|
675
|
+
if (!config.ignoreComments) {
|
|
676
|
+
const commentRef = (0, xmlUtils_js_1.getFirstElementByTagName)(runNode, "w:commentReference");
|
|
677
|
+
if (commentRef) {
|
|
678
|
+
const id = commentRef.getAttribute("w:id");
|
|
679
|
+
if (id && commentMap.has(id)) {
|
|
680
|
+
const commentNodes = commentMap.get(id);
|
|
681
|
+
const commentInfo = commentMetadataMap.get(id);
|
|
682
|
+
const commentNode = {
|
|
683
|
+
type: 'comment',
|
|
684
|
+
text: commentNodes.map((n) => n.text).join(' '),
|
|
685
|
+
children: commentNodes,
|
|
686
|
+
metadata: commentInfo || { commentId: id }
|
|
687
|
+
};
|
|
688
|
+
if (children.length > 0) {
|
|
689
|
+
const target = children[children.length - 1];
|
|
690
|
+
if (!target.comments)
|
|
691
|
+
target.comments = [];
|
|
692
|
+
target.comments.push(commentNode);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
comments.push(commentNode);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
else if ((0, xmlUtils_js_1.isElement)(node) && node.nodeName === 'w:hyperlink') {
|
|
702
|
+
const hlNode = node;
|
|
703
|
+
const rId = hlNode.getAttribute("r:id");
|
|
704
|
+
const anchor = hlNode.getAttribute("w:anchor");
|
|
705
|
+
let linkMetadata;
|
|
706
|
+
if (anchor && !config.ignoreInternalLinks) {
|
|
707
|
+
linkMetadata = { link: '#' + anchor, linkType: 'internal' };
|
|
708
|
+
}
|
|
709
|
+
else if (rId && relsMap[rId]) {
|
|
710
|
+
linkMetadata = { link: relsMap[rId], linkType: 'external' };
|
|
711
|
+
}
|
|
712
|
+
// Process children of hyperlink (usually runs)
|
|
713
|
+
const hlChildren = Array.from(hlNode.childNodes);
|
|
714
|
+
for (const child of hlChildren) {
|
|
715
|
+
// Capture the current length of children to apply metadata to new nodes
|
|
716
|
+
const startIndex = children.length;
|
|
717
|
+
processChildNode(child);
|
|
718
|
+
// Apply link metadata to the newly added text nodes
|
|
719
|
+
if (linkMetadata) {
|
|
720
|
+
for (let i = startIndex; i < children.length; i++) {
|
|
721
|
+
if (children[i].type === 'text') {
|
|
722
|
+
children[i].metadata = { ...(children[i].metadata ?? {}), ...linkMetadata };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else if ((0, xmlUtils_js_1.isElement)(node) && node.nodeName === 'w:bookmarkStart') {
|
|
729
|
+
const bookmarkName = node.getAttribute("w:name");
|
|
730
|
+
if (bookmarkName && !bookmarkName.startsWith('_GoBack') && !config.ignoreInternalLinks) {
|
|
731
|
+
anchorIds.push(bookmarkName);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
else if ((0, xmlUtils_js_1.isElement)(node) && (node.nodeName === 'mc:AlternateContent' || node.nodeName === 'AlternateContent')) {
|
|
735
|
+
const resolved = resolveAlternateContent(node);
|
|
736
|
+
for (const rNode of resolved)
|
|
737
|
+
processChildNode(rNode);
|
|
738
|
+
}
|
|
739
|
+
else if ((0, xmlUtils_js_1.isElement)(node) && (node.nodeName === 'w:pict' || node.nodeName === 'pict' || node.nodeName === 'w:drawing' || node.nodeName === 'drawing')) {
|
|
740
|
+
// Extract text boxes from legacy shapes or modern drawings
|
|
741
|
+
const textBoxes = (0, xmlUtils_js_1.getElementsByTagName)(node, "w:txbxContent");
|
|
742
|
+
for (const txbx of textBoxes) {
|
|
743
|
+
const txbxChildren = Array.from(txbx.childNodes);
|
|
744
|
+
for (const txbxChild of txbxChildren) {
|
|
745
|
+
if ((0, xmlUtils_js_1.isElement)(txbxChild) && txbxChild.nodeName === 'w:p') {
|
|
746
|
+
const nestedP = parseParagraph(txbxChild, documentContent);
|
|
747
|
+
children.push(...(nestedP.children || []));
|
|
748
|
+
text += nestedP.text;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
else if ((0, xmlUtils_js_1.isElement)(node) && (node.nodeName === 'm:oMath' || node.nodeName === 'oMath'
|
|
754
|
+
|| node.nodeName === 'm:oMathPara' || node.nodeName === 'oMathPara')) {
|
|
755
|
+
// Equations. Without this branch they reach the generic fallback below, which
|
|
756
|
+
// recurses into every child and concatenates the `m:t` runs with no separators -
|
|
757
|
+
// so `<m:num>1</m:num><m:den>2</m:den>` came out as "12". That is worse than
|
|
758
|
+
// dropping the formula: the result still reads as a number, so nothing downstream
|
|
759
|
+
// can tell it is wrong.
|
|
760
|
+
//
|
|
761
|
+
// `m:oMathPara` is a display equation on its own line; a bare `m:oMath` is inline.
|
|
762
|
+
const isBlock = node.nodeName === 'm:oMathPara' || node.nodeName === 'oMathPara';
|
|
763
|
+
const latex = (0, mathUtils_js_1.ommlToLatex)(node);
|
|
764
|
+
if (!(0, mathUtils_js_1.isEmptyMath)(latex)) {
|
|
765
|
+
text += latex;
|
|
766
|
+
children.push({
|
|
767
|
+
type: 'code',
|
|
768
|
+
text: latex,
|
|
769
|
+
metadata: { math: isBlock ? 'block' : 'inline' }
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
else if (node.childNodes.length > 0) {
|
|
774
|
+
// Generic fallback for unknown elements that might contain content
|
|
775
|
+
for (const child of Array.from(node.childNodes))
|
|
776
|
+
processChildNode(child);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
const anchorIds = [...pendingAnchorIds];
|
|
780
|
+
const childNodes = Array.from(pNode.childNodes);
|
|
781
|
+
for (const child of childNodes) {
|
|
782
|
+
processChildNode(child);
|
|
783
|
+
}
|
|
784
|
+
const commonMetadata = anchorIds.length > 0 ? { anchorIds } : {};
|
|
785
|
+
if (isList) {
|
|
786
|
+
const numIdNode = (0, xmlUtils_js_1.getFirstElementByTagName)(numPr, "w:numId");
|
|
787
|
+
const ilvlNode = (0, xmlUtils_js_1.getFirstElementByTagName)(numPr, "w:ilvl");
|
|
788
|
+
const numId = numIdNode ? numIdNode.getAttribute("w:val") || '0' : '0';
|
|
789
|
+
const ilvl = ilvlNode ? parseInt(ilvlNode.getAttribute("w:val") || '0', 10) : 0;
|
|
790
|
+
let listType = 'ordered';
|
|
791
|
+
let itemIndex = 0;
|
|
792
|
+
if (numId && numberingMap[numId]) {
|
|
793
|
+
const ilvlStr = ilvl.toString();
|
|
794
|
+
if (!numberingState[numId])
|
|
795
|
+
numberingState[numId] = {};
|
|
796
|
+
if (!numberingState[numId][ilvlStr])
|
|
797
|
+
numberingState[numId][ilvlStr] = 0;
|
|
798
|
+
numberingState[numId][ilvlStr]++;
|
|
799
|
+
for (let k = ilvl + 1; k < 10; k++) {
|
|
800
|
+
if (numberingState[numId][k.toString()])
|
|
801
|
+
numberingState[numId][k.toString()] = 0;
|
|
802
|
+
}
|
|
803
|
+
const numFmt = numberingMap[numId][ilvlStr]?.numFmt || 'decimal';
|
|
804
|
+
listType = numFmt === 'bullet' ? 'unordered' : 'ordered';
|
|
805
|
+
// Track itemIndex (starts at override or default, continues across interruptions for same listId)
|
|
806
|
+
if (!listCounters[numId])
|
|
807
|
+
listCounters[numId] = {};
|
|
808
|
+
if (listCounters[numId][ilvlStr] === undefined) {
|
|
809
|
+
listCounters[numId][ilvlStr] = (numberingMap[numId][ilvlStr]?.start ?? 1) - 1;
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
listCounters[numId][ilvlStr]++;
|
|
813
|
+
}
|
|
814
|
+
itemIndex = listCounters[numId][ilvlStr];
|
|
815
|
+
}
|
|
816
|
+
const listNode = {
|
|
817
|
+
type: 'list',
|
|
818
|
+
text: text,
|
|
819
|
+
children: children,
|
|
820
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
821
|
+
...(comments.length > 0 ? { comments } : {}),
|
|
822
|
+
metadata: {
|
|
823
|
+
listType,
|
|
824
|
+
indentation: ilvl,
|
|
825
|
+
paragraphIndentation: paraIndentation,
|
|
826
|
+
alignment: (alignment || 'left'),
|
|
827
|
+
listId: numId,
|
|
828
|
+
itemIndex: itemIndex,
|
|
829
|
+
style: pStyleVal,
|
|
830
|
+
...commonMetadata
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
if (config.includeRawContent)
|
|
834
|
+
listNode.rawContent = (0, xmlUtils_js_1.getRawContent)(pNode, documentContent, config);
|
|
835
|
+
return listNode;
|
|
836
|
+
}
|
|
837
|
+
else if (isHeading) {
|
|
838
|
+
const level = pStyleVal ? parseInt(pStyleVal.replace("Heading", ""), 10) || 1 : 1;
|
|
839
|
+
const headingNode = {
|
|
840
|
+
type: 'heading',
|
|
841
|
+
text: text,
|
|
842
|
+
children: children,
|
|
843
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
844
|
+
...(comments.length > 0 ? { comments } : {}),
|
|
845
|
+
metadata: { level, alignment, paragraphIndentation: paraIndentation, style: pStyleVal ?? undefined, ...commonMetadata }
|
|
846
|
+
};
|
|
847
|
+
if (config.includeRawContent)
|
|
848
|
+
headingNode.rawContent = (0, xmlUtils_js_1.getRawContent)(pNode, documentContent, config);
|
|
849
|
+
return headingNode;
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
const paraNode = {
|
|
853
|
+
type: 'paragraph',
|
|
854
|
+
text: text,
|
|
855
|
+
children: children,
|
|
856
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
857
|
+
...(comments.length > 0 ? { comments } : {}),
|
|
858
|
+
metadata: { alignment, paragraphIndentation: paraIndentation, style: pStyleVal ?? undefined, ...commonMetadata }
|
|
859
|
+
};
|
|
860
|
+
if (config.includeRawContent)
|
|
861
|
+
paraNode.rawContent = (0, xmlUtils_js_1.getRawContent)(pNode, documentContent, config);
|
|
862
|
+
return paraNode;
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
// Helper to parse a table node
|
|
866
|
+
const parseTable = (tblNode, documentContent, pendingAnchorIds = []) => {
|
|
867
|
+
const rows = [];
|
|
868
|
+
const trNodes = (0, xmlUtils_js_1.getDirectChildren)(tblNode, "w:tr");
|
|
869
|
+
// Track vertical merges: colIndex -> { startCellNode, rowSpan }
|
|
870
|
+
const vMergeMap = new Map();
|
|
871
|
+
for (let rIndex = 0; rIndex < trNodes.length; rIndex++) {
|
|
872
|
+
const trNode = trNodes[rIndex];
|
|
873
|
+
const cells = [];
|
|
874
|
+
// Only get direct child cells, not nested table cells
|
|
875
|
+
const tcNodes = (0, xmlUtils_js_1.getDirectChildren)(trNode, "w:tc");
|
|
876
|
+
let visualCol = 0;
|
|
877
|
+
for (let tcIndex = 0; tcIndex < tcNodes.length; tcIndex++) {
|
|
878
|
+
const tcNode = tcNodes[tcIndex];
|
|
879
|
+
const tcPr = (0, xmlUtils_js_1.getFirstElementByTagName)(tcNode, "w:tcPr");
|
|
880
|
+
// Horizontal merge (colspan)
|
|
881
|
+
let colSpan = 1;
|
|
882
|
+
if (tcPr) {
|
|
883
|
+
const gridSpan = (0, xmlUtils_js_1.getFirstElementByTagName)(tcPr, "w:gridSpan");
|
|
884
|
+
if (gridSpan) {
|
|
885
|
+
colSpan = parseInt(gridSpan.getAttribute("w:val") || "1", 10);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
let vMergeRestart = false;
|
|
889
|
+
let isVMerge = false;
|
|
890
|
+
if (tcPr) {
|
|
891
|
+
const vMerge = (0, xmlUtils_js_1.getFirstElementByTagName)(tcPr, "w:vMerge");
|
|
892
|
+
if (vMerge) {
|
|
893
|
+
isVMerge = true;
|
|
894
|
+
const val = vMerge.getAttribute("w:val");
|
|
895
|
+
// If it's explicit restart, or if we don't have an active merge for this column, treat as restart
|
|
896
|
+
if (val === "restart" || !vMergeMap.has(visualCol)) {
|
|
897
|
+
vMergeRestart = true;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const cellChildren = [];
|
|
902
|
+
let cellText = '';
|
|
903
|
+
// Cells contain paragraphs (and other block-level elements)
|
|
904
|
+
const cellContentNodes = Array.from(tcNode.childNodes);
|
|
905
|
+
for (const child of cellContentNodes) {
|
|
906
|
+
if ((0, xmlUtils_js_1.isElement)(child) && child.nodeName === 'w:p') {
|
|
907
|
+
const pNode = parseParagraph(child, documentContent);
|
|
908
|
+
cellChildren.push(pNode);
|
|
909
|
+
cellText += pNode.text;
|
|
910
|
+
}
|
|
911
|
+
else if ((0, xmlUtils_js_1.isElement)(child) && child.nodeName === 'w:tbl') {
|
|
912
|
+
const nestedTable = parseTable(child, documentContent);
|
|
913
|
+
cellChildren.push(nestedTable);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const cellNode = {
|
|
917
|
+
type: 'cell',
|
|
918
|
+
text: cellText,
|
|
919
|
+
children: cellChildren,
|
|
920
|
+
metadata: { row: rIndex, col: visualCol }
|
|
921
|
+
};
|
|
922
|
+
if (colSpan > 1)
|
|
923
|
+
cellNode.metadata.colSpan = colSpan;
|
|
924
|
+
if (tcPr) {
|
|
925
|
+
const shd = (0, xmlUtils_js_1.getFirstElementByTagName)(tcPr, "w:shd");
|
|
926
|
+
if (shd) {
|
|
927
|
+
const fill = shd.getAttribute("w:fill");
|
|
928
|
+
if (fill && fill !== "auto") {
|
|
929
|
+
cellNode.metadata.backgroundColor = "#" + fill;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (isVMerge) {
|
|
934
|
+
if (vMergeRestart) {
|
|
935
|
+
vMergeMap.set(visualCol, { node: cellNode, span: 1 });
|
|
936
|
+
cells.push(cellNode);
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
const mergeInfo = vMergeMap.get(visualCol);
|
|
940
|
+
if (mergeInfo) {
|
|
941
|
+
mergeInfo.span++;
|
|
942
|
+
mergeInfo.node.metadata.rowSpan = mergeInfo.span;
|
|
943
|
+
if (cellChildren.length > 0) {
|
|
944
|
+
if (!mergeInfo.node.children)
|
|
945
|
+
mergeInfo.node.children = [];
|
|
946
|
+
mergeInfo.node.children.push(...cellChildren);
|
|
947
|
+
mergeInfo.node.text += " " + cellText;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
// Fallback: if we found a continue but no restart, treat as normal cell
|
|
952
|
+
cells.push(cellNode);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
vMergeMap.delete(visualCol);
|
|
958
|
+
cells.push(cellNode);
|
|
959
|
+
}
|
|
960
|
+
visualCol += colSpan;
|
|
961
|
+
}
|
|
962
|
+
const rowNode = {
|
|
963
|
+
type: 'row',
|
|
964
|
+
children: cells,
|
|
965
|
+
};
|
|
966
|
+
rows.push(rowNode);
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
type: 'table',
|
|
970
|
+
children: rows
|
|
971
|
+
};
|
|
972
|
+
};
|
|
973
|
+
// Pre-process footnotes and endnotes to be inserted inline later
|
|
974
|
+
if (!config.ignoreNotes) {
|
|
975
|
+
const footnotesFile = files.find(f => f.path.match(footnotesFileRegex));
|
|
976
|
+
if (footnotesFile) {
|
|
977
|
+
const footnotesDoc = (0, xmlUtils_js_1.parseXmlString)(footnotesFile.content.toString());
|
|
978
|
+
const footnoteXml = footnotesFile.content.toString();
|
|
979
|
+
const footnoteNodes = (0, xmlUtils_js_1.getElementsByTagName)(footnotesDoc, "w:footnote");
|
|
980
|
+
for (const node of footnoteNodes) {
|
|
981
|
+
const id = node.getAttribute("w:id");
|
|
982
|
+
if (!id || id === "-1" || id === "0")
|
|
983
|
+
continue;
|
|
984
|
+
const pNodes = (0, xmlUtils_js_1.getElementsByTagName)(node, "w:p");
|
|
985
|
+
footnoteMap.set(id, pNodes.map(p => parseParagraph(p, footnoteXml)));
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const endnotesFile = files.find(f => f.path.match(endnotesFileRegex));
|
|
989
|
+
if (endnotesFile) {
|
|
990
|
+
const endnotesDoc = (0, xmlUtils_js_1.parseXmlString)(endnotesFile.content.toString());
|
|
991
|
+
const endnoteXml = endnotesFile.content.toString();
|
|
992
|
+
const endnoteNodes = (0, xmlUtils_js_1.getElementsByTagName)(endnotesDoc, "w:endnote");
|
|
993
|
+
for (const node of endnoteNodes) {
|
|
994
|
+
const id = node.getAttribute("w:id");
|
|
995
|
+
if (!id || id === "-1" || id === "0")
|
|
996
|
+
continue;
|
|
997
|
+
const pNodes = (0, xmlUtils_js_1.getElementsByTagName)(node, "w:p");
|
|
998
|
+
endnoteMap.set(id, pNodes.map(p => parseParagraph(p, endnoteXml)));
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
// Pre-process comments
|
|
1003
|
+
if (!config.ignoreComments) {
|
|
1004
|
+
const commentsFile = files.find(f => f.path.match(commentsFileRegex));
|
|
1005
|
+
if (commentsFile) {
|
|
1006
|
+
const commentsDoc = (0, xmlUtils_js_1.parseXmlString)(commentsFile.content.toString());
|
|
1007
|
+
const commentsXml = commentsFile.content.toString();
|
|
1008
|
+
const commentNodes = (0, xmlUtils_js_1.getElementsByTagName)(commentsDoc, "w:comment");
|
|
1009
|
+
for (const node of commentNodes) {
|
|
1010
|
+
const id = node.getAttribute("w:id");
|
|
1011
|
+
if (!id)
|
|
1012
|
+
continue;
|
|
1013
|
+
const author = node.getAttribute("w:author") || undefined;
|
|
1014
|
+
const date = node.getAttribute("w:date") || undefined;
|
|
1015
|
+
const initials = node.getAttribute("w:initials") || undefined;
|
|
1016
|
+
commentMetadataMap.set(id, { commentId: id, author, date, initials });
|
|
1017
|
+
const pNodes = (0, xmlUtils_js_1.getElementsByTagName)(node, "w:p");
|
|
1018
|
+
commentMap.set(id, pNodes.map(p => parseParagraph(p, commentsXml)));
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
// Pre-process headers and footers
|
|
1023
|
+
const headers = [];
|
|
1024
|
+
const footers = [];
|
|
1025
|
+
if (!config.ignoreHeadersAndFooters) {
|
|
1026
|
+
const headerFiles = files.filter(f => f.path.match(headerFileRegex));
|
|
1027
|
+
for (const hFile of headerFiles) {
|
|
1028
|
+
const hDoc = (0, xmlUtils_js_1.parseXmlString)(hFile.content.toString());
|
|
1029
|
+
const hXml = hFile.content.toString();
|
|
1030
|
+
const hNodes = Array.from(hDoc.documentElement.childNodes).filter(xmlUtils_js_1.isElement);
|
|
1031
|
+
for (const child of hNodes) {
|
|
1032
|
+
if (child.nodeName === 'w:p')
|
|
1033
|
+
headers.push(parseParagraph(child, hXml));
|
|
1034
|
+
else if (child.nodeName === 'w:tbl')
|
|
1035
|
+
headers.push(parseTable(child, hXml));
|
|
1036
|
+
else if (child.nodeName === 'w:sdt') {
|
|
1037
|
+
const contentNode = (0, xmlUtils_js_1.getFirstElementByTagName)(child, "w:sdtContent");
|
|
1038
|
+
if (contentNode) {
|
|
1039
|
+
for (const sdtChild of Array.from(contentNode.childNodes).filter(xmlUtils_js_1.isElement)) {
|
|
1040
|
+
if (sdtChild.nodeName === 'w:p')
|
|
1041
|
+
headers.push(parseParagraph(sdtChild, hXml));
|
|
1042
|
+
else if (sdtChild.nodeName === 'w:tbl')
|
|
1043
|
+
headers.push(parseTable(sdtChild, hXml));
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
const footerFiles = files.filter(f => f.path.match(footerFileRegex));
|
|
1050
|
+
for (const fFile of footerFiles) {
|
|
1051
|
+
const fDoc = (0, xmlUtils_js_1.parseXmlString)(fFile.content.toString());
|
|
1052
|
+
const fXml = fFile.content.toString();
|
|
1053
|
+
const fNodes = Array.from(fDoc.documentElement.childNodes).filter(xmlUtils_js_1.isElement);
|
|
1054
|
+
for (const child of fNodes) {
|
|
1055
|
+
if (child.nodeName === 'w:p')
|
|
1056
|
+
footers.push(parseParagraph(child, fXml));
|
|
1057
|
+
else if (child.nodeName === 'w:tbl')
|
|
1058
|
+
footers.push(parseTable(child, fXml));
|
|
1059
|
+
else if (child.nodeName === 'w:sdt') {
|
|
1060
|
+
const contentNode = (0, xmlUtils_js_1.getFirstElementByTagName)(child, "w:sdtContent");
|
|
1061
|
+
if (contentNode) {
|
|
1062
|
+
for (const sdtChild of Array.from(contentNode.childNodes).filter(xmlUtils_js_1.isElement)) {
|
|
1063
|
+
if (sdtChild.nodeName === 'w:p')
|
|
1064
|
+
footers.push(parseParagraph(sdtChild, fXml));
|
|
1065
|
+
else if (sdtChild.nodeName === 'w:tbl')
|
|
1066
|
+
footers.push(parseTable(sdtChild, fXml));
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
for (const file of files) {
|
|
1074
|
+
if (file.path.match(mediaFileRegex))
|
|
1075
|
+
continue;
|
|
1076
|
+
if (file.path.match(numberingFileRegex))
|
|
1077
|
+
continue;
|
|
1078
|
+
if (file.path.match(relsFileRegex))
|
|
1079
|
+
continue;
|
|
1080
|
+
if (file.path.match(stylesFileRegex))
|
|
1081
|
+
continue;
|
|
1082
|
+
if (file.path.match(footnotesFileRegex))
|
|
1083
|
+
continue;
|
|
1084
|
+
if (file.path.match(endnotesFileRegex))
|
|
1085
|
+
continue;
|
|
1086
|
+
if (file.path.match(commentsFileRegex))
|
|
1087
|
+
continue;
|
|
1088
|
+
if (file.path.match(headerFileRegex))
|
|
1089
|
+
continue;
|
|
1090
|
+
if (file.path.match(footerFileRegex))
|
|
1091
|
+
continue;
|
|
1092
|
+
const documentContent = file.content.toString();
|
|
1093
|
+
const doc = (0, xmlUtils_js_1.parseXmlString)(documentContent, { locator: config.includeRawContent });
|
|
1094
|
+
const body = (0, xmlUtils_js_1.getFirstElementByTagName)(doc, "w:body");
|
|
1095
|
+
if (body) {
|
|
1096
|
+
const bodyChildren = Array.from(body.childNodes);
|
|
1097
|
+
let pendingAnchorIds = [];
|
|
1098
|
+
for (const child of bodyChildren) {
|
|
1099
|
+
(0, errorUtils_js_1.checkAbortSignal)(config.abortSignal);
|
|
1100
|
+
if ((0, xmlUtils_js_1.isElement)(child)) {
|
|
1101
|
+
if (child.nodeName === 'w:p') {
|
|
1102
|
+
content.push(parseParagraph(child, documentContent, pendingAnchorIds));
|
|
1103
|
+
pendingAnchorIds = [];
|
|
1104
|
+
}
|
|
1105
|
+
else if (child.nodeName === 'w:tbl') {
|
|
1106
|
+
content.push(parseTable(child, documentContent, pendingAnchorIds));
|
|
1107
|
+
pendingAnchorIds = [];
|
|
1108
|
+
}
|
|
1109
|
+
else if (child.nodeName === 'w:bookmarkStart') {
|
|
1110
|
+
const bookmarkName = child.getAttribute("w:name");
|
|
1111
|
+
if (bookmarkName && !bookmarkName.startsWith('_GoBack') && !config.ignoreInternalLinks) {
|
|
1112
|
+
pendingAnchorIds.push(bookmarkName);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
// Extract attachments
|
|
1120
|
+
if (config.extractAttachments) {
|
|
1121
|
+
for (const media of mediaFiles) {
|
|
1122
|
+
const attachment = (0, imageUtils_js_1.createAttachment)(media.path.split('/').pop() || 'image', media.content);
|
|
1123
|
+
attachments.push(attachment);
|
|
1124
|
+
if (config.ocr) {
|
|
1125
|
+
if (attachment.mimeType.startsWith('image/')) {
|
|
1126
|
+
try {
|
|
1127
|
+
attachment.ocrText = (await (0, ocrUtils_js_1.performOcr)(media.content, { ...config.ocrConfig })).trim();
|
|
1128
|
+
}
|
|
1129
|
+
catch (e) {
|
|
1130
|
+
(0, errorUtils_js_1.logWarning)(types_js_1.OfficeWarningType.OCR_FAILED, config, attachment.name, e);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
// Assign OCR text to image nodes
|
|
1136
|
+
if (config.ocr) {
|
|
1137
|
+
const assignOcr = (nodes) => {
|
|
1138
|
+
for (const node of nodes) {
|
|
1139
|
+
if (node.type === 'image' && 'attachmentName' in (node.metadata || {})) {
|
|
1140
|
+
const meta = node.metadata;
|
|
1141
|
+
const attachment = attachments.find(a => a.name === meta.attachmentName);
|
|
1142
|
+
if (attachment && attachment.ocrText) {
|
|
1143
|
+
node.text = attachment.ocrText;
|
|
1144
|
+
attachment.altText = meta.altText;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (node.children) {
|
|
1148
|
+
assignOcr(node.children);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
assignOcr(content);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
const toTextSync = () => content.map(c => {
|
|
1156
|
+
// Recursive text extraction
|
|
1157
|
+
const getText = (node) => {
|
|
1158
|
+
let t = '';
|
|
1159
|
+
if (node.children) {
|
|
1160
|
+
t += node.children.map(getText).filter(t => t != '').join(!node.children[0]?.children ? '' : config.newlineDelimiter);
|
|
1161
|
+
}
|
|
1162
|
+
else if (node.type === 'break') {
|
|
1163
|
+
t += config.newlineDelimiter;
|
|
1164
|
+
}
|
|
1165
|
+
else
|
|
1166
|
+
t += node.text || '';
|
|
1167
|
+
return t;
|
|
1168
|
+
};
|
|
1169
|
+
return getText(c);
|
|
1170
|
+
}).filter(t => t != '').join(config.newlineDelimiter);
|
|
1171
|
+
const auxiliaryContent = (headers.length > 0 || footers.length > 0) ? {
|
|
1172
|
+
...(headers.length > 0 ? { headers } : {}),
|
|
1173
|
+
...(footers.length > 0 ? { footers } : {})
|
|
1174
|
+
} : undefined;
|
|
1175
|
+
return (0, astUtils_js_1.createAST)('docx', { ...metadata, formatting: docDefaults, styleMap: styleMap }, content, attachments, config, auxiliaryContent, toTextSync);
|
|
1176
|
+
};
|
|
1177
|
+
exports.parseWord = parseWord;
|