@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,950 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PowerPoint Presentation (PPTX) Parser
|
|
4
|
+
*
|
|
5
|
+
* **PPTX Format Overview:**
|
|
6
|
+
* PPTX is the default format for Microsoft PowerPoint since Office 2007, based on OOXML.
|
|
7
|
+
*
|
|
8
|
+
* **File Structure:**
|
|
9
|
+
* - `ppt/presentation.xml` - Presentation structure and slide list
|
|
10
|
+
* - `ppt/slides/slide1.xml` - Individual slide content
|
|
11
|
+
* - `ppt/notesSlides/notesSlide1.xml` - Speaker notes
|
|
12
|
+
* - `ppt/slideLayouts/*` - Slide layout definitions
|
|
13
|
+
* - `ppt/media/*` - Embedded images and media
|
|
14
|
+
*
|
|
15
|
+
* **Key Elements:**
|
|
16
|
+
* - `<p:sld>` - Slide
|
|
17
|
+
* - `<p:txBody>` - Text body containing paragraphs
|
|
18
|
+
* - `<a:p>` - Paragraph
|
|
19
|
+
* - `<a:r>` - Text run with formatting
|
|
20
|
+
* - `<a:t>` - Text content
|
|
21
|
+
*
|
|
22
|
+
* @module PowerPointParser
|
|
23
|
+
* @see https://www.ecma-international.org/publications-and-standards/standards/ecma-376/
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.parsePowerPoint = void 0;
|
|
27
|
+
const types_js_1 = require("../types.js");
|
|
28
|
+
const astUtils_js_1 = require("../utils/astUtils.js");
|
|
29
|
+
const chartUtils_js_1 = require("../utils/chartUtils.js");
|
|
30
|
+
const errorUtils_js_1 = require("../utils/errorUtils.js");
|
|
31
|
+
const imageUtils_js_1 = require("../utils/imageUtils.js");
|
|
32
|
+
const mathUtils_js_1 = require("../utils/mathUtils.js");
|
|
33
|
+
const ocrUtils_js_1 = require("../utils/ocrUtils.js");
|
|
34
|
+
const xmlUtils_js_1 = require("../utils/xmlUtils.js");
|
|
35
|
+
const zipUtils_js_1 = require("../utils/zipUtils.js");
|
|
36
|
+
/**
|
|
37
|
+
* Parses a PowerPoint presentation (.pptx) and extracts slides and notes.
|
|
38
|
+
*
|
|
39
|
+
* @param buffer - The PPTX file as a Buffer
|
|
40
|
+
* @param config - Parser configuration
|
|
41
|
+
* @returns A promise resolving to the parsed AST
|
|
42
|
+
*/
|
|
43
|
+
const parsePowerPoint = async (buffer, config) => {
|
|
44
|
+
// Honour cancellation requests immediately — before extracting the ZIP archive.
|
|
45
|
+
// PPTX presentations can have many slides with media/charts and optional OCR per image,
|
|
46
|
+
// so an early abort prevents decompressing and traversing data that will be discarded.
|
|
47
|
+
(0, errorUtils_js_1.checkAbortSignal)(config.abortSignal);
|
|
48
|
+
const allFilesRegex = /ppt\/(notesSlides|slides)\/(notesSlide|slide)\d+.xml/g;
|
|
49
|
+
const slidesRegex = /ppt\/slides\/slide\d+.xml/g;
|
|
50
|
+
const slideRelsRegex = /ppt\/slides\/_rels\/slide\d+\.xml\.rels/;
|
|
51
|
+
const slideNumberRegex = /lide(\d+)\.xml/;
|
|
52
|
+
const mediaFileRegex = /ppt\/media\/.*/;
|
|
53
|
+
const chartFileRegex = /ppt\/charts\/chart\d+\.xml/;
|
|
54
|
+
const corePropsFileRegex = /docProps\/core\.xml/;
|
|
55
|
+
const customPropsFileRegex = /docProps\/custom\.xml/;
|
|
56
|
+
const appPropsFileRegex = /docProps\/app\.xml/;
|
|
57
|
+
const commentsFileRegex = /ppt\/comments\/comment\d+\.xml/;
|
|
58
|
+
const commentAuthorsRegex = /ppt\/commentAuthors\.xml/;
|
|
59
|
+
const slideMastersRegex = /ppt\/slideMasters\/slideMaster\d+\.xml/;
|
|
60
|
+
const presentationFileRegex = /ppt\/presentation\.xml/;
|
|
61
|
+
const files = await (0, zipUtils_js_1.extractFiles)(buffer, x => !!x.match(config.ignoreNotes ? slidesRegex : allFilesRegex) ||
|
|
62
|
+
!!x.match(corePropsFileRegex) ||
|
|
63
|
+
!!x.match(customPropsFileRegex) ||
|
|
64
|
+
!!x.match(appPropsFileRegex) ||
|
|
65
|
+
!!x.match(slideRelsRegex) ||
|
|
66
|
+
(!config.ignoreComments && (!!x.match(commentsFileRegex) || !!x.match(commentAuthorsRegex))) ||
|
|
67
|
+
(!config.ignoreSlideMasters && !!x.match(slideMastersRegex)) ||
|
|
68
|
+
!!x.match(presentationFileRegex) ||
|
|
69
|
+
(!!config.extractAttachments && (!!x.match(mediaFileRegex) || !!x.match(chartFileRegex))), config.decompressionLimits, config);
|
|
70
|
+
// ppt/presentation.xml is the part that makes an archive a presentation, and unlike the
|
|
71
|
+
// slides it is always present: PowerPoint can save a deck with no slides at all, so an
|
|
72
|
+
// empty ppt/slides/ is a warning rather than a failure.
|
|
73
|
+
(0, zipUtils_js_1.findRequiredPart)(files, path => !!path.match(presentationFileRegex), config, { fileType: 'pptx', part: 'ppt/presentation.xml' });
|
|
74
|
+
if (!files.some(file => !!file.path.match(slidesRegex)))
|
|
75
|
+
(0, errorUtils_js_1.logWarning)(types_js_1.OfficeWarningType.NO_SLIDES_FOUND, config);
|
|
76
|
+
// Extract metadata
|
|
77
|
+
const corePropsFile = files.find(f => f.path.match(corePropsFileRegex));
|
|
78
|
+
const metadata = corePropsFile ? (0, xmlUtils_js_1.parseOfficeMetadata)(corePropsFile.content.toString()) : {};
|
|
79
|
+
const customPropsFile = files.find(f => f.path.match(customPropsFileRegex));
|
|
80
|
+
if (customPropsFile) {
|
|
81
|
+
const customProperties = (0, xmlUtils_js_1.parseOOXMLCustomProperties)(customPropsFile.content.toString());
|
|
82
|
+
if (Object.keys(customProperties).length > 0)
|
|
83
|
+
metadata.customProperties = customProperties;
|
|
84
|
+
}
|
|
85
|
+
const appPropsFile = files.find(f => f.path.match(appPropsFileRegex));
|
|
86
|
+
if (appPropsFile) {
|
|
87
|
+
const appProperties = (0, xmlUtils_js_1.parseOOXMLAppProperties)(appPropsFile.content.toString());
|
|
88
|
+
if (Object.keys(appProperties).length > 0)
|
|
89
|
+
metadata.nativeProperties = appProperties;
|
|
90
|
+
}
|
|
91
|
+
// Sort files
|
|
92
|
+
files.sort((a, b) => {
|
|
93
|
+
const aMatch = a.path.match(slideNumberRegex);
|
|
94
|
+
const bMatch = b.path.match(slideNumberRegex);
|
|
95
|
+
const aNum = aMatch ? parseInt(aMatch[1]) : 0;
|
|
96
|
+
const bNum = bMatch ? parseInt(bMatch[1]) : 0;
|
|
97
|
+
return aNum - bNum;
|
|
98
|
+
});
|
|
99
|
+
const content = [];
|
|
100
|
+
const slideRelsMap = {};
|
|
101
|
+
const authorMap = {};
|
|
102
|
+
if (!config.ignoreComments) {
|
|
103
|
+
const authorsFile = files.find(f => f.path === 'ppt/commentAuthors.xml');
|
|
104
|
+
if (authorsFile) {
|
|
105
|
+
const authorsXml = (0, xmlUtils_js_1.parseXmlString)(authorsFile.content.toString());
|
|
106
|
+
const authorNodes = (0, xmlUtils_js_1.getElementsByTagName)(authorsXml, "p:cmAuthor");
|
|
107
|
+
for (const aNode of authorNodes) {
|
|
108
|
+
const id = aNode.getAttribute("id");
|
|
109
|
+
if (id !== null) {
|
|
110
|
+
authorMap[id] = {
|
|
111
|
+
author: aNode.getAttribute("name") || undefined,
|
|
112
|
+
initials: aNode.getAttribute("initials") || undefined
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let currentListId = 0;
|
|
119
|
+
let runningListIndex = 0;
|
|
120
|
+
let lastWasList = false;
|
|
121
|
+
let lastListType = null;
|
|
122
|
+
let lastListIndent = 0;
|
|
123
|
+
// per indent counters (for nested lists)
|
|
124
|
+
const levelCounters = {};
|
|
125
|
+
// Helper to parse a table node
|
|
126
|
+
const parseTable = (tblNode, xmlContentString) => {
|
|
127
|
+
const rows = [];
|
|
128
|
+
const trNodes = (0, xmlUtils_js_1.getElementsByTagName)(tblNode, "a:tr");
|
|
129
|
+
for (let rIndex = 0; rIndex < trNodes.length; rIndex++) {
|
|
130
|
+
const trNode = trNodes[rIndex];
|
|
131
|
+
const cells = [];
|
|
132
|
+
const tcNodes = (0, xmlUtils_js_1.getElementsByTagName)(trNode, "a:tc");
|
|
133
|
+
for (let cIndex = 0; cIndex < tcNodes.length; cIndex++) {
|
|
134
|
+
const tcNode = tcNodes[cIndex];
|
|
135
|
+
const cellChildren = [];
|
|
136
|
+
let cellText = '';
|
|
137
|
+
// Cells contain text bodies (txBody) which contain paragraphs
|
|
138
|
+
const txBody = (0, xmlUtils_js_1.getFirstElementByTagName)(tcNode, "a:txBody");
|
|
139
|
+
if (txBody) {
|
|
140
|
+
const paragraphs = (0, xmlUtils_js_1.getElementsByTagName)(txBody, "a:p");
|
|
141
|
+
for (const p of paragraphs) {
|
|
142
|
+
// Reuse paragraph parsing logic if possible, or duplicate for now
|
|
143
|
+
// For simplicity, duplicating basic logic here as the main loop one is tied to shapes
|
|
144
|
+
const pNode = {
|
|
145
|
+
type: 'paragraph',
|
|
146
|
+
text: '',
|
|
147
|
+
children: [],
|
|
148
|
+
metadata: {}
|
|
149
|
+
};
|
|
150
|
+
if (config.includeRawContent) {
|
|
151
|
+
pNode.rawContent = (0, xmlUtils_js_1.getRawContent)(p, xmlContentString, config);
|
|
152
|
+
}
|
|
153
|
+
const runs = (0, xmlUtils_js_1.getElementsByTagName)(p, "a:r");
|
|
154
|
+
for (const r of runs) {
|
|
155
|
+
const t = (0, xmlUtils_js_1.getFirstElementByTagName)(r, "a:t");
|
|
156
|
+
if (t && t.childNodes[0]) {
|
|
157
|
+
const textContent = t.childNodes[0].nodeValue || '';
|
|
158
|
+
pNode.text += textContent;
|
|
159
|
+
const rPr = (0, xmlUtils_js_1.getFirstElementByTagName)(r, "a:rPr");
|
|
160
|
+
const formatting = {};
|
|
161
|
+
if (rPr) {
|
|
162
|
+
if (rPr.getAttribute("b") === "1")
|
|
163
|
+
formatting.bold = true;
|
|
164
|
+
if (rPr.getAttribute("i") === "1")
|
|
165
|
+
formatting.italic = true;
|
|
166
|
+
if (rPr.getAttribute("u") === "sng")
|
|
167
|
+
formatting.underline = true;
|
|
168
|
+
if (rPr.getAttribute("strike") === "sngStrike")
|
|
169
|
+
formatting.strikethrough = true;
|
|
170
|
+
const sz = rPr.getAttribute("sz");
|
|
171
|
+
if (sz)
|
|
172
|
+
formatting.size = (parseInt(sz) / 100).toString() + 'pt';
|
|
173
|
+
const solidFill = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, "a:solidFill");
|
|
174
|
+
if (solidFill) {
|
|
175
|
+
const srgbClr = (0, xmlUtils_js_1.getFirstElementByTagName)(solidFill, "a:srgbClr");
|
|
176
|
+
if (srgbClr) {
|
|
177
|
+
const val = srgbClr.getAttribute("val");
|
|
178
|
+
if (val)
|
|
179
|
+
formatting.color = '#' + val;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const latin = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, "a:latin");
|
|
183
|
+
if (latin) {
|
|
184
|
+
const typeface = latin.getAttribute("typeface");
|
|
185
|
+
if (typeface)
|
|
186
|
+
formatting.font = typeface;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
pNode.children?.push({
|
|
190
|
+
type: 'text',
|
|
191
|
+
text: textContent,
|
|
192
|
+
formatting: formatting
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
cellChildren.push(pNode);
|
|
197
|
+
cellText += pNode.text;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
let backgroundColor;
|
|
201
|
+
const tcPr = (0, xmlUtils_js_1.getFirstElementByTagName)(tcNode, "a:tcPr");
|
|
202
|
+
if (tcPr) {
|
|
203
|
+
for (const child of Array.from(tcPr.childNodes)) {
|
|
204
|
+
if ((0, xmlUtils_js_1.isElement)(child) && child.nodeName === "a:solidFill") {
|
|
205
|
+
const srgbClr = (0, xmlUtils_js_1.getFirstElementByTagName)(child, "a:srgbClr");
|
|
206
|
+
if (srgbClr) {
|
|
207
|
+
const val = srgbClr.getAttribute("val");
|
|
208
|
+
if (val)
|
|
209
|
+
backgroundColor = "#" + val;
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const cellNode = {
|
|
216
|
+
type: 'cell',
|
|
217
|
+
text: cellText,
|
|
218
|
+
children: cellChildren,
|
|
219
|
+
metadata: { row: rIndex, col: cIndex, ...(backgroundColor ? { backgroundColor } : {}) }
|
|
220
|
+
};
|
|
221
|
+
cells.push(cellNode);
|
|
222
|
+
}
|
|
223
|
+
const rowNode = {
|
|
224
|
+
type: 'row',
|
|
225
|
+
children: cells
|
|
226
|
+
};
|
|
227
|
+
rows.push(rowNode);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
type: 'table',
|
|
231
|
+
children: rows
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
/** Extract an AST node for p:pic */
|
|
235
|
+
const extractImageNode = (imageNode, slideNumber, xmlContentString) => {
|
|
236
|
+
const blip = (0, xmlUtils_js_1.getFirstElementByTagName)(imageNode, "a:blip");
|
|
237
|
+
if (!blip)
|
|
238
|
+
return null;
|
|
239
|
+
const rId = blip.getAttribute("r:embed");
|
|
240
|
+
if (!rId)
|
|
241
|
+
return null;
|
|
242
|
+
const rel = slideRelsMap[slideNumber]?.[rId];
|
|
243
|
+
if (!rel || rel.type !== "image")
|
|
244
|
+
return null;
|
|
245
|
+
const attachmentName = rel.target;
|
|
246
|
+
const nvPicPr = (0, xmlUtils_js_1.getFirstElementByTagName)(imageNode, "p:nvPicPr");
|
|
247
|
+
const cNvPr = nvPicPr ? (0, xmlUtils_js_1.getFirstElementByTagName)(nvPicPr, "p:cNvPr") : null;
|
|
248
|
+
const altText = cNvPr?.getAttribute("descr") || undefined;
|
|
249
|
+
return {
|
|
250
|
+
type: "image",
|
|
251
|
+
text: '',
|
|
252
|
+
metadata: {
|
|
253
|
+
attachmentName,
|
|
254
|
+
altText,
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Extract an AST node for p:graphicFrame that contains a chart.
|
|
260
|
+
* ... (comments omitted for brevity) ...
|
|
261
|
+
*/
|
|
262
|
+
const extractChartNode = (frameNode, slideNumber, xmlContentString) => {
|
|
263
|
+
// Step 1: Find <a:graphicData>
|
|
264
|
+
const graphicData = (0, xmlUtils_js_1.getFirstElementByTagName)(frameNode, "a:graphicData");
|
|
265
|
+
if (!graphicData) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
// Step 2: Verify chart namespace
|
|
269
|
+
// Must be: http://schemas.openxmlformats.org/drawingml/2006/chart
|
|
270
|
+
const uri = graphicData.getAttribute("uri");
|
|
271
|
+
const isChartGraphic = uri === "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
272
|
+
if (!isChartGraphic) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
// Step 3: Find <c:chart>
|
|
276
|
+
const cChart = (0, xmlUtils_js_1.getFirstElementByTagName)(graphicData, "c:chart");
|
|
277
|
+
if (!cChart) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
// Step 4: Extract r:id (relationship id)
|
|
281
|
+
const rId = cChart.getAttribute("r:id");
|
|
282
|
+
if (!rId) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
// Step 5: Resolve relationship target from slideRelsMap
|
|
286
|
+
const rel = slideRelsMap[slideNumber]?.[rId];
|
|
287
|
+
if (!rel || rel.type !== "chart") {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
// rel.target will be something like "chart1.xml"
|
|
291
|
+
const attachmentName = rel.target;
|
|
292
|
+
// Step 6: Build AST node
|
|
293
|
+
const chartNode = {
|
|
294
|
+
type: "chart",
|
|
295
|
+
text: "", // chart text gets filled later
|
|
296
|
+
metadata: {
|
|
297
|
+
attachmentName // name used to link to attachments & chartData
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
// Optional: include raw XML of the whole frame
|
|
301
|
+
if (config.includeRawContent) {
|
|
302
|
+
chartNode.rawContent = (0, xmlUtils_js_1.getRawContent)(frameNode, xmlContentString, config);
|
|
303
|
+
}
|
|
304
|
+
return chartNode;
|
|
305
|
+
};
|
|
306
|
+
/** Extract an AST node for p:graphicFrame */
|
|
307
|
+
const extractGraphicFrameNode = (frameNode, slideNumber, xmlContentString) => {
|
|
308
|
+
const tbl = (0, xmlUtils_js_1.getFirstElementByTagName)(frameNode, "a:tbl");
|
|
309
|
+
if (tbl) {
|
|
310
|
+
const tableNode = parseTable(tbl, xmlContentString);
|
|
311
|
+
if (config.includeRawContent) {
|
|
312
|
+
tableNode.rawContent = (0, xmlUtils_js_1.getRawContent)(frameNode, xmlContentString, config);
|
|
313
|
+
}
|
|
314
|
+
if (tableNode.children && tableNode.children.length > 0) {
|
|
315
|
+
return tableNode;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (frameNode.getElementsByTagName("c:chart").length > 0) {
|
|
319
|
+
const chartNode = extractChartNode(frameNode, slideNumber, xmlContentString);
|
|
320
|
+
if (chartNode) {
|
|
321
|
+
return chartNode;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
};
|
|
326
|
+
/** Extract text and hyperlinks from a p:sp shape. */
|
|
327
|
+
const extractShapeNodes = (spNode, slideNumber, xmlContentString) => {
|
|
328
|
+
const nodes = [];
|
|
329
|
+
// Check for placeholder type (title, body, etc.)
|
|
330
|
+
const nvSpPr = (0, xmlUtils_js_1.getFirstElementByTagName)(spNode, "p:nvSpPr");
|
|
331
|
+
const nvPr = nvSpPr ? (0, xmlUtils_js_1.getFirstElementByTagName)(nvSpPr, "p:nvPr") : null;
|
|
332
|
+
const ph = nvPr ? (0, xmlUtils_js_1.getFirstElementByTagName)(nvPr, "p:ph") : null;
|
|
333
|
+
const type = ph ? ph.getAttribute("type") : "body";
|
|
334
|
+
const isTitle = type === "title" || type === "ctrTitle";
|
|
335
|
+
const txBody = (0, xmlUtils_js_1.getFirstElementByTagName)(spNode, "p:txBody");
|
|
336
|
+
if (txBody) {
|
|
337
|
+
const paragraphs = (0, xmlUtils_js_1.getElementsByTagName)(txBody, "a:p");
|
|
338
|
+
for (let i = 0; i < paragraphs.length; i++) {
|
|
339
|
+
const p = paragraphs[i];
|
|
340
|
+
let pNode;
|
|
341
|
+
if (isTitle) {
|
|
342
|
+
pNode = {
|
|
343
|
+
type: 'heading',
|
|
344
|
+
text: '',
|
|
345
|
+
children: [],
|
|
346
|
+
metadata: { level: 1 }
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
pNode = {
|
|
351
|
+
type: 'paragraph',
|
|
352
|
+
text: '',
|
|
353
|
+
children: [],
|
|
354
|
+
metadata: {}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
// Paragraph Alignment and List Detection
|
|
358
|
+
const pPr = (0, xmlUtils_js_1.getFirstElementByTagName)(p, "a:pPr");
|
|
359
|
+
let isList = false;
|
|
360
|
+
let listType = 'unordered';
|
|
361
|
+
let lvl = 0;
|
|
362
|
+
if (pPr) {
|
|
363
|
+
const lvlAttr = pPr.getAttribute("lvl");
|
|
364
|
+
if (lvlAttr)
|
|
365
|
+
lvl = parseInt(lvlAttr);
|
|
366
|
+
const buAutoNum = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "a:buAutoNum");
|
|
367
|
+
const buChar = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "a:buChar");
|
|
368
|
+
const buBlip = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "a:buBlip");
|
|
369
|
+
const buNode = (0, xmlUtils_js_1.getFirstElementByTagName)(pPr, "a:bu");
|
|
370
|
+
if (buAutoNum) {
|
|
371
|
+
isList = true;
|
|
372
|
+
listType = 'ordered';
|
|
373
|
+
}
|
|
374
|
+
else if (buChar || buBlip) {
|
|
375
|
+
isList = true;
|
|
376
|
+
listType = 'unordered';
|
|
377
|
+
}
|
|
378
|
+
else if (buNode) {
|
|
379
|
+
// inherited bullet from a list style
|
|
380
|
+
isList = true;
|
|
381
|
+
listType = 'unordered';
|
|
382
|
+
}
|
|
383
|
+
const algn = pPr.getAttribute("algn");
|
|
384
|
+
if (algn) {
|
|
385
|
+
const alignMap = {
|
|
386
|
+
'l': 'left',
|
|
387
|
+
'ctr': 'center',
|
|
388
|
+
'r': 'right',
|
|
389
|
+
'just': 'justify'
|
|
390
|
+
};
|
|
391
|
+
if (alignMap[algn]) {
|
|
392
|
+
pNode.metadata.alignment = alignMap[algn];
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (isList) {
|
|
397
|
+
const ilvl = lvl;
|
|
398
|
+
// detect a new list when bullet type changes or previous was not a list
|
|
399
|
+
const newList = !lastWasList ||
|
|
400
|
+
listType !== lastListType;
|
|
401
|
+
if (newList) {
|
|
402
|
+
// new list → new ID
|
|
403
|
+
currentListId++;
|
|
404
|
+
// clear counters for nested levels
|
|
405
|
+
for (const k in levelCounters) {
|
|
406
|
+
delete levelCounters[k];
|
|
407
|
+
}
|
|
408
|
+
// start item index at 1
|
|
409
|
+
runningListIndex = 0;
|
|
410
|
+
levelCounters[ilvl] = 0;
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// same listId, but indentation may change
|
|
414
|
+
// if going deeper → start at 1 for that level
|
|
415
|
+
if (ilvl > lastListIndent) {
|
|
416
|
+
runningListIndex = 0;
|
|
417
|
+
levelCounters[ilvl] = 0;
|
|
418
|
+
}
|
|
419
|
+
// if going shallower → restore previous level counter + 1
|
|
420
|
+
else if (ilvl < lastListIndent) {
|
|
421
|
+
// remove deeper counters
|
|
422
|
+
for (const lvlKey in levelCounters) {
|
|
423
|
+
const lv = parseInt(lvlKey);
|
|
424
|
+
if (lv > ilvl)
|
|
425
|
+
delete levelCounters[lv];
|
|
426
|
+
}
|
|
427
|
+
// continue counter at this level
|
|
428
|
+
const prev = levelCounters[ilvl] || 0;
|
|
429
|
+
runningListIndex = prev + 1;
|
|
430
|
+
levelCounters[ilvl] = runningListIndex;
|
|
431
|
+
}
|
|
432
|
+
// same level → increment
|
|
433
|
+
else {
|
|
434
|
+
const prev = levelCounters[ilvl] || 0;
|
|
435
|
+
runningListIndex = prev + 1;
|
|
436
|
+
levelCounters[ilvl] = runningListIndex;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// update tracking state
|
|
440
|
+
lastWasList = true;
|
|
441
|
+
lastListType = listType;
|
|
442
|
+
lastListIndent = ilvl;
|
|
443
|
+
// metadata output
|
|
444
|
+
pNode = {
|
|
445
|
+
type: 'list',
|
|
446
|
+
text: pNode.text,
|
|
447
|
+
children: pNode.children,
|
|
448
|
+
metadata: {
|
|
449
|
+
listType,
|
|
450
|
+
indentation: ilvl,
|
|
451
|
+
listId: currentListId.toString(),
|
|
452
|
+
itemIndex: runningListIndex,
|
|
453
|
+
alignment: pNode.metadata?.alignment || 'left',
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
lastWasList = false;
|
|
459
|
+
lastListType = null;
|
|
460
|
+
lastListIndent = 0;
|
|
461
|
+
}
|
|
462
|
+
if (isTitle && pNode.type === 'heading') {
|
|
463
|
+
pNode.metadata = { ...pNode.metadata, level: 1 };
|
|
464
|
+
}
|
|
465
|
+
if (config.includeRawContent) {
|
|
466
|
+
pNode.rawContent = (0, xmlUtils_js_1.getRawContent)(p, xmlContentString, config);
|
|
467
|
+
}
|
|
468
|
+
// Process all children of <a:p> in order (runs, breaks, fields)
|
|
469
|
+
const children = Array.from(p.childNodes);
|
|
470
|
+
let activeNode = pNode;
|
|
471
|
+
nodes.push(activeNode);
|
|
472
|
+
for (const childNode of children) {
|
|
473
|
+
if (!(0, xmlUtils_js_1.isElement)(childNode))
|
|
474
|
+
continue;
|
|
475
|
+
const element = childNode;
|
|
476
|
+
const tag = element.tagName;
|
|
477
|
+
if (tag === "a:r" || tag === "a:fld") {
|
|
478
|
+
const t = (0, xmlUtils_js_1.getFirstElementByTagName)(element, "a:t");
|
|
479
|
+
if (t && t.childNodes[0]) {
|
|
480
|
+
const textContent = t.childNodes[0].nodeValue || "";
|
|
481
|
+
activeNode.text += textContent;
|
|
482
|
+
const rPr = (0, xmlUtils_js_1.getFirstElementByTagName)(element, "a:rPr");
|
|
483
|
+
const formatting = {};
|
|
484
|
+
if (rPr) {
|
|
485
|
+
if (rPr.getAttribute("b") === "1")
|
|
486
|
+
formatting.bold = true;
|
|
487
|
+
if (rPr.getAttribute("i") === "1")
|
|
488
|
+
formatting.italic = true;
|
|
489
|
+
if (rPr.getAttribute("u") === "sng")
|
|
490
|
+
formatting.underline = true;
|
|
491
|
+
if (rPr.getAttribute("strike") === "sngStrike")
|
|
492
|
+
formatting.strikethrough = true;
|
|
493
|
+
const sz = rPr.getAttribute("sz");
|
|
494
|
+
if (sz)
|
|
495
|
+
formatting.size = (parseInt(sz) / 100).toString() + "pt";
|
|
496
|
+
// Color extraction
|
|
497
|
+
const solidFill = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, "a:solidFill");
|
|
498
|
+
if (solidFill) {
|
|
499
|
+
const srgbClr = (0, xmlUtils_js_1.getFirstElementByTagName)(solidFill, "a:srgbClr");
|
|
500
|
+
if (srgbClr) {
|
|
501
|
+
const val = srgbClr.getAttribute("val");
|
|
502
|
+
if (val)
|
|
503
|
+
formatting.color = "#" + val;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Highlight extraction
|
|
507
|
+
const highlight = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, "a:highlight");
|
|
508
|
+
if (highlight) {
|
|
509
|
+
const srgbClr = (0, xmlUtils_js_1.getFirstElementByTagName)(highlight, "a:srgbClr");
|
|
510
|
+
if (srgbClr) {
|
|
511
|
+
const val = srgbClr.getAttribute("val");
|
|
512
|
+
if (val)
|
|
513
|
+
formatting.backgroundColor = "#" + val;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// Font family
|
|
517
|
+
const latin = (0, xmlUtils_js_1.getFirstElementByTagName)(rPr, "a:latin");
|
|
518
|
+
if (latin) {
|
|
519
|
+
const typeface = latin.getAttribute("typeface");
|
|
520
|
+
if (typeface)
|
|
521
|
+
formatting.font = typeface;
|
|
522
|
+
}
|
|
523
|
+
// Subscript/Superscript
|
|
524
|
+
const baseline = rPr.getAttribute("baseline");
|
|
525
|
+
if (baseline) {
|
|
526
|
+
const baselineVal = parseInt(baseline);
|
|
527
|
+
if (baselineVal < 0)
|
|
528
|
+
formatting.subscript = true;
|
|
529
|
+
if (baselineVal > 0)
|
|
530
|
+
formatting.superscript = true;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const textNode = {
|
|
534
|
+
type: 'text',
|
|
535
|
+
text: textContent,
|
|
536
|
+
formatting: formatting
|
|
537
|
+
};
|
|
538
|
+
// Check for Hyperlinks
|
|
539
|
+
const hlinkClick = (0, xmlUtils_js_1.getFirstElementByTagName)(element, "a:hlinkClick");
|
|
540
|
+
if (hlinkClick) {
|
|
541
|
+
const rId = hlinkClick.getAttribute("r:id");
|
|
542
|
+
const action = hlinkClick.getAttribute("action");
|
|
543
|
+
let link;
|
|
544
|
+
let linkType;
|
|
545
|
+
if (rId && slideRelsMap[slideNumber] && slideRelsMap[slideNumber][rId] && slideRelsMap[slideNumber][rId].type === "hyperlink") {
|
|
546
|
+
link = slideRelsMap[slideNumber][rId].target;
|
|
547
|
+
linkType = "external";
|
|
548
|
+
}
|
|
549
|
+
else if (rId && slideRelsMap[slideNumber] && slideRelsMap[slideNumber][rId] && slideRelsMap[slideNumber][rId].type === "slide") {
|
|
550
|
+
link = slideRelsMap[slideNumber][rId].target;
|
|
551
|
+
linkType = "internal";
|
|
552
|
+
}
|
|
553
|
+
else if (action) {
|
|
554
|
+
link = action;
|
|
555
|
+
linkType = "internal";
|
|
556
|
+
}
|
|
557
|
+
if (link) {
|
|
558
|
+
textNode.metadata = { link, linkType };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
activeNode.children?.push(textNode);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
else if (tag === "a:br") {
|
|
565
|
+
if (isList) {
|
|
566
|
+
// Split the list item on soft break into a paragraph node
|
|
567
|
+
activeNode = {
|
|
568
|
+
type: 'paragraph',
|
|
569
|
+
text: '',
|
|
570
|
+
children: [],
|
|
571
|
+
metadata: {
|
|
572
|
+
paragraphIndentation: { left: lvl },
|
|
573
|
+
alignment: pNode.metadata?.alignment || 'left'
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
if (config.includeRawContent) {
|
|
577
|
+
activeNode.rawContent = (0, xmlUtils_js_1.getRawContent)(p, xmlContentString, config);
|
|
578
|
+
}
|
|
579
|
+
nodes.push(activeNode);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
// In a normal paragraph, just add a newline
|
|
583
|
+
activeNode.text += "\n";
|
|
584
|
+
activeNode.children?.push({ type: 'text', text: "\n" });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
// Equations. This loop dispatches on `a:r`/`a:fld`, so an `m:oMath` -
|
|
589
|
+
// which is a sibling of the runs, not one of them - was never visited at
|
|
590
|
+
// all and the formula vanished from the slide without a warning.
|
|
591
|
+
//
|
|
592
|
+
// PowerPoint writes the equation either directly in the paragraph or
|
|
593
|
+
// wrapped in `mc:AlternateContent`/`a14:m` for pre-2010 readers, so take
|
|
594
|
+
// the element itself when it is the equation and search inside it
|
|
595
|
+
// otherwise. `getElementsByTagName` returns document order, which is the
|
|
596
|
+
// order the equations are read in.
|
|
597
|
+
const isMath = tag === "m:oMath" || tag === "m:oMathPara";
|
|
598
|
+
const equations = isMath ? [element] : (0, xmlUtils_js_1.getElementsByTagName)(element, "m:oMath");
|
|
599
|
+
for (const equation of equations) {
|
|
600
|
+
const latex = (0, mathUtils_js_1.ommlToLatex)(equation);
|
|
601
|
+
if ((0, mathUtils_js_1.isEmptyMath)(latex))
|
|
602
|
+
continue;
|
|
603
|
+
activeNode.text += latex;
|
|
604
|
+
activeNode.children?.push({
|
|
605
|
+
type: 'code',
|
|
606
|
+
text: latex,
|
|
607
|
+
metadata: { math: tag === "m:oMathPara" ? 'block' : 'inline' }
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return nodes.filter(n => n.text?.trim() || (n.children && n.children.length > 0));
|
|
615
|
+
};
|
|
616
|
+
/**
|
|
617
|
+
* Recursively traverses a PowerPoint shape tree (p:spTree),
|
|
618
|
+
* including grouped shapes (p:grpSp), and dispatches each element
|
|
619
|
+
* to the appropriate handler (shape, image, chart, etc.).
|
|
620
|
+
*
|
|
621
|
+
* This function preserves the visual order because it processes
|
|
622
|
+
* children in the order they appear in the XML. It also accumulates
|
|
623
|
+
* transforms so nested groups inherit positional transforms.
|
|
624
|
+
*
|
|
625
|
+
* @param treeNode The XML node representing <p:spTree>
|
|
626
|
+
* @param slideNumber Current slide number for relationship resolution
|
|
627
|
+
* @param xmlContentString The source XML string for raw content extraction
|
|
628
|
+
*/
|
|
629
|
+
function traverseSpTree(treeNode, slideNumber, xmlContentString) {
|
|
630
|
+
const nodes = [];
|
|
631
|
+
// Process children in XML order (this preserves Z-order)
|
|
632
|
+
for (const child of Array.from(treeNode?.childNodes || [])) {
|
|
633
|
+
if (!(0, xmlUtils_js_1.isElement)(child)) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const element = child;
|
|
637
|
+
const tag = element.tagName;
|
|
638
|
+
// Case 1: Normal shape
|
|
639
|
+
if (tag === "p:sp") {
|
|
640
|
+
nodes.push(...extractShapeNodes(element, slideNumber, xmlContentString));
|
|
641
|
+
}
|
|
642
|
+
// Case 2: Inline picture
|
|
643
|
+
else if (tag === "p:pic") {
|
|
644
|
+
const imageNode = extractImageNode(element, slideNumber, xmlContentString);
|
|
645
|
+
if (imageNode) {
|
|
646
|
+
nodes.push(imageNode);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
// Case 3: Chart or other graphic frame
|
|
650
|
+
else if (tag === "p:graphicFrame") {
|
|
651
|
+
const tableNode = extractGraphicFrameNode(element, slideNumber, xmlContentString);
|
|
652
|
+
if (tableNode) {
|
|
653
|
+
nodes.push(tableNode);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// Case 4: Grouped shape (recursive!)
|
|
657
|
+
else if (tag === "p:grpSp") {
|
|
658
|
+
// Recurse into the group element itself which holds the child shapes
|
|
659
|
+
nodes.push(...traverseSpTree(element, slideNumber, xmlContentString));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return nodes;
|
|
663
|
+
}
|
|
664
|
+
// First pass: Process relationships
|
|
665
|
+
for (const file of files) {
|
|
666
|
+
// Check whether this file is a slideX.xml.rels file
|
|
667
|
+
if (file.path.match(slideRelsRegex)) {
|
|
668
|
+
/**
|
|
669
|
+
* Builds a map of slide number to a map of relationship IDs containing:
|
|
670
|
+
* - type: The relationship category (image, hyperlink, chart, etc)
|
|
671
|
+
* - target: The fully normalized target path inside the PPTX zip
|
|
672
|
+
*
|
|
673
|
+
* Example structure:
|
|
674
|
+
* {
|
|
675
|
+
* 1: {
|
|
676
|
+
* "rId2": { type: "image", target: "ppt/media/image3.png" },
|
|
677
|
+
* "rId5": { type: "hyperlink", target: "https://example.com" }
|
|
678
|
+
* }
|
|
679
|
+
* }
|
|
680
|
+
*
|
|
681
|
+
* @param files All extracted PPTX ZIP files.
|
|
682
|
+
* @param slideRelsMap A map of slide number to relationship info.
|
|
683
|
+
*/
|
|
684
|
+
// Extract slide number from path
|
|
685
|
+
const match = file.path.match(/slide(\d+)\.xml\.rels/);
|
|
686
|
+
if (match) {
|
|
687
|
+
// Convert matched number to integer
|
|
688
|
+
const slideNum = parseInt(match[1]);
|
|
689
|
+
// Prepare map for this slide
|
|
690
|
+
slideRelsMap[slideNum] = {};
|
|
691
|
+
// Parse the rels XML
|
|
692
|
+
const relsXml = (0, xmlUtils_js_1.parseXmlString)(file.content.toString());
|
|
693
|
+
// Get all Relationship nodes
|
|
694
|
+
const relationships = (0, xmlUtils_js_1.getElementsByTagName)(relsXml, "Relationship");
|
|
695
|
+
// Loop through each relationship node
|
|
696
|
+
for (let i = 0; i < relationships.length; i++) {
|
|
697
|
+
// Relationship ID, Example: "rId2"
|
|
698
|
+
const id = relationships[i].getAttribute("Id");
|
|
699
|
+
// Relationship Type, Example: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
|
|
700
|
+
const typeAttr = relationships[i].getAttribute("Type");
|
|
701
|
+
// Raw Target, may be relative or absolute
|
|
702
|
+
const targetRaw = relationships[i].getAttribute("Target");
|
|
703
|
+
// Only proceed if ID and Type exist
|
|
704
|
+
if (id && typeAttr && targetRaw) {
|
|
705
|
+
// Simplify Type to a short keyword (image, hyperlink, chart, etc)
|
|
706
|
+
// This is optional but very useful.
|
|
707
|
+
let simplifiedType = "other";
|
|
708
|
+
// Check image
|
|
709
|
+
if (typeAttr.includes("relationships/image")) {
|
|
710
|
+
simplifiedType = "image";
|
|
711
|
+
}
|
|
712
|
+
// Check hyperlink
|
|
713
|
+
else if (typeAttr.includes("relationships/hyperlink")) {
|
|
714
|
+
simplifiedType = "hyperlink";
|
|
715
|
+
}
|
|
716
|
+
// Check chart
|
|
717
|
+
else if (typeAttr.includes("relationships/chart")) {
|
|
718
|
+
simplifiedType = "chart";
|
|
719
|
+
}
|
|
720
|
+
// Check slide references
|
|
721
|
+
else if (typeAttr.includes("relationships/slide")) {
|
|
722
|
+
simplifiedType = "slide";
|
|
723
|
+
}
|
|
724
|
+
// Check notes
|
|
725
|
+
else if (typeAttr.includes("relationships/notesSlide")) {
|
|
726
|
+
simplifiedType = "notes";
|
|
727
|
+
}
|
|
728
|
+
// Check comments
|
|
729
|
+
else if (typeAttr.includes("relationships/comments")) {
|
|
730
|
+
simplifiedType = "comments";
|
|
731
|
+
}
|
|
732
|
+
// Now normalize the target only if it is a local file path.
|
|
733
|
+
// Hyperlinks are external and should not be normalized.
|
|
734
|
+
let normalizedTarget = targetRaw;
|
|
735
|
+
// Local paths never contain "http" or "https"
|
|
736
|
+
const isExternal = targetRaw.startsWith("http://") || targetRaw.startsWith("https://");
|
|
737
|
+
// If not external, normalize the target which is just the name of the item.
|
|
738
|
+
if (!isExternal) {
|
|
739
|
+
normalizedTarget = normalizedTarget.split('/').pop() || '';
|
|
740
|
+
}
|
|
741
|
+
// Finally store full relationship info
|
|
742
|
+
slideRelsMap[slideNum][id] = {
|
|
743
|
+
type: simplifiedType,
|
|
744
|
+
target: normalizedTarget
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const slidesMap = {};
|
|
752
|
+
const slideMasters = [];
|
|
753
|
+
// Now for processing all the other files - slides and notes.
|
|
754
|
+
for (const file of files) {
|
|
755
|
+
(0, errorUtils_js_1.checkAbortSignal)(config.abortSignal);
|
|
756
|
+
if (file.path.match(mediaFileRegex))
|
|
757
|
+
continue;
|
|
758
|
+
if (file.path.match(chartFileRegex))
|
|
759
|
+
continue;
|
|
760
|
+
if (file.path.match(slideRelsRegex))
|
|
761
|
+
continue;
|
|
762
|
+
// All three document-property parts are extracted for metadata and were read earlier;
|
|
763
|
+
// only the core one was skipped here, leaving the other two to be parsed again as if
|
|
764
|
+
// they might be slides.
|
|
765
|
+
if (file.path.match(corePropsFileRegex))
|
|
766
|
+
continue;
|
|
767
|
+
if (file.path.match(appPropsFileRegex))
|
|
768
|
+
continue;
|
|
769
|
+
if (file.path.match(customPropsFileRegex))
|
|
770
|
+
continue;
|
|
771
|
+
if (file.path.includes("comment"))
|
|
772
|
+
continue;
|
|
773
|
+
// This loop treats every remaining file as a slide or note, so the presentation part
|
|
774
|
+
// has to be skipped explicitly: it carries no slide number and would otherwise be
|
|
775
|
+
// added to the deck as an empty slide.
|
|
776
|
+
if (file.path.match(presentationFileRegex))
|
|
777
|
+
continue;
|
|
778
|
+
const xmlContentString = file.content.toString();
|
|
779
|
+
const xml = (0, xmlUtils_js_1.parseXmlString)(xmlContentString, { locator: config.includeRawContent });
|
|
780
|
+
const slideMatch = file.path.match(slideNumberRegex);
|
|
781
|
+
const slideNumber = slideMatch ? parseInt(slideMatch[1]) : 0;
|
|
782
|
+
const masterMatch = file.path.match(/slideMaster(\d+)\.xml/);
|
|
783
|
+
const masterNumber = masterMatch ? parseInt(masterMatch[1]) : 0;
|
|
784
|
+
const isNote = file.path.includes("notesSlide");
|
|
785
|
+
const isMaster = file.path.includes("slideMaster");
|
|
786
|
+
const nodeType = isNote ? 'note' : (isMaster ? 'slideMaster' : 'slide');
|
|
787
|
+
const nodeNumber = isMaster ? masterNumber : slideNumber;
|
|
788
|
+
let slideNode;
|
|
789
|
+
if (isNote) {
|
|
790
|
+
slideNode = {
|
|
791
|
+
type: 'note',
|
|
792
|
+
children: [],
|
|
793
|
+
metadata: {
|
|
794
|
+
slideNumber: nodeNumber,
|
|
795
|
+
noteId: `slide-note-${slideNumber}`
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
slideNode = {
|
|
801
|
+
type: isMaster ? 'slideMaster' : 'slide',
|
|
802
|
+
children: [],
|
|
803
|
+
metadata: {
|
|
804
|
+
slideNumber: nodeNumber
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (config.includeRawContent) {
|
|
809
|
+
slideNode.rawContent = (0, xmlUtils_js_1.getRawContent)(xml, xmlContentString, config);
|
|
810
|
+
}
|
|
811
|
+
const spTree = (0, xmlUtils_js_1.getFirstElementByTagName)(xml, "p:spTree");
|
|
812
|
+
if (spTree) {
|
|
813
|
+
slideNode.children?.push(...traverseSpTree(spTree, nodeNumber, xmlContentString));
|
|
814
|
+
}
|
|
815
|
+
if (slideNode.children && slideNode.children.length > 0) {
|
|
816
|
+
if (isMaster) {
|
|
817
|
+
slideMasters.push(slideNode);
|
|
818
|
+
}
|
|
819
|
+
else if (isNote) {
|
|
820
|
+
if (!slidesMap[slideNumber])
|
|
821
|
+
slidesMap[slideNumber] = { type: 'slide', children: [], metadata: { slideNumber } };
|
|
822
|
+
if (!slidesMap[slideNumber].notes)
|
|
823
|
+
slidesMap[slideNumber].notes = [];
|
|
824
|
+
slidesMap[slideNumber].notes.push(slideNode);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
if (!slidesMap[slideNumber]) {
|
|
828
|
+
slidesMap[slideNumber] = slideNode;
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
slidesMap[slideNumber].children = slideNode.children;
|
|
832
|
+
slidesMap[slideNumber].rawContent = slideNode.rawContent;
|
|
833
|
+
}
|
|
834
|
+
// Process comments
|
|
835
|
+
if (!config.ignoreComments && slideRelsMap[slideNumber]) {
|
|
836
|
+
const commentRels = Object.values(slideRelsMap[slideNumber]).filter(r => r.type === "comments");
|
|
837
|
+
for (const rel of commentRels) {
|
|
838
|
+
const cFile = files.find(f => f.path.endsWith(rel.target));
|
|
839
|
+
if (cFile) {
|
|
840
|
+
const cXml = (0, xmlUtils_js_1.parseXmlString)(cFile.content.toString());
|
|
841
|
+
const commentNodes = (0, xmlUtils_js_1.getElementsByTagName)(cXml, "p:cm");
|
|
842
|
+
for (const cNode of commentNodes) {
|
|
843
|
+
const authorId = cNode.getAttribute("authorId");
|
|
844
|
+
const authorData = authorId !== null ? authorMap[authorId] : undefined;
|
|
845
|
+
const text = (0, xmlUtils_js_1.getElementsByTagName)(cNode, "a:t").map(t => t.textContent || '').join('');
|
|
846
|
+
if (text) {
|
|
847
|
+
if (!slidesMap[slideNumber].comments)
|
|
848
|
+
slidesMap[slideNumber].comments = [];
|
|
849
|
+
slidesMap[slideNumber].comments.push({
|
|
850
|
+
type: 'comment',
|
|
851
|
+
text,
|
|
852
|
+
children: [{ type: 'text', text, formatting: {} }],
|
|
853
|
+
metadata: authorData && authorData.author ? { author: authorData.author } : undefined
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const sortedSlideNumbers = Object.keys(slidesMap).map(Number).sort((a, b) => a - b);
|
|
864
|
+
for (const num of sortedSlideNumbers) {
|
|
865
|
+
content.push(slidesMap[num]);
|
|
866
|
+
}
|
|
867
|
+
const attachments = [];
|
|
868
|
+
const mediaFiles = files.filter(f => f.path.match(/ppt\/media\/.*/));
|
|
869
|
+
const chartFiles = files.filter(f => f.path.match(/ppt\/charts\/chart\d+\.xml/));
|
|
870
|
+
// First run to extract attachments and to assign ocr to image files.
|
|
871
|
+
if (config.extractAttachments) {
|
|
872
|
+
// Extract media files as attachments
|
|
873
|
+
for (const media of mediaFiles) {
|
|
874
|
+
const attachment = (0, imageUtils_js_1.createAttachment)(media.path.split('/').pop() || 'image', media.content);
|
|
875
|
+
attachments.push(attachment);
|
|
876
|
+
if (config.ocr) {
|
|
877
|
+
if (attachment.mimeType.startsWith('image/')) {
|
|
878
|
+
try {
|
|
879
|
+
attachment.ocrText = (await (0, ocrUtils_js_1.performOcr)(media.content, { ...config.ocrConfig })).trim();
|
|
880
|
+
}
|
|
881
|
+
catch (e) {
|
|
882
|
+
(0, errorUtils_js_1.logWarning)(types_js_1.OfficeWarningType.OCR_FAILED, config, attachment.name, e);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
// Extract chart files as attachments
|
|
888
|
+
for (const chart of chartFiles) {
|
|
889
|
+
const attachment = {
|
|
890
|
+
type: 'chart',
|
|
891
|
+
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // Generic XML type for now
|
|
892
|
+
data: chart.content.toString('base64'),
|
|
893
|
+
name: chart.path.split('/').pop() || '',
|
|
894
|
+
extension: 'xml'
|
|
895
|
+
};
|
|
896
|
+
attachments.push(attachment);
|
|
897
|
+
// Extract text from chart XML
|
|
898
|
+
try {
|
|
899
|
+
const chartData = await (0, chartUtils_js_1.extractChartData)(chart.content);
|
|
900
|
+
// Assign chartData to attachment
|
|
901
|
+
attachment.chartData = chartData;
|
|
902
|
+
}
|
|
903
|
+
catch (e) {
|
|
904
|
+
(0, errorUtils_js_1.logWarning)(types_js_1.OfficeWarningType.CHART_DATA_EXTRACTION_FAILED, config, chart.path, e);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
// Loop through nodes to find images and charts and link their text and chartData
|
|
908
|
+
const assignAttachmentData = (nodes) => {
|
|
909
|
+
for (const node of nodes) {
|
|
910
|
+
if ('attachmentName' in (node.metadata || {})) {
|
|
911
|
+
const meta = node.metadata;
|
|
912
|
+
const attachment = attachments.find(a => a.name === meta.attachmentName);
|
|
913
|
+
if (attachment) {
|
|
914
|
+
if (node.type === 'image') {
|
|
915
|
+
attachment.altText = meta.altText;
|
|
916
|
+
if (attachment.ocrText)
|
|
917
|
+
node.text = attachment.ocrText;
|
|
918
|
+
}
|
|
919
|
+
if (node.type === 'chart') {
|
|
920
|
+
node.text = attachment.chartData?.rawTexts.join(config.newlineDelimiter);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (node.children) {
|
|
925
|
+
assignAttachmentData(node.children);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
assignAttachmentData(content);
|
|
930
|
+
}
|
|
931
|
+
// putNotesAtLast is deprecated. Notes are now structurally attached to their respective slides.
|
|
932
|
+
const toTextSync = () => content.map(c => {
|
|
933
|
+
// Recursive text extraction
|
|
934
|
+
const getText = (node) => {
|
|
935
|
+
let t = '';
|
|
936
|
+
if (node.children) {
|
|
937
|
+
t += node.children.map(getText).filter(t => t != '').join(!node.children[0]?.children ? '' : config.newlineDelimiter);
|
|
938
|
+
}
|
|
939
|
+
else
|
|
940
|
+
t += node.text || '';
|
|
941
|
+
return t;
|
|
942
|
+
};
|
|
943
|
+
return getText(c);
|
|
944
|
+
}).filter(t => t != '').join(config.newlineDelimiter);
|
|
945
|
+
const auxiliaryContent = slideMasters.length > 0 ? {
|
|
946
|
+
slideMasters
|
|
947
|
+
} : undefined;
|
|
948
|
+
return (0, astUtils_js_1.createAST)('pptx', metadata, content, attachments, config, auxiliaryContent, toTextSync);
|
|
949
|
+
};
|
|
950
|
+
exports.parsePowerPoint = parsePowerPoint;
|