@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.
Files changed (236) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +46 -0
  3. package/assets/skill/SKILL.md +7 -6
  4. package/assets/skill/cli-reference.md +14 -6
  5. package/assets/skill/mcp-reference.md +4 -1
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
  9. package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
  10. package/browser-extension/dist/manifest.json +1 -1
  11. package/browser-extension/dist/preview.html +1 -1
  12. package/browser-extension/dist/service-worker.js +32 -33
  13. package/bunfig.toml +2 -0
  14. package/package.json +40 -26
  15. package/spec/cli.md +30 -11
  16. package/spec/db/schema.sql +146 -1
  17. package/spec/mcp.md +26 -0
  18. package/src/app/context-runtime-types.ts +3 -0
  19. package/src/app/context-runtime.ts +2 -0
  20. package/src/cli/commands/ask.ts +6 -1
  21. package/src/cli/commands/daemon.ts +21 -8
  22. package/src/cli/commands/embed.ts +77 -41
  23. package/src/cli/commands/mcp/install.ts +20 -0
  24. package/src/cli/commands/mcp/paths.ts +25 -0
  25. package/src/cli/commands/mcp/status.ts +6 -0
  26. package/src/cli/detach.ts +3 -2
  27. package/src/cli/program.ts +6 -0
  28. package/src/config/types.ts +3 -3
  29. package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
  30. package/src/converters/adapters/officeparser/adapter.ts +1 -2
  31. package/src/converters/versions.ts +6 -8
  32. package/src/core/context-evidence.ts +8 -4
  33. package/src/core/job-manager.ts +95 -13
  34. package/src/core/network-boundary-inventory.ts +10 -0
  35. package/src/core/shutdown-budget.ts +45 -0
  36. package/src/embed/backlog.ts +107 -4
  37. package/src/embed/batch.ts +42 -2
  38. package/src/embed/fingerprint.ts +16 -0
  39. package/src/embed/retry.ts +113 -5
  40. package/src/embed/variant-backlog.ts +105 -0
  41. package/src/embed/variant-plan.ts +62 -0
  42. package/src/embed/variant-retry.ts +113 -0
  43. package/src/ingestion/graph-reconciliation.ts +327 -0
  44. package/src/ingestion/sync.ts +9 -272
  45. package/src/llm/http-inference.ts +6 -0
  46. package/src/llm/httpEmbedding.ts +37 -6
  47. package/src/llm/httpGeneration.ts +18 -3
  48. package/src/llm/httpRerank.ts +23 -5
  49. package/src/llm/inference-cancellation.ts +168 -0
  50. package/src/llm/inference-scope.ts +202 -0
  51. package/src/llm/lazy-ports.ts +115 -0
  52. package/src/llm/native-worker/client.ts +541 -0
  53. package/src/llm/native-worker/dispatcher.ts +228 -0
  54. package/src/llm/native-worker/embedding-identity.ts +33 -0
  55. package/src/llm/native-worker/entry.ts +173 -0
  56. package/src/llm/native-worker/errors.ts +32 -0
  57. package/src/llm/native-worker/evaluation.ts +16 -0
  58. package/src/llm/native-worker/owned-exit.ts +108 -0
  59. package/src/llm/native-worker/owner.ts +141 -0
  60. package/src/llm/native-worker/ports.ts +317 -0
  61. package/src/llm/native-worker/protocol.ts +442 -0
  62. package/src/llm/native-worker/runtime-config.ts +92 -0
  63. package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
  64. package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
  65. package/src/llm/nodeLlamaCpp/generation.ts +34 -5
  66. package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
  67. package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
  68. package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
  69. package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
  70. package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
  71. package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
  72. package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
  73. package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
  74. package/src/llm/types.ts +35 -5
  75. package/src/mcp/context.ts +27 -0
  76. package/src/mcp/http-transport.ts +12 -10
  77. package/src/mcp/server.ts +3 -0
  78. package/src/mcp/tool-profile.ts +30 -8
  79. package/src/mcp/tools/context.ts +8 -11
  80. package/src/mcp/tools/embed.ts +1 -1
  81. package/src/mcp/tools/index-cmd.ts +1 -1
  82. package/src/mcp/tools/index.ts +10 -8
  83. package/src/mcp/tools/query.ts +14 -30
  84. package/src/mcp/tools/vsearch.ts +1 -1
  85. package/src/pipeline/answer.ts +23 -3
  86. package/src/pipeline/claim-verifier.ts +6 -0
  87. package/src/pipeline/expansion.ts +43 -40
  88. package/src/pipeline/explain.ts +6 -2
  89. package/src/pipeline/filters.ts +63 -0
  90. package/src/pipeline/fusion.ts +29 -9
  91. package/src/pipeline/graph-retrieval.ts +29 -9
  92. package/src/pipeline/hybrid.ts +198 -55
  93. package/src/pipeline/hydration.ts +161 -0
  94. package/src/pipeline/owner-fusion.ts +87 -0
  95. package/src/pipeline/rerank.ts +35 -11
  96. package/src/pipeline/search.ts +13 -2
  97. package/src/pipeline/types.ts +5 -3
  98. package/src/pipeline/vsearch.ts +87 -7
  99. package/src/sdk/client.ts +47 -3
  100. package/src/sdk/embed.ts +63 -39
  101. package/src/serve/background-runtime.ts +1 -1
  102. package/src/serve/context.ts +41 -56
  103. package/src/serve/embed-scheduler.ts +58 -35
  104. package/src/serve/public/components/IndexingProgress.tsx +46 -60
  105. package/src/serve/public/globals.built.css +1 -1
  106. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  107. package/src/serve/resident-admission.ts +36 -36
  108. package/src/serve/resident-background-work.ts +20 -2
  109. package/src/serve/resident-request.ts +11 -5
  110. package/src/serve/resident-runtime.ts +97 -61
  111. package/src/serve/resident-shutdown.ts +153 -0
  112. package/src/serve/routes/api.ts +3 -1
  113. package/src/serve/server.ts +47 -26
  114. package/src/store/migrations/028-vector-variants.ts +54 -0
  115. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  116. package/src/store/migrations/index.ts +4 -0
  117. package/src/store/sqlite/adapter.ts +251 -183
  118. package/src/store/sqlite/eligibility.ts +174 -0
  119. package/src/store/sqlite/graph-edge-application.ts +66 -0
  120. package/src/store/sqlite/graph-reference-state.ts +194 -0
  121. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  122. package/src/store/types.ts +80 -12
  123. package/src/store/vector/eligibility.ts +36 -0
  124. package/src/store/vector/freshness.ts +33 -6
  125. package/src/store/vector/lazy.ts +81 -0
  126. package/src/store/vector/sqlite-vec.ts +106 -54
  127. package/src/store/vector/stats.ts +14 -3
  128. package/src/store/vector/types.ts +35 -2
  129. package/src/store/vector/variant-search.ts +192 -0
  130. package/src/store/vector/variants.ts +451 -0
  131. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  132. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  133. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  134. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  135. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  136. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  137. package/vendor/converters/markitdown-ts/package.json +77 -0
  138. package/vendor/converters/officeparser/LICENSE +21 -0
  139. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  140. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  141. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  142. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  143. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  144. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  145. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  146. package/vendor/converters/officeparser/dist/cli.js +381 -0
  147. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  148. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  149. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  150. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  151. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  152. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  153. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  154. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  155. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  156. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  157. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  158. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  159. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  160. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  161. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  162. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  163. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  164. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  165. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  166. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  167. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  168. package/vendor/converters/officeparser/dist/index.js +72 -0
  169. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  170. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  171. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  172. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  173. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  174. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  175. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  176. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  177. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  178. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  179. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  180. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  181. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  182. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  183. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  184. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  185. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  186. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  187. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  188. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  189. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  190. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  191. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  192. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  193. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  194. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  195. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  196. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  197. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  198. package/vendor/converters/officeparser/dist/types.js +107 -0
  199. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  200. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  201. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  202. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  203. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  204. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  205. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  206. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  207. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  208. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  209. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  210. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  211. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  212. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  213. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  214. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  215. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  216. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  217. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  218. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  219. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  220. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  221. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  222. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  223. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  224. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  225. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  226. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  227. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  228. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  229. package/vendor/converters/officeparser/package.json +147 -0
  230. package/vendor/converters/upstream-manifest.json +124 -0
  231. package/vendor/dependency-fixes/README.md +77 -0
  232. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  233. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip +0 -0
  234. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip.sha256 +0 -1
  235. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  236. /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;