pptx2js 0.4.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/lib/convert.js ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * 转换流水线编排器
3
+ */
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { parseXml } = require('./xml-parser');
7
+ const { unpack } = require('./unpacker');
8
+ const { buildRelationIndex } = require('./rels');
9
+ const { extractEntities } = require('./extractor');
10
+ const { mapToIR } = require('./mapper');
11
+ const { generateScript } = require('./codegen');
12
+ const {
13
+ packageMedia,
14
+ writeConversionLog,
15
+ writeOutputReadme,
16
+ } = require('./packager');
17
+
18
+ /** @typedef {object} ConvertOptions
19
+ * @property {string} [outputDir]
20
+ * @property {boolean} [noMedia]
21
+ * @property {boolean} [strictDegrade]
22
+ * @property {boolean} [strictSkip]
23
+ * @property {'minimal'|'info'|'verbose'} [logLevel]
24
+ * @property {number} [maxFileSize]
25
+ */
26
+
27
+ /** @typedef {object} ConvertResult
28
+ * @property {string} outputDir
29
+ * @property {string} scriptPath
30
+ * @property {string} logPath
31
+ * @property {object} log
32
+ */
33
+
34
+ const DEFAULT_OUTPUT = './pptx2js-output';
35
+ const DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024;
36
+
37
+ /**
38
+ * @param {string} filePath
39
+ * @param {ConvertOptions} [options]
40
+ * @returns {Promise<ConvertResult>}
41
+ */
42
+ async function convert(filePath, options = {}) {
43
+ const resolvedPath = path.resolve(filePath);
44
+ if (!fs.existsSync(resolvedPath)) {
45
+ throw new Error(`源文件不存在: ${resolvedPath}`);
46
+ }
47
+
48
+ const stat = fs.statSync(resolvedPath);
49
+ if (stat.size > (options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE)) {
50
+ // 大文件流式解析 — 实现阶段补充
51
+ }
52
+
53
+ const outputDir = path.resolve(options.outputDir ?? DEFAULT_OUTPUT);
54
+ fs.mkdirSync(outputDir, { recursive: true });
55
+
56
+ const archive = await unpack(resolvedPath);
57
+
58
+ /** @type {Record<string, object>} */
59
+ const parsed = {};
60
+ for (const [entryPath, content] of archive.files) {
61
+ if (content === '__binary__') continue;
62
+ if (entryPath.endsWith('.xml') || entryPath.endsWith('.rels')) {
63
+ parsed[entryPath] = await parseXml(content);
64
+ }
65
+ }
66
+
67
+ const relIndex = buildRelationIndex(parsed);
68
+ const ctx = { relIndex, parsed, sourcePath: resolvedPath };
69
+ const entities = extractEntities(ctx);
70
+ const ir = mapToIR(entities, ctx);
71
+ const script = generateScript(ir, options);
72
+
73
+ const scriptPath = path.join(outputDir, 'output.js');
74
+ fs.writeFileSync(scriptPath, script, 'utf8');
75
+
76
+ await packageMedia({ archive, ir }, outputDir, {
77
+ noMedia: options.noMedia,
78
+ });
79
+
80
+ const log = buildConversionLog(resolvedPath, stat, entities, ir);
81
+ writeConversionLog(outputDir, log);
82
+ writeOutputReadme(outputDir, {
83
+ sourcePath: resolvedPath,
84
+ generatedAt: new Date().toISOString(),
85
+ });
86
+
87
+ if (options.strictDegrade && log.statistics.degraded > 0) {
88
+ const err = new Error('存在退化项(--strict-degrade)');
89
+ err.code = 'STRICT_DEGRADE';
90
+ throw err;
91
+ }
92
+ if (options.strictSkip) {
93
+ const errors = (log.omitted || []).filter((o) => o.severity === 'error');
94
+ if (errors.length > 0) {
95
+ const err = new Error('存在 error 级别跳过项(--strict-skip)');
96
+ err.code = 'STRICT_SKIP';
97
+ throw err;
98
+ }
99
+ }
100
+
101
+ return {
102
+ outputDir,
103
+ scriptPath,
104
+ logPath: path.join(outputDir, 'conversion.log'),
105
+ log,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * @param {string} sourcePath
111
+ * @param {fs.Stats} stat
112
+ * @param {import('./extractor').SlideEntity[][]} entitiesBySlide
113
+ * @param {import('./mapper').IntermediateRepresentation} ir
114
+ */
115
+ function buildConversionLog(sourcePath, stat, entitiesBySlide, ir) {
116
+ let full = 0;
117
+ let degraded = 0;
118
+ let skipped = 0;
119
+
120
+ /** @type {object[]} */
121
+ const slides = [];
122
+ /** @type {object[]} */
123
+ const degradedList = [];
124
+ /** @type {object[]} */
125
+ const omitted = [];
126
+ /** @type {object[]} */
127
+ const warnings = [];
128
+
129
+ entitiesBySlide.forEach((entities, slideIndex) => {
130
+ /** @type {object[]} */
131
+ const slideItems = [];
132
+
133
+ for (const entity of entities) {
134
+ const bounds = entity.bounds;
135
+ const base = {
136
+ slideIndex,
137
+ elementBounds: bounds,
138
+ kind: entity.kind,
139
+ decision: entity.decision,
140
+ };
141
+
142
+ if (entity.decision === 'FULL') full++;
143
+ else if (entity.decision === 'DEGRADE') degraded++;
144
+ else skipped++;
145
+
146
+ slideItems.push(base);
147
+
148
+ if (entity.decision === 'DEGRADE' && entity.degradeReason) {
149
+ degradedList.push({
150
+ ...base,
151
+ reason: entity.degradeReason,
152
+ severity: 'warn',
153
+ });
154
+ }
155
+
156
+ if (entity.decision === 'SKIP') {
157
+ omitted.push({
158
+ ...base,
159
+ type: entity.kind,
160
+ reason: entity.skipReason,
161
+ severity: entity.skipReason?.includes('尚未支持') ? 'warn' : 'error',
162
+ });
163
+ }
164
+ }
165
+
166
+ slides.push({ slideIndex, items: slideItems });
167
+ });
168
+
169
+ if (ir.slides.length === 0) {
170
+ warnings.push({
171
+ message: '未解析到任何幻灯片,请检查 presentation.xml 与关系文件',
172
+ severity: 'warn',
173
+ });
174
+ }
175
+
176
+ return {
177
+ source: {
178
+ path: sourcePath,
179
+ size: stat.size,
180
+ slideCount: ir.slides.length,
181
+ },
182
+ statistics: {
183
+ slides: ir.slides.length,
184
+ full,
185
+ degraded,
186
+ skipped,
187
+ },
188
+ slides,
189
+ degraded: degradedList,
190
+ omitted,
191
+ warnings,
192
+ };
193
+ }
194
+
195
+ module.exports = { convert, DEFAULT_OUTPUT, DEFAULT_MAX_FILE_SIZE };
@@ -0,0 +1,394 @@
1
+ /**
2
+ * ③ 实体提取器
3
+ */
4
+ const path = require('path');
5
+ const { asArray, attr, child, textContent } = require('./xml-utils');
6
+ const { boundsFromXfrm } = require('./utils/bounds');
7
+ const {
8
+ loadColorScheme,
9
+ resolveFillColor,
10
+ resolveColorFromContainer,
11
+ } = require('./utils/color');
12
+ const { getSlidePaths, getThemePath } = require('./presentation');
13
+ const {
14
+ buildSlideInheritance,
15
+ getEffectiveXfrm,
16
+ mergeTxBody,
17
+ resolvePlaceholderSps,
18
+ } = require('./placeholder');
19
+ const { isTableFrame, isChartFrame, isSmartArtFrame } = require('./graphic');
20
+ const { extractTable } = require('./table');
21
+ const { extractChart } = require('./chart');
22
+ const { extractSmartArt } = require('./smartart');
23
+
24
+ /** @typedef {'FULL' | 'DEGRADE' | 'SKIP'} ConversionDecision */
25
+
26
+ /**
27
+ * @typedef {object} SlideEntity
28
+ * @property {number} slideIndex
29
+ * @property {string} slidePath
30
+ * @property {ConversionDecision} decision
31
+ * @property {string} kind
32
+ * @property {import('./utils/bounds').Bounds} bounds
33
+ * @property {object} [text]
34
+ * @property {object} [image]
35
+ * @property {object} [shape]
36
+ * @property {object} [table]
37
+ * @property {object} [chart]
38
+ * @property {string} [skipReason]
39
+ * @property {string} [degradeReason]
40
+ */
41
+
42
+ const PRST_TO_SHAPE = {
43
+ rect: 'RECTANGLE',
44
+ roundRect: 'ROUNDED_RECTANGLE',
45
+ ellipse: 'OVAL',
46
+ line: 'LINE',
47
+ triangle: 'TRIANGLE',
48
+ rtTriangle: 'RIGHT_TRIANGLE',
49
+ diamond: 'DIAMOND',
50
+ pentagon: 'PENTAGON',
51
+ hexagon: 'HEXAGON',
52
+ star5: 'STAR5',
53
+ };
54
+
55
+ /**
56
+ * @typedef {object} ExtractContext
57
+ * @property {Record<string, object>} parsed
58
+ * @property {import('./rels').RelationIndex} relIndex
59
+ * @property {string} [sourcePath]
60
+ */
61
+
62
+ /**
63
+ * @param {ExtractContext} ctx
64
+ * @returns {SlideEntity[][]}
65
+ */
66
+ function extractEntities(ctx) {
67
+ const { parsed, relIndex } = ctx;
68
+ const themePath = getThemePath(parsed, relIndex);
69
+ const scheme = loadColorScheme(parsed, themePath);
70
+ const slidePaths = getSlidePaths(parsed, relIndex);
71
+
72
+ return slidePaths.map((slidePath, slideIndex) =>
73
+ extractSlide(slidePath, slideIndex, ctx, scheme)
74
+ );
75
+ }
76
+
77
+ /**
78
+ * @param {string} slidePath
79
+ * @param {number} slideIndex
80
+ * @param {ExtractContext} ctx
81
+ * @param {Record<string, string>} scheme
82
+ */
83
+ function extractSlide(slidePath, slideIndex, ctx, scheme) {
84
+ const doc = ctx.parsed[slidePath];
85
+ const slide = child(doc, 'p:sld');
86
+ const cSld = child(slide, 'p:cSld');
87
+ const spTree = child(cSld, 'p:spTree');
88
+ if (!spTree) return [];
89
+
90
+ const inheritance = buildSlideInheritance(ctx.parsed, ctx.relIndex, slidePath);
91
+
92
+ /** @type {SlideEntity[]} */
93
+ const entities = [];
94
+
95
+ const bg = extractSlideBackground(cSld, scheme);
96
+ if (bg) {
97
+ entities.push({
98
+ slideIndex,
99
+ slidePath,
100
+ decision: bg.degraded ? 'DEGRADE' : 'FULL',
101
+ kind: 'background',
102
+ bounds: { x: 0, y: 0, w: 0, h: 0 },
103
+ shape: { type: 'background', fill: bg.color },
104
+ degradeReason: bg.degraded ? '渐变背景退化为纯色' : undefined,
105
+ });
106
+ }
107
+
108
+ const walkCtx = {
109
+ slideIndex,
110
+ slidePath,
111
+ relIndex: ctx.relIndex,
112
+ parsed: ctx.parsed,
113
+ scheme,
114
+ inheritance,
115
+ offset: { x: 0, y: 0 },
116
+ entities,
117
+ };
118
+
119
+ walkSpTree(spTree, walkCtx);
120
+ return entities;
121
+ }
122
+
123
+ function extractSlideBackground(cSld, scheme) {
124
+ const bgPr = child(child(cSld, 'p:bg'), 'p:bgPr');
125
+ if (!bgPr) return null;
126
+ const { color, degraded } = resolveFillColor(bgPr, scheme);
127
+ if (!color) return null;
128
+ return { color, degraded };
129
+ }
130
+
131
+ function walkSpTree(spTree, ctx) {
132
+ for (const sp of asArray(spTree['p:sp'])) {
133
+ extractShape(sp, ctx);
134
+ }
135
+ for (const pic of asArray(spTree['p:pic'])) {
136
+ extractPicture(pic, ctx);
137
+ }
138
+ for (const grp of asArray(spTree['p:grpSp'])) {
139
+ flattenGroup(grp, ctx);
140
+ }
141
+ for (const frame of asArray(spTree['p:graphicFrame'])) {
142
+ if (isTableFrame(frame)) {
143
+ const entity = extractTable(frame, ctx);
144
+ if (entity) ctx.entities.push(entity);
145
+ } else if (isChartFrame(frame)) {
146
+ const entity = extractChart(frame, ctx);
147
+ if (entity) ctx.entities.push(entity);
148
+ } else if (isSmartArtFrame(frame)) {
149
+ const entity = extractSmartArt(frame, ctx);
150
+ if (entity) ctx.entities.push(entity);
151
+ } else {
152
+ skipElement(frame, ctx, '不支持的 graphicFrame 类型');
153
+ }
154
+ }
155
+ for (const cxn of asArray(spTree['p:cxnSp'])) {
156
+ extractConnector(cxn, ctx);
157
+ }
158
+ }
159
+
160
+ function flattenGroup(grp, ctx) {
161
+ const grpXfrm = child(child(grp, 'p:grpSpPr'), 'a:xfrm');
162
+ const off = readOffset(grpXfrm);
163
+ const inner = child(grp, 'p:spTree');
164
+ if (!inner) return;
165
+ walkSpTree(inner, {
166
+ ...ctx,
167
+ offset: { x: ctx.offset.x + off.x, y: ctx.offset.y + off.y },
168
+ });
169
+ }
170
+
171
+ function extractShape(sp, ctx) {
172
+ const xfrm = getEffectiveXfrm(sp, ctx.inheritance);
173
+ const bounds = boundsFromXfrm(xfrm, ctx.offset);
174
+
175
+ const { layoutSp, masterSp } = resolvePlaceholderSps(sp, ctx.inheritance);
176
+ const txBodyRaw = child(sp, 'p:txBody');
177
+
178
+ if (txBodyRaw) {
179
+ const txBody = mergeTxBody(txBodyRaw, layoutSp, masterSp);
180
+ const runs = extractTextRuns(txBody, ctx.scheme, {
181
+ relIndex: ctx.relIndex,
182
+ slidePath: ctx.slidePath,
183
+ });
184
+ if (runs.length === 0) return;
185
+
186
+ const hasGradient = runs.some((r) => r.degraded);
187
+ ctx.entities.push({
188
+ slideIndex: ctx.slideIndex,
189
+ slidePath: ctx.slidePath,
190
+ decision: hasGradient ? 'DEGRADE' : 'FULL',
191
+ kind: 'text',
192
+ bounds,
193
+ text: { runs: runs.map(({ text, options }) => ({ text, options })) },
194
+ degradeReason: hasGradient ? '文本渐变退化为纯色' : undefined,
195
+ });
196
+ return;
197
+ }
198
+
199
+ const spPr = child(sp, 'p:spPr');
200
+ const prst = attr(child(spPr, 'a:prstGeom'), 'prst');
201
+ if (!prst) return;
202
+
203
+ const shapeName = PRST_TO_SHAPE[prst];
204
+ const { color: fillColor, degraded: fillDegraded } = resolveFillColor(
205
+ spPr,
206
+ ctx.scheme
207
+ );
208
+ const ln = child(spPr, 'a:ln');
209
+ const lineColor = ln ? resolveFillColor(ln, ctx.scheme).color : null;
210
+
211
+ if (!shapeName) {
212
+ ctx.entities.push({
213
+ slideIndex: ctx.slideIndex,
214
+ slidePath: ctx.slidePath,
215
+ decision: 'DEGRADE',
216
+ kind: 'shape',
217
+ bounds,
218
+ shape: { type: 'RECTANGLE', fill: fillColor, line: lineColor },
219
+ degradeReason: `未知预设形状 "${prst}",退化为矩形`,
220
+ });
221
+ return;
222
+ }
223
+
224
+ ctx.entities.push({
225
+ slideIndex: ctx.slideIndex,
226
+ slidePath: ctx.slidePath,
227
+ decision: fillDegraded ? 'DEGRADE' : 'FULL',
228
+ kind: 'shape',
229
+ bounds,
230
+ shape: { type: shapeName, fill: fillColor, line: lineColor },
231
+ degradeReason: fillDegraded ? '渐变填充退化为纯色' : undefined,
232
+ });
233
+ }
234
+
235
+ function extractPicture(pic, ctx) {
236
+ const spPr = child(pic, 'p:spPr');
237
+ const bounds = boundsFromXfrm(child(spPr, 'a:xfrm'), ctx.offset);
238
+
239
+ const blip = child(child(pic, 'p:blipFill'), 'a:blip');
240
+ const embedId = attr(blip, 'r:embed');
241
+ if (!embedId) return;
242
+
243
+ const mediaPath = ctx.relIndex.resolve(ctx.slidePath, embedId);
244
+ if (!mediaPath) return;
245
+
246
+ ctx.entities.push({
247
+ slideIndex: ctx.slideIndex,
248
+ slidePath: ctx.slidePath,
249
+ decision: 'FULL',
250
+ kind: 'image',
251
+ bounds,
252
+ image: {
253
+ zipPath: mediaPath,
254
+ fileName: path.posix.basename(mediaPath),
255
+ },
256
+ });
257
+ }
258
+
259
+ function extractConnector(cxn, ctx) {
260
+ const spPr = child(cxn, 'p:spPr');
261
+ const bounds = boundsFromXfrm(child(spPr, 'a:xfrm'), ctx.offset);
262
+ const { color: lineColor } = resolveFillColor(child(spPr, 'a:ln'), ctx.scheme);
263
+
264
+ ctx.entities.push({
265
+ slideIndex: ctx.slideIndex,
266
+ slidePath: ctx.slidePath,
267
+ decision: 'FULL',
268
+ kind: 'shape',
269
+ bounds,
270
+ shape: { type: 'LINE', fill: null, line: lineColor },
271
+ });
272
+ }
273
+
274
+ function skipElement(_node, ctx, reason) {
275
+ ctx.entities.push({
276
+ slideIndex: ctx.slideIndex,
277
+ slidePath: ctx.slidePath,
278
+ decision: 'SKIP',
279
+ kind: 'skip',
280
+ bounds: { x: 0, y: 0, w: 0, h: 0 },
281
+ skipReason: reason,
282
+ });
283
+ }
284
+
285
+ function readOffset(xfrm) {
286
+ const off = child(xfrm, 'a:off');
287
+ return {
288
+ x: parseInt(attr(off, 'x') ?? '0', 10),
289
+ y: parseInt(attr(off, 'y') ?? '0', 10),
290
+ };
291
+ }
292
+
293
+ const PARA_ALIGN_MAP = {
294
+ l: 'left',
295
+ ctr: 'center',
296
+ r: 'right',
297
+ just: 'justify',
298
+ dist: 'justify',
299
+ };
300
+
301
+ function extractTextRuns(txBody, scheme, linkCtx) {
302
+ const runs = [];
303
+ for (const p of asArray(txBody['a:p'])) {
304
+ const pPr = child(p, 'a:pPr');
305
+ const bullet = extractBullet(pPr);
306
+ const paraOpts = extractParaOptions(pPr);
307
+
308
+ for (const r of asArray(p['a:r'])) {
309
+ const text = textContent(r['a:t']);
310
+ if (!text) continue;
311
+ const options = extractRunOptions(r['a:rPr'], scheme, linkCtx);
312
+ Object.assign(options, paraOpts);
313
+ if (bullet) Object.assign(options, bullet);
314
+ runs.push({ text, options, degraded: options._degraded });
315
+ }
316
+ }
317
+ return runs;
318
+ }
319
+
320
+ /**
321
+ * @param {object|null|undefined} pPr
322
+ */
323
+ function extractParaOptions(pPr) {
324
+ if (!pPr) return {};
325
+ const opts = {};
326
+
327
+ const algn = attr(pPr, 'algn');
328
+ if (algn && PARA_ALIGN_MAP[algn]) opts.align = PARA_ALIGN_MAP[algn];
329
+
330
+ const lvl = attr(pPr, 'lvl');
331
+ if (lvl) opts.indentLevel = parseInt(lvl, 10);
332
+
333
+ // 注:a:spcPct(百分比段前/段后距)暂不处理
334
+ const spcBef = attr(child(child(pPr, 'a:spcBef'), 'a:spcPts'), 'val');
335
+ if (spcBef) opts.paraSpaceBefore = parseInt(spcBef, 10) / 100;
336
+
337
+ // 注:a:spcPct(百分比段后距)暂不处理
338
+ const spcAft = attr(child(child(pPr, 'a:spcAft'), 'a:spcPts'), 'val');
339
+ if (spcAft) opts.paraSpaceAfter = parseInt(spcAft, 10) / 100;
340
+
341
+ // 注:a:spcPct(百分比行距)暂不处理
342
+ const lnSpc = attr(child(child(pPr, 'a:lnSpc'), 'a:spcPts'), 'val');
343
+ if (lnSpc) opts.lineSpacing = parseInt(lnSpc, 10) / 100;
344
+
345
+ return opts;
346
+ }
347
+
348
+ function extractBullet(pPr) {
349
+ if (!pPr) return null;
350
+ if (child(pPr, 'a:buChar')) return { bullet: true };
351
+ if (child(pPr, 'a:buAutoNum')) return { bullet: { type: 'number' } };
352
+ return null;
353
+ }
354
+
355
+ function extractRunOptions(rPr, scheme, linkCtx) {
356
+ const options = {};
357
+ let degraded = false;
358
+ if (!rPr) return options;
359
+
360
+ const sz = attr(rPr, 'sz');
361
+ if (sz) options.fontSize = parseInt(sz, 10) / 100;
362
+ if (attr(rPr, 'b') === '1') options.bold = true;
363
+ if (attr(rPr, 'i') === '1') options.italic = true;
364
+
365
+ const face = attr(child(rPr, 'a:latin'), 'typeface');
366
+ if (face) options.fontFace = face;
367
+
368
+ const solid = child(rPr, 'a:solidFill');
369
+ const grad = child(rPr, 'a:gradFill');
370
+ if (grad) {
371
+ degraded = true;
372
+ const g = resolveFillColor({ 'a:gradFill': grad }, scheme);
373
+ if (g.color) options.color = g.color;
374
+ } else if (solid) {
375
+ const c = resolveColorFromContainer(solid, scheme);
376
+ if (c) options.color = c;
377
+ }
378
+
379
+ const hlink = child(rPr, 'a:hlinkClick');
380
+ if (hlink && linkCtx) {
381
+ const relId = attr(hlink, 'r:id');
382
+ const url = relId && linkCtx.relIndex.resolve(linkCtx.slidePath, relId);
383
+ if (url) options.hyperlink = { url };
384
+ }
385
+
386
+ if (degraded) options._degraded = true;
387
+ return options;
388
+ }
389
+
390
+ module.exports = {
391
+ extractEntities,
392
+ PRST_TO_SHAPE,
393
+ boundsFromXfrm,
394
+ };
package/lib/graphic.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * graphicFrame 辅助:识别表格 / 图表等 graphicData URI
3
+ */
4
+ const { attr, child } = require('./xml-utils');
5
+
6
+ const URI_TABLE =
7
+ 'http://schemas.openxmlformats.org/drawingml/2006/table';
8
+ const URI_CHART =
9
+ 'http://schemas.openxmlformats.org/drawingml/2006/chart';
10
+
11
+ /**
12
+ * @param {object} graphicFrame
13
+ * @returns {string|undefined}
14
+ */
15
+ function getGraphicUri(graphicFrame) {
16
+ const graphicData = child(child(graphicFrame, 'a:graphic'), 'a:graphicData');
17
+ return attr(graphicData, 'uri');
18
+ }
19
+
20
+ /**
21
+ * @param {object} graphicFrame
22
+ * @returns {boolean}
23
+ */
24
+ function isTableFrame(graphicFrame) {
25
+ const uri = getGraphicUri(graphicFrame);
26
+ return Boolean(uri && uri.includes('table'));
27
+ }
28
+
29
+ /**
30
+ * @param {object} graphicFrame
31
+ * @returns {boolean}
32
+ */
33
+ function isChartFrame(graphicFrame) {
34
+ const uri = getGraphicUri(graphicFrame);
35
+ return Boolean(uri && uri.includes('chart'));
36
+ }
37
+
38
+ /**
39
+ * @param {object} graphicFrame
40
+ * @returns {boolean}
41
+ */
42
+ function isSmartArtFrame(graphicFrame) {
43
+ const uri = getGraphicUri(graphicFrame);
44
+ return Boolean(uri && uri.includes('diagram'));
45
+ }
46
+
47
+ /**
48
+ * @param {object} graphicFrame
49
+ * @returns {object|null} p:xfrm 节点
50
+ */
51
+ function getGraphicXfrm(graphicFrame) {
52
+ return child(graphicFrame, 'p:xfrm');
53
+ }
54
+
55
+ module.exports = {
56
+ URI_TABLE,
57
+ URI_CHART,
58
+ getGraphicUri,
59
+ isTableFrame,
60
+ isChartFrame,
61
+ isSmartArtFrame,
62
+ getGraphicXfrm,
63
+ };
package/lib/index.js ADDED
@@ -0,0 +1,7 @@
1
+ const { convert, DEFAULT_OUTPUT, DEFAULT_MAX_FILE_SIZE } = require('./convert');
2
+
3
+ module.exports = {
4
+ convert,
5
+ DEFAULT_OUTPUT,
6
+ DEFAULT_MAX_FILE_SIZE,
7
+ };