@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,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML Parsing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides helper functions for parsing and navigating XML documents.
|
|
5
|
+
* Used extensively by OOXML parsers (DOCX, XLSX, PPTX) and OpenOffice parsers (ODT, ODP, ODS).
|
|
6
|
+
*
|
|
7
|
+
* OOXML (Office Open XML) is an XML-based format used by Microsoft Office.
|
|
8
|
+
* Documents are ZIP archives containing multiple XML files describing structure, content, and formatting.
|
|
9
|
+
*
|
|
10
|
+
* @module xmlUtils
|
|
11
|
+
*/
|
|
12
|
+
import { OfficeMetadata } from '../types';
|
|
13
|
+
/**
|
|
14
|
+
* Type guard for Element nodes.
|
|
15
|
+
*/
|
|
16
|
+
export declare const isElement: (node: Node) => node is Element;
|
|
17
|
+
/**
|
|
18
|
+
* Parses an XML string into a DOM Document object.
|
|
19
|
+
*
|
|
20
|
+
* Uses the @xmldom/xmldom library to parse XML strings in a Node.js environment.
|
|
21
|
+
*
|
|
22
|
+
* @param xml - The XML content as a string
|
|
23
|
+
* @param options - Optional parser settings (e.g., enable locators for source mapping)
|
|
24
|
+
* @returns A Document object that can be queried using standard DOM methods
|
|
25
|
+
*/
|
|
26
|
+
export declare const parseXmlString: (xml: string, options?: {
|
|
27
|
+
locator?: boolean;
|
|
28
|
+
}) => Document;
|
|
29
|
+
/**
|
|
30
|
+
* Gets all elements with a specific tag name and returns them as an array.
|
|
31
|
+
*
|
|
32
|
+
* This is a convenience wrapper around the DOM API's getElementsByTagName method
|
|
33
|
+
* that converts the HTMLCollection/NodeList to a proper JavaScript array for easier manipulation.
|
|
34
|
+
*
|
|
35
|
+
* @param element - The element or document to search within
|
|
36
|
+
* @param tagName - The tag name to search for (e.g., 'w:t', 'w:p', 'item')
|
|
37
|
+
* @returns An array of matching elements (empty array if none found)
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const paragraphs = getElementsByTagName(doc, 'w:p');
|
|
41
|
+
* paragraphs.forEach(p => console.log(p.textContent));
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare const getElementsByTagName: (element: Element | Document, tagName: string) => Element[];
|
|
45
|
+
export declare const serializeXml: (node: Node, options?: {
|
|
46
|
+
preserveWhitespace?: boolean;
|
|
47
|
+
}) => string;
|
|
48
|
+
/**
|
|
49
|
+
* Attempts to extract the original raw substring from the source XML for a given node.
|
|
50
|
+
* Requires the document to have been parsed with { locator: true }.
|
|
51
|
+
*
|
|
52
|
+
* @param node - The DOM node to extract source for
|
|
53
|
+
* @param sourceXml - The original XML source string
|
|
54
|
+
* @returns The raw XML substring, or undefined if it cannot be reliably determined
|
|
55
|
+
*/
|
|
56
|
+
export declare const getSourceSubstring: (node: any, sourceXml: string) => string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* High-level helper to get raw content for a node based on OfficeParserConfig.
|
|
59
|
+
*
|
|
60
|
+
* @param node - The DOM node
|
|
61
|
+
* @param sourceXml - The original source XML string
|
|
62
|
+
* @param config - The parser configuration
|
|
63
|
+
* @returns The raw content string (serialized or original)
|
|
64
|
+
*/
|
|
65
|
+
export declare const getRawContent: (node: Node, sourceXml: string, config: {
|
|
66
|
+
serializeRawContent?: boolean;
|
|
67
|
+
preserveXmlWhitespace?: boolean;
|
|
68
|
+
}) => string;
|
|
69
|
+
/**
|
|
70
|
+
* Gets the first element with the specified tag name within a parent element.
|
|
71
|
+
*
|
|
72
|
+
* @param parent - The parent element or document to search within
|
|
73
|
+
* @param tagName - The tag name to search for
|
|
74
|
+
* @returns The first matching element, or undefined if none found
|
|
75
|
+
*/
|
|
76
|
+
export declare const getFirstElementByTagName: (parent: Element | Document, tagName: string) => Element | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Gets the value of an attribute from an element.
|
|
79
|
+
*
|
|
80
|
+
* @param element - The element to get the attribute from
|
|
81
|
+
* @param attrName - The name of the attribute
|
|
82
|
+
* @returns The attribute value or undefined if not set
|
|
83
|
+
*/
|
|
84
|
+
export declare const getAttribute: (element: Element, attrName: string) => string | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Gets direct child elements with a specific tag name.
|
|
87
|
+
* Unlike getElementsByTagName, this does not search recursively.
|
|
88
|
+
*
|
|
89
|
+
* @param parent - The parent element
|
|
90
|
+
* @param tagName - The tag name to search for
|
|
91
|
+
* @returns An array of matching direct child elements
|
|
92
|
+
*/
|
|
93
|
+
export declare const getDirectChildren: (parent: Element, tagName: string) => Element[];
|
|
94
|
+
/**
|
|
95
|
+
* Parses OOXML document metadata from the docProps/core.xml file.
|
|
96
|
+
*
|
|
97
|
+
* OOXML documents (DOCX, XLSX, PPTX) store metadata in a standard location:
|
|
98
|
+
* `docProps/core.xml` within the ZIP archive.
|
|
99
|
+
*
|
|
100
|
+
* This file follows the Dublin Core metadata standard with OOXML-specific extensions.
|
|
101
|
+
* Common metadata elements:
|
|
102
|
+
* - dc:title - Document title
|
|
103
|
+
* - dc:creator - Original author
|
|
104
|
+
* - cp:lastModifiedBy - User who last modified the document
|
|
105
|
+
* - dcterms:created - Creation timestamp
|
|
106
|
+
* - dcterms:modified - Last modification timestamp
|
|
107
|
+
*
|
|
108
|
+
* @param xmlContent - The raw XML content string from docProps/core.xml
|
|
109
|
+
* @returns An OfficeMetadata object with extracted properties (empty object if parsing fails)
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const coreXml = files.find(f => f.path === 'docProps/core.xml').content.toString();
|
|
113
|
+
* const metadata = parseOfficeMetadata(coreXml);
|
|
114
|
+
*
|
|
115
|
+
* console.log(metadata.author); // "John Smith"
|
|
116
|
+
* console.log(metadata.title); // "Annual Report"
|
|
117
|
+
* console.log(metadata.created); // Date object
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @see https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085e39-c695-4f83-91e8-3f277bb4e111
|
|
121
|
+
*/
|
|
122
|
+
export declare const parseOfficeMetadata: (xmlContent: string) => OfficeMetadata;
|
|
123
|
+
/**
|
|
124
|
+
* Parses OOXML custom document properties from `docProps/custom.xml`.
|
|
125
|
+
*
|
|
126
|
+
* Custom properties are user-defined key/value pairs that authors can attach to OOXML documents
|
|
127
|
+
* (DOCX, XLSX, PPTX). They are stored in `docProps/custom.xml` inside the ZIP archive.
|
|
128
|
+
*
|
|
129
|
+
* Property values are typed using the `vt:` namespace (docPropsVTypes):
|
|
130
|
+
* - `vt:lpwstr` / `vt:lpstr` / `vt:bstr` → string
|
|
131
|
+
* - `vt:bool` → boolean
|
|
132
|
+
* - `vt:i1`..`vt:i8`, `vt:int`, `vt:r4`, `vt:r8`, `vt:decimal` → number
|
|
133
|
+
* - `vt:filetime` / `vt:date` → Date
|
|
134
|
+
*
|
|
135
|
+
* @param xmlContent - Raw XML string from `docProps/custom.xml`
|
|
136
|
+
* @returns A record of property name → typed value (empty object if none found)
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const customXml = files.find(f => f.path === 'docProps/custom.xml').content.toString();
|
|
140
|
+
* const props = parseOOXMLCustomProperties(customXml);
|
|
141
|
+
* console.log(props['Department']); // "Engineering"
|
|
142
|
+
* console.log(props['Priority']); // 1 (number)
|
|
143
|
+
* console.log(props['Reviewed']); // true (boolean)
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export declare const parseOOXMLCustomProperties: (xmlContent: string) => Record<string, string | number | boolean | Date>;
|
|
147
|
+
/**
|
|
148
|
+
* Parses OOXML application properties from `docProps/app.xml`.
|
|
149
|
+
*
|
|
150
|
+
* Application properties contain document statistics and application settings.
|
|
151
|
+
*
|
|
152
|
+
* @param xmlContent - Raw XML string from `docProps/app.xml`
|
|
153
|
+
* @returns A record of property name -> typed value
|
|
154
|
+
*/
|
|
155
|
+
export declare const parseOOXMLAppProperties: (xmlContent: string) => Record<string, string | number | boolean>;
|
|
156
|
+
/**
|
|
157
|
+
* Decodes XML entities (standard named entities, decimal, and hexadecimal entities) in a string.
|
|
158
|
+
* Useful when parsing content with regular expressions instead of a full DOM parser.
|
|
159
|
+
*
|
|
160
|
+
* @param text - The XML-encoded string
|
|
161
|
+
* @returns The decoded string
|
|
162
|
+
*/
|
|
163
|
+
export declare const decodeXmlEntities: (text: string) => string;
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* XML Parsing Utilities
|
|
4
|
+
*
|
|
5
|
+
* Provides helper functions for parsing and navigating XML documents.
|
|
6
|
+
* Used extensively by OOXML parsers (DOCX, XLSX, PPTX) and OpenOffice parsers (ODT, ODP, ODS).
|
|
7
|
+
*
|
|
8
|
+
* OOXML (Office Open XML) is an XML-based format used by Microsoft Office.
|
|
9
|
+
* Documents are ZIP archives containing multiple XML files describing structure, content, and formatting.
|
|
10
|
+
*
|
|
11
|
+
* @module xmlUtils
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.decodeXmlEntities = exports.parseOOXMLAppProperties = exports.parseOOXMLCustomProperties = exports.parseOfficeMetadata = exports.getDirectChildren = exports.getAttribute = exports.getFirstElementByTagName = exports.getRawContent = exports.getSourceSubstring = exports.serializeXml = exports.getElementsByTagName = exports.parseXmlString = exports.isElement = void 0;
|
|
15
|
+
const xmldom_1 = require("@xmldom/xmldom");
|
|
16
|
+
const dateUtils_js_1 = require("./dateUtils.js");
|
|
17
|
+
/**
|
|
18
|
+
* Type guard for Element nodes.
|
|
19
|
+
*/
|
|
20
|
+
const isElement = (node) => {
|
|
21
|
+
return node.nodeType === 1;
|
|
22
|
+
};
|
|
23
|
+
exports.isElement = isElement;
|
|
24
|
+
/**
|
|
25
|
+
* Parses an XML string into a DOM Document object.
|
|
26
|
+
*
|
|
27
|
+
* Uses the @xmldom/xmldom library to parse XML strings in a Node.js environment.
|
|
28
|
+
*
|
|
29
|
+
* @param xml - The XML content as a string
|
|
30
|
+
* @param options - Optional parser settings (e.g., enable locators for source mapping)
|
|
31
|
+
* @returns A Document object that can be queried using standard DOM methods
|
|
32
|
+
*/
|
|
33
|
+
const parseXmlString = (xml, options = {}) => {
|
|
34
|
+
const parser = new xmldom_1.DOMParser(options);
|
|
35
|
+
// @xmldom/xmldom 0.9.x is strict: a UTF-8 BOM (U+FEFF) prepended to the
|
|
36
|
+
// XML string causes a fatalError because the XML declaration is no longer
|
|
37
|
+
// at position 0. Strip it before parsing.
|
|
38
|
+
const sanitized = xml.charCodeAt(0) === 0xFEFF ? xml.slice(1) : xml.trim();
|
|
39
|
+
return parser.parseFromString(sanitized, "text/xml");
|
|
40
|
+
};
|
|
41
|
+
exports.parseXmlString = parseXmlString;
|
|
42
|
+
/**
|
|
43
|
+
* Gets all elements with a specific tag name and returns them as an array.
|
|
44
|
+
*
|
|
45
|
+
* This is a convenience wrapper around the DOM API's getElementsByTagName method
|
|
46
|
+
* that converts the HTMLCollection/NodeList to a proper JavaScript array for easier manipulation.
|
|
47
|
+
*
|
|
48
|
+
* @param element - The element or document to search within
|
|
49
|
+
* @param tagName - The tag name to search for (e.g., 'w:t', 'w:p', 'item')
|
|
50
|
+
* @returns An array of matching elements (empty array if none found)
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const paragraphs = getElementsByTagName(doc, 'w:p');
|
|
54
|
+
* paragraphs.forEach(p => console.log(p.textContent));
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
const getElementsByTagName = (element, tagName) => {
|
|
58
|
+
const results = Array.from(element.getElementsByTagName(tagName));
|
|
59
|
+
// Resilience: If prefixed tag (e.g., 'dc:title') not found, try local name (e.g., 'title')
|
|
60
|
+
if (results.length === 0 && tagName.includes(':')) {
|
|
61
|
+
const localName = tagName.split(':').pop();
|
|
62
|
+
return Array.from(element.getElementsByTagName(localName));
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
};
|
|
66
|
+
exports.getElementsByTagName = getElementsByTagName;
|
|
67
|
+
/**
|
|
68
|
+
* Serializes a DOM Node (Document, Element, etc.) back into an XML string.
|
|
69
|
+
* This is cross-platform and works in both Node.js and Browser environments.
|
|
70
|
+
*
|
|
71
|
+
* @param node - The DOM node to serialize
|
|
72
|
+
* @param options - Serialization options
|
|
73
|
+
* @returns The XML string representation
|
|
74
|
+
*/
|
|
75
|
+
const serializer = new xmldom_1.XMLSerializer();
|
|
76
|
+
const serializeXml = (node, options = {}) => {
|
|
77
|
+
// Note: xmldom's XMLSerializer doesn't natively support a 'pretty' or 'preserve'
|
|
78
|
+
// flag in a way that matches all user expectations, but it defaults to
|
|
79
|
+
// preserving structure. Formatting (indentation) is usually handled by the
|
|
80
|
+
// parser's initial whitespace handling.
|
|
81
|
+
// @ts-ignore - xmldom's Node is compatible with the global Node interface
|
|
82
|
+
return serializer.serializeToString(node);
|
|
83
|
+
};
|
|
84
|
+
exports.serializeXml = serializeXml;
|
|
85
|
+
/**
|
|
86
|
+
* Attempts to extract the original raw substring from the source XML for a given node.
|
|
87
|
+
* Requires the document to have been parsed with { locator: true }.
|
|
88
|
+
*
|
|
89
|
+
* @param node - The DOM node to extract source for
|
|
90
|
+
* @param sourceXml - The original XML source string
|
|
91
|
+
* @returns The raw XML substring, or undefined if it cannot be reliably determined
|
|
92
|
+
*/
|
|
93
|
+
const getSourceSubstring = (node, sourceXml) => {
|
|
94
|
+
if (!node || typeof node.lineNumber !== 'number' || typeof node.columnNumber !== 'number') {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
// Convert line/column to absolute index
|
|
98
|
+
const lines = sourceXml.split('\n');
|
|
99
|
+
let startIdx = 0;
|
|
100
|
+
for (let i = 0; i < node.lineNumber - 1; i++) {
|
|
101
|
+
startIdx += lines[i].length + 1; // +1 for newline
|
|
102
|
+
}
|
|
103
|
+
startIdx += node.columnNumber - 1;
|
|
104
|
+
// To find the end of the node, we look for the closing tag.
|
|
105
|
+
// This is a heuristic approach that works well for simple structured nodes (p, tbl, etc.)
|
|
106
|
+
// but might be complex for overlapping namespaces or malformed XML.
|
|
107
|
+
if ((0, exports.isElement)(node)) {
|
|
108
|
+
const tagName = node.tagName;
|
|
109
|
+
const closingTag = `</${tagName}>`;
|
|
110
|
+
const endIdx = sourceXml.indexOf(closingTag, startIdx);
|
|
111
|
+
if (endIdx !== -1) {
|
|
112
|
+
return sourceXml.substring(startIdx, endIdx + closingTag.length);
|
|
113
|
+
}
|
|
114
|
+
// Self-closing tag handling (e.g., <w:p/>)
|
|
115
|
+
const selfClosingEnd = sourceXml.indexOf('/>', startIdx);
|
|
116
|
+
const nextOpenTag = sourceXml.indexOf('<', startIdx + 1);
|
|
117
|
+
if (selfClosingEnd !== -1 && (nextOpenTag === -1 || selfClosingEnd < nextOpenTag)) {
|
|
118
|
+
return sourceXml.substring(startIdx, selfClosingEnd + 2);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
};
|
|
123
|
+
exports.getSourceSubstring = getSourceSubstring;
|
|
124
|
+
/**
|
|
125
|
+
* High-level helper to get raw content for a node based on OfficeParserConfig.
|
|
126
|
+
*
|
|
127
|
+
* @param node - The DOM node
|
|
128
|
+
* @param sourceXml - The original source XML string
|
|
129
|
+
* @param config - The parser configuration
|
|
130
|
+
* @returns The raw content string (serialized or original)
|
|
131
|
+
*/
|
|
132
|
+
const getRawContent = (node, sourceXml, config) => {
|
|
133
|
+
if (config.serializeRawContent === false) {
|
|
134
|
+
const original = (0, exports.getSourceSubstring)(node, sourceXml);
|
|
135
|
+
if (original)
|
|
136
|
+
return original;
|
|
137
|
+
}
|
|
138
|
+
return (0, exports.serializeXml)(node, { preserveWhitespace: config.preserveXmlWhitespace });
|
|
139
|
+
};
|
|
140
|
+
exports.getRawContent = getRawContent;
|
|
141
|
+
/**
|
|
142
|
+
* Gets the first element with the specified tag name within a parent element.
|
|
143
|
+
*
|
|
144
|
+
* @param parent - The parent element or document to search within
|
|
145
|
+
* @param tagName - The tag name to search for
|
|
146
|
+
* @returns The first matching element, or undefined if none found
|
|
147
|
+
*/
|
|
148
|
+
const getFirstElementByTagName = (parent, tagName) => {
|
|
149
|
+
const elements = parent.getElementsByTagName(tagName);
|
|
150
|
+
if (elements && elements.length > 0) {
|
|
151
|
+
return elements[0];
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
};
|
|
155
|
+
exports.getFirstElementByTagName = getFirstElementByTagName;
|
|
156
|
+
/**
|
|
157
|
+
* Gets the value of an attribute from an element.
|
|
158
|
+
*
|
|
159
|
+
* @param element - The element to get the attribute from
|
|
160
|
+
* @param attrName - The name of the attribute
|
|
161
|
+
* @returns The attribute value or undefined if not set
|
|
162
|
+
*/
|
|
163
|
+
const getAttribute = (element, attrName) => {
|
|
164
|
+
const attr = element.getAttribute(attrName);
|
|
165
|
+
return attr !== null ? attr : undefined;
|
|
166
|
+
};
|
|
167
|
+
exports.getAttribute = getAttribute;
|
|
168
|
+
/**
|
|
169
|
+
* Gets direct child elements with a specific tag name.
|
|
170
|
+
* Unlike getElementsByTagName, this does not search recursively.
|
|
171
|
+
*
|
|
172
|
+
* @param parent - The parent element
|
|
173
|
+
* @param tagName - The tag name to search for
|
|
174
|
+
* @returns An array of matching direct child elements
|
|
175
|
+
*/
|
|
176
|
+
const getDirectChildren = (parent, tagName) => {
|
|
177
|
+
const result = [];
|
|
178
|
+
if (!parent.childNodes)
|
|
179
|
+
return result;
|
|
180
|
+
for (let i = 0; i < parent.childNodes.length; i++) {
|
|
181
|
+
const child = parent.childNodes[i];
|
|
182
|
+
if ((0, exports.isElement)(child) && child.tagName === tagName) {
|
|
183
|
+
result.push(child);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
};
|
|
188
|
+
exports.getDirectChildren = getDirectChildren;
|
|
189
|
+
/**
|
|
190
|
+
* Parses OOXML document metadata from the docProps/core.xml file.
|
|
191
|
+
*
|
|
192
|
+
* OOXML documents (DOCX, XLSX, PPTX) store metadata in a standard location:
|
|
193
|
+
* `docProps/core.xml` within the ZIP archive.
|
|
194
|
+
*
|
|
195
|
+
* This file follows the Dublin Core metadata standard with OOXML-specific extensions.
|
|
196
|
+
* Common metadata elements:
|
|
197
|
+
* - dc:title - Document title
|
|
198
|
+
* - dc:creator - Original author
|
|
199
|
+
* - cp:lastModifiedBy - User who last modified the document
|
|
200
|
+
* - dcterms:created - Creation timestamp
|
|
201
|
+
* - dcterms:modified - Last modification timestamp
|
|
202
|
+
*
|
|
203
|
+
* @param xmlContent - The raw XML content string from docProps/core.xml
|
|
204
|
+
* @returns An OfficeMetadata object with extracted properties (empty object if parsing fails)
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* const coreXml = files.find(f => f.path === 'docProps/core.xml').content.toString();
|
|
208
|
+
* const metadata = parseOfficeMetadata(coreXml);
|
|
209
|
+
*
|
|
210
|
+
* console.log(metadata.author); // "John Smith"
|
|
211
|
+
* console.log(metadata.title); // "Annual Report"
|
|
212
|
+
* console.log(metadata.created); // Date object
|
|
213
|
+
* ```
|
|
214
|
+
*
|
|
215
|
+
* @see https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085e39-c695-4f83-91e8-3f277bb4e111
|
|
216
|
+
*/
|
|
217
|
+
const parseOfficeMetadata = (xmlContent) => {
|
|
218
|
+
// Step 1: Parse the XML content into a DOM document
|
|
219
|
+
const xml = (0, exports.parseXmlString)(xmlContent);
|
|
220
|
+
const metadata = {};
|
|
221
|
+
// Check for OOXML Core Properties
|
|
222
|
+
const coreProperties = (0, exports.getElementsByTagName)(xml, "cp:coreProperties")[0];
|
|
223
|
+
if (coreProperties) {
|
|
224
|
+
metadata.nativeProperties = {};
|
|
225
|
+
for (let i = 0; i < coreProperties.childNodes.length; i++) {
|
|
226
|
+
const child = coreProperties.childNodes[i];
|
|
227
|
+
if ((0, exports.isElement)(child)) {
|
|
228
|
+
metadata.nativeProperties[child.tagName] = child.textContent;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Step 3: Extract title (Dublin Core element)
|
|
232
|
+
const title = (0, exports.getElementsByTagName)(coreProperties, "dc:title")[0];
|
|
233
|
+
if (title && title.textContent)
|
|
234
|
+
metadata.title = title.textContent;
|
|
235
|
+
// Step 4: Extract author/creator (Dublin Core element)
|
|
236
|
+
const author = (0, exports.getElementsByTagName)(coreProperties, "dc:creator")[0];
|
|
237
|
+
if (author && author.textContent)
|
|
238
|
+
metadata.author = author.textContent;
|
|
239
|
+
// Step 5: Extract last modifier (OOXML Core Properties element)
|
|
240
|
+
const lastModifiedBy = (0, exports.getElementsByTagName)(coreProperties, "cp:lastModifiedBy")[0];
|
|
241
|
+
if (lastModifiedBy && lastModifiedBy.textContent)
|
|
242
|
+
metadata.lastModifiedBy = lastModifiedBy.textContent;
|
|
243
|
+
// Step 6: Extract creation date (Dublin Core Terms element)
|
|
244
|
+
const created = (0, exports.getElementsByTagName)(coreProperties, "dcterms:created")[0];
|
|
245
|
+
if (created && created.textContent)
|
|
246
|
+
metadata.created = (0, dateUtils_js_1.parseOfficeDate)(created.textContent);
|
|
247
|
+
// Step 7: Extract last modification date (Dublin Core Terms element)
|
|
248
|
+
const modified = (0, exports.getElementsByTagName)(coreProperties, "dcterms:modified")[0];
|
|
249
|
+
if (modified && modified.textContent)
|
|
250
|
+
metadata.modified = (0, dateUtils_js_1.parseOfficeDate)(modified.textContent);
|
|
251
|
+
// Step 8: Extract description and subject (Dublin Core elements)
|
|
252
|
+
const description = (0, exports.getElementsByTagName)(coreProperties, "dc:description")[0];
|
|
253
|
+
if (description && description.textContent)
|
|
254
|
+
metadata.description = description.textContent;
|
|
255
|
+
const subject = (0, exports.getElementsByTagName)(coreProperties, "dc:subject")[0];
|
|
256
|
+
if (subject && subject.textContent)
|
|
257
|
+
metadata.subject = subject.textContent;
|
|
258
|
+
const keywords = (0, exports.getElementsByTagName)(coreProperties, "cp:keywords")[0];
|
|
259
|
+
if (keywords && keywords.textContent)
|
|
260
|
+
metadata.keywords = keywords.textContent;
|
|
261
|
+
return metadata;
|
|
262
|
+
}
|
|
263
|
+
// Check for ODF Meta
|
|
264
|
+
const officeMeta = (0, exports.getElementsByTagName)(xml, "office:meta")[0];
|
|
265
|
+
if (officeMeta) {
|
|
266
|
+
metadata.nativeProperties = {};
|
|
267
|
+
for (let i = 0; i < officeMeta.childNodes.length; i++) {
|
|
268
|
+
const child = officeMeta.childNodes[i];
|
|
269
|
+
if ((0, exports.isElement)(child)) {
|
|
270
|
+
metadata.nativeProperties[child.tagName] = child.textContent;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const title = (0, exports.getElementsByTagName)(officeMeta, "dc:title")[0];
|
|
274
|
+
if (title && title.textContent)
|
|
275
|
+
metadata.title = title.textContent;
|
|
276
|
+
const author = (0, exports.getElementsByTagName)(officeMeta, "dc:creator")[0];
|
|
277
|
+
if (author && author.textContent)
|
|
278
|
+
metadata.author = author.textContent;
|
|
279
|
+
const description = (0, exports.getElementsByTagName)(officeMeta, "dc:description")[0];
|
|
280
|
+
if (description && description.textContent)
|
|
281
|
+
metadata.description = description.textContent;
|
|
282
|
+
const subject = (0, exports.getElementsByTagName)(officeMeta, "dc:subject")[0];
|
|
283
|
+
if (subject && subject.textContent)
|
|
284
|
+
metadata.subject = subject.textContent;
|
|
285
|
+
const keywordElements = (0, exports.getElementsByTagName)(officeMeta, "meta:keyword");
|
|
286
|
+
if (keywordElements.length > 0) {
|
|
287
|
+
metadata.keywords = keywordElements.map(k => k.textContent).filter(Boolean).join(', ');
|
|
288
|
+
}
|
|
289
|
+
const created = (0, exports.getElementsByTagName)(officeMeta, "meta:creation-date")[0];
|
|
290
|
+
if (created && created.textContent)
|
|
291
|
+
metadata.created = (0, dateUtils_js_1.parseOfficeDate)(created.textContent);
|
|
292
|
+
const modified = (0, exports.getElementsByTagName)(officeMeta, "dc:date")[0];
|
|
293
|
+
if (modified && modified.textContent)
|
|
294
|
+
metadata.modified = (0, dateUtils_js_1.parseOfficeDate)(modified.textContent);
|
|
295
|
+
// Extract user-defined custom properties (meta:user-defined)
|
|
296
|
+
const userDefined = (0, exports.getElementsByTagName)(officeMeta, "meta:user-defined");
|
|
297
|
+
if (userDefined.length > 0) {
|
|
298
|
+
const customProperties = {};
|
|
299
|
+
for (const el of userDefined) {
|
|
300
|
+
const name = el.getAttribute("meta:name");
|
|
301
|
+
if (!name || !el.textContent)
|
|
302
|
+
continue;
|
|
303
|
+
const valueType = el.getAttribute("meta:value-type") || "string";
|
|
304
|
+
const raw = el.textContent;
|
|
305
|
+
if (valueType === "boolean") {
|
|
306
|
+
customProperties[name] = raw.toLowerCase() === "true";
|
|
307
|
+
}
|
|
308
|
+
else if (valueType === "float") {
|
|
309
|
+
const num = Number(raw);
|
|
310
|
+
if (!isNaN(num))
|
|
311
|
+
customProperties[name] = num;
|
|
312
|
+
}
|
|
313
|
+
else if (valueType === "date" || valueType === "time") {
|
|
314
|
+
const date = (0, dateUtils_js_1.parseOfficeDate)(raw);
|
|
315
|
+
if (date)
|
|
316
|
+
customProperties[name] = date;
|
|
317
|
+
else
|
|
318
|
+
customProperties[name] = raw;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
customProperties[name] = raw;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (Object.keys(customProperties).length > 0) {
|
|
325
|
+
metadata.customProperties = customProperties;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return metadata;
|
|
330
|
+
};
|
|
331
|
+
exports.parseOfficeMetadata = parseOfficeMetadata;
|
|
332
|
+
/**
|
|
333
|
+
* Parses OOXML custom document properties from `docProps/custom.xml`.
|
|
334
|
+
*
|
|
335
|
+
* Custom properties are user-defined key/value pairs that authors can attach to OOXML documents
|
|
336
|
+
* (DOCX, XLSX, PPTX). They are stored in `docProps/custom.xml` inside the ZIP archive.
|
|
337
|
+
*
|
|
338
|
+
* Property values are typed using the `vt:` namespace (docPropsVTypes):
|
|
339
|
+
* - `vt:lpwstr` / `vt:lpstr` / `vt:bstr` → string
|
|
340
|
+
* - `vt:bool` → boolean
|
|
341
|
+
* - `vt:i1`..`vt:i8`, `vt:int`, `vt:r4`, `vt:r8`, `vt:decimal` → number
|
|
342
|
+
* - `vt:filetime` / `vt:date` → Date
|
|
343
|
+
*
|
|
344
|
+
* @param xmlContent - Raw XML string from `docProps/custom.xml`
|
|
345
|
+
* @returns A record of property name → typed value (empty object if none found)
|
|
346
|
+
* @example
|
|
347
|
+
* ```typescript
|
|
348
|
+
* const customXml = files.find(f => f.path === 'docProps/custom.xml').content.toString();
|
|
349
|
+
* const props = parseOOXMLCustomProperties(customXml);
|
|
350
|
+
* console.log(props['Department']); // "Engineering"
|
|
351
|
+
* console.log(props['Priority']); // 1 (number)
|
|
352
|
+
* console.log(props['Reviewed']); // true (boolean)
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
const parseOOXMLCustomProperties = (xmlContent) => {
|
|
356
|
+
const xml = (0, exports.parseXmlString)(xmlContent);
|
|
357
|
+
const result = {};
|
|
358
|
+
const properties = (0, exports.getElementsByTagName)(xml, "property");
|
|
359
|
+
for (const prop of properties) {
|
|
360
|
+
const name = prop.getAttribute("name");
|
|
361
|
+
if (!name)
|
|
362
|
+
continue;
|
|
363
|
+
// The value is the first child element (typed using vt: namespace)
|
|
364
|
+
for (let i = 0; i < prop.childNodes.length; i++) {
|
|
365
|
+
const child = prop.childNodes[i];
|
|
366
|
+
if (child.nodeType !== 1)
|
|
367
|
+
continue; // skip non-elements
|
|
368
|
+
const el = child;
|
|
369
|
+
const tag = el.tagName || '';
|
|
370
|
+
const text = el.textContent || '';
|
|
371
|
+
if (/vt:lpwstr|vt:lpstr|vt:bstr/.test(tag)) {
|
|
372
|
+
result[name] = text;
|
|
373
|
+
}
|
|
374
|
+
else if (/vt:bool/.test(tag)) {
|
|
375
|
+
result[name] = text.toLowerCase() === 'true';
|
|
376
|
+
}
|
|
377
|
+
else if (/vt:(i[1248]|ui[1248]|int|uint|r4|r8|decimal)/.test(tag)) {
|
|
378
|
+
const num = Number(text);
|
|
379
|
+
if (!isNaN(num))
|
|
380
|
+
result[name] = num;
|
|
381
|
+
}
|
|
382
|
+
else if (/vt:filetime|vt:date/.test(tag)) {
|
|
383
|
+
const date = (0, dateUtils_js_1.parseOfficeDate)(text);
|
|
384
|
+
if (date)
|
|
385
|
+
result[name] = date;
|
|
386
|
+
else
|
|
387
|
+
result[name] = text;
|
|
388
|
+
}
|
|
389
|
+
else if (text) {
|
|
390
|
+
// Fallback: store as string for any other vt: type
|
|
391
|
+
result[name] = text;
|
|
392
|
+
}
|
|
393
|
+
break; // only one value element per property
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return result;
|
|
397
|
+
};
|
|
398
|
+
exports.parseOOXMLCustomProperties = parseOOXMLCustomProperties;
|
|
399
|
+
/**
|
|
400
|
+
* Parses OOXML application properties from `docProps/app.xml`.
|
|
401
|
+
*
|
|
402
|
+
* Application properties contain document statistics and application settings.
|
|
403
|
+
*
|
|
404
|
+
* @param xmlContent - Raw XML string from `docProps/app.xml`
|
|
405
|
+
* @returns A record of property name -> typed value
|
|
406
|
+
*/
|
|
407
|
+
const parseOOXMLAppProperties = (xmlContent) => {
|
|
408
|
+
const xml = (0, exports.parseXmlString)(xmlContent);
|
|
409
|
+
const result = {};
|
|
410
|
+
const appProperties = (0, exports.getElementsByTagName)(xml, "Properties")[0];
|
|
411
|
+
if (appProperties) {
|
|
412
|
+
for (let i = 0; i < appProperties.childNodes.length; i++) {
|
|
413
|
+
const child = appProperties.childNodes[i];
|
|
414
|
+
if ((0, exports.isElement)(child)) {
|
|
415
|
+
const text = child.textContent || '';
|
|
416
|
+
if (text.toLowerCase() === 'true')
|
|
417
|
+
result[child.tagName] = true;
|
|
418
|
+
else if (text.toLowerCase() === 'false')
|
|
419
|
+
result[child.tagName] = false;
|
|
420
|
+
else if (!isNaN(Number(text)) && text.trim() !== '')
|
|
421
|
+
result[child.tagName] = Number(text);
|
|
422
|
+
else
|
|
423
|
+
result[child.tagName] = text;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return result;
|
|
428
|
+
};
|
|
429
|
+
exports.parseOOXMLAppProperties = parseOOXMLAppProperties;
|
|
430
|
+
/**
|
|
431
|
+
* Decodes XML entities (standard named entities, decimal, and hexadecimal entities) in a string.
|
|
432
|
+
* Useful when parsing content with regular expressions instead of a full DOM parser.
|
|
433
|
+
*
|
|
434
|
+
* @param text - The XML-encoded string
|
|
435
|
+
* @returns The decoded string
|
|
436
|
+
*/
|
|
437
|
+
const decodeXmlEntities = (text) => {
|
|
438
|
+
return text.replace(/&([^;]+);/g, (match, entity) => {
|
|
439
|
+
if (entity.startsWith('#')) {
|
|
440
|
+
if (entity[1] === 'x' || entity[1] === 'X') {
|
|
441
|
+
const hex = entity.slice(2);
|
|
442
|
+
const code = parseInt(hex, 16);
|
|
443
|
+
return (!isNaN(code) && code >= 0 && code <= 0x10FFFF) ? String.fromCodePoint(code) : match;
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
const dec = entity.slice(1);
|
|
447
|
+
const code = parseInt(dec, 10);
|
|
448
|
+
return (!isNaN(code) && code >= 0 && code <= 0x10FFFF) ? String.fromCodePoint(code) : match;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
switch (entity) {
|
|
452
|
+
case 'amp': return '&';
|
|
453
|
+
case 'lt': return '<';
|
|
454
|
+
case 'gt': return '>';
|
|
455
|
+
case 'quot': return '"';
|
|
456
|
+
case 'apos': return "'";
|
|
457
|
+
default: return match;
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
exports.decodeXmlEntities = decodeXmlEntities;
|