pptxtojson 0.0.5 → 0.0.6

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/src/pptxtojson.js CHANGED
@@ -1,1082 +1,1296 @@
1
- import JSZip from 'jszip'
2
- import * as txml from 'txml/dist/txml.mjs'
3
- import tinycolor from 'tinycolor2'
4
-
5
- import { extractFileExtension, base64ArrayBuffer, eachElement, getTextByPathList, angleToDegrees } from './utils'
6
-
7
- const FACTOR = 75 / 914400
8
-
9
- let themeContent = null
10
-
11
- export async function parse(file) {
12
- const slides = []
13
-
14
- const zip = await JSZip.loadAsync(file)
15
-
16
- const filesInfo = await getContentTypes(zip)
17
- const size = await getSlideSize(zip)
18
- themeContent = await loadTheme(zip)
19
-
20
- for (const filename of filesInfo.slides) {
21
- const singleSlide = await processSingleSlide(zip, filename)
22
- slides.push(singleSlide)
23
- }
24
-
25
- return { slides, size }
26
- }
27
-
28
- function simplifyLostLess(children, parentAttributes = {}) {
29
- const out = {}
30
- if (!children.length) return out
31
-
32
- if (children.length === 1 && typeof children[0] === 'string') {
33
- return Object.keys(parentAttributes).length ? {
34
- attrs: parentAttributes,
35
- value: children[0],
36
- } : children[0]
37
- }
38
- for (const child of children) {
39
- if (typeof child !== 'object') return
40
- if (child.tagName === '?xml') continue
41
-
42
- if (!out[child.tagName]) out[child.tagName] = []
43
-
44
- const kids = simplifyLostLess(child.children || [], child.attributes)
45
- out[child.tagName].push(kids)
46
-
47
- if (Object.keys(child.attributes).length) {
48
- kids.attrs = child.attributes
49
- }
50
- }
51
- for (const child in out) {
52
- if (out[child].length === 1) out[child] = out[child][0]
53
- }
54
-
55
- return out
56
- }
57
-
58
- async function readXmlFile(zip, filename) {
59
- const data = await zip.file(filename).async('string')
60
- return simplifyLostLess(txml.parse(data))
61
- }
62
-
63
- async function getContentTypes(zip) {
64
- const ContentTypesJson = await readXmlFile(zip, '[Content_Types].xml')
65
- const subObj = ContentTypesJson['Types']['Override']
66
- const slidesLocArray = []
67
- const slideLayoutsLocArray = []
68
-
69
- for (const item of subObj) {
70
- switch (item['attrs']['ContentType']) {
71
- case 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml':
72
- slidesLocArray.push(item['attrs']['PartName'].substr(1))
73
- break
74
- case 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml':
75
- slideLayoutsLocArray.push(item['attrs']['PartName'].substr(1))
76
- break
77
- default:
78
- }
79
- }
80
- return {
81
- slides: slidesLocArray,
82
- slideLayouts: slideLayoutsLocArray,
83
- }
84
- }
85
-
86
- async function getSlideSize(zip) {
87
- const content = await readXmlFile(zip, 'ppt/presentation.xml')
88
- const sldSzAttrs = content['p:presentation']['p:sldSz']['attrs']
89
- return {
90
- width: parseInt(sldSzAttrs['cx']) * FACTOR,
91
- height: parseInt(sldSzAttrs['cy']) * FACTOR,
92
- }
93
- }
94
-
95
- async function loadTheme(zip) {
96
- const preResContent = await readXmlFile(zip, 'ppt/_rels/presentation.xml.rels')
97
- const relationshipArray = preResContent['Relationships']['Relationship']
98
- let themeURI
99
-
100
- if (relationshipArray.constructor === Array) {
101
- for (const relationshipItem of relationshipArray) {
102
- if (relationshipItem['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
103
- themeURI = relationshipItem['attrs']['Target']
104
- break
105
- }
106
- }
107
- }
108
- else if (relationshipArray['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
109
- themeURI = relationshipArray['attrs']['Target']
110
- }
111
-
112
- if (!themeURI) throw Error(`Can't open theme file.`)
113
-
114
- return await readXmlFile(zip, 'ppt/' + themeURI)
115
- }
116
-
117
- async function processSingleSlide(zip, sldFileName) {
118
- const resName = sldFileName.replace('slides/slide', 'slides/_rels/slide') + '.rels'
119
- const resContent = await readXmlFile(zip, resName)
120
- let relationshipArray = resContent['Relationships']['Relationship']
121
- let layoutFilename = ''
122
- const slideResObj = {}
123
-
124
- if (relationshipArray.constructor === Array) {
125
- for (const relationshipArrayItem of relationshipArray) {
126
- switch (relationshipArrayItem['attrs']['Type']) {
127
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout':
128
- layoutFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
129
- break
130
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide':
131
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image':
132
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart':
133
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink':
134
- default:
135
- slideResObj[relationshipArrayItem['attrs']['Id']] = {
136
- type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
137
- target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
138
- }
139
- }
140
- }
141
- }
142
- else layoutFilename = relationshipArray['attrs']['Target'].replace('../', 'ppt/')
143
-
144
- const slideLayoutContent = await readXmlFile(zip, layoutFilename)
145
- const slideLayoutTables = await indexNodes(slideLayoutContent)
146
-
147
- const slideLayoutResFilename = layoutFilename.replace('slideLayouts/slideLayout', 'slideLayouts/_rels/slideLayout') + '.rels'
148
- const slideLayoutResContent = await readXmlFile(zip, slideLayoutResFilename)
149
- relationshipArray = slideLayoutResContent['Relationships']['Relationship']
150
-
151
- let masterFilename = ''
152
- if (relationshipArray.constructor === Array) {
153
- for (const relationshipArrayItem of relationshipArray) {
154
- switch (relationshipArrayItem['attrs']['Type']) {
155
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster':
156
- masterFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
157
- break
158
- default:
159
- }
160
- }
161
- }
162
- else masterFilename = relationshipArray['attrs']['Target'].replace('../', 'ppt/')
163
-
164
- const slideMasterContent = await readXmlFile(zip, masterFilename)
165
- const slideMasterTextStyles = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:txStyles'])
166
- const slideMasterTables = indexNodes(slideMasterContent)
167
-
168
- const slideContent = await readXmlFile(zip, sldFileName)
169
- const nodes = slideContent['p:sld']['p:cSld']['p:spTree']
170
- const warpObj = {
171
- zip,
172
- slideLayoutTables: slideLayoutTables,
173
- slideMasterTables: slideMasterTables,
174
- slideResObj: slideResObj,
175
- slideMasterTextStyles: slideMasterTextStyles,
176
- }
177
-
178
- const bgColor = '#' + getSlideBackgroundFill(slideContent, slideLayoutContent, slideMasterContent)
179
-
180
- const elements = []
181
- for (const nodeKey in nodes) {
182
- if (nodes[nodeKey].constructor === Array) {
183
- for (const node of nodes[nodeKey]) {
184
- const ret = await processNodesInSlide(nodeKey, node, warpObj)
185
- if (ret) elements.push(ret)
186
- }
187
- }
188
- else {
189
- const ret = await processNodesInSlide(nodeKey, nodes[nodeKey], warpObj)
190
- if (ret) elements.push(ret)
191
- }
192
- }
193
-
194
- return {
195
- fill: bgColor,
196
- elements,
197
- }
198
- }
199
-
200
- function indexNodes(content) {
201
-
202
- const keys = Object.keys(content)
203
- const spTreeNode = content[keys[0]]['p:cSld']['p:spTree']
204
-
205
- const idTable = {}
206
- const idxTable = {}
207
- const typeTable = {}
208
-
209
- for (const key in spTreeNode) {
210
- if (key === 'p:nvGrpSpPr' || key === 'p:grpSpPr') continue
211
-
212
- const targetNode = spTreeNode[key]
213
-
214
- if (targetNode.constructor === Array) {
215
- for (const targetNodeItem of targetNode) {
216
- const nvSpPrNode = targetNodeItem['p:nvSpPr']
217
- const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
218
- const idx = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'idx'])
219
- const type = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'type'])
220
-
221
- if (id) idTable[id] = targetNodeItem
222
- if (idx) idxTable[idx] = targetNodeItem
223
- if (type) typeTable[type] = targetNodeItem
224
- }
225
- }
226
- else {
227
- const nvSpPrNode = targetNode['p:nvSpPr']
228
- const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
229
- const idx = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'idx'])
230
- const type = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'type'])
231
-
232
- if (id) idTable[id] = targetNode
233
- if (idx) idxTable[idx] = targetNode
234
- if (type) typeTable[type] = targetNode
235
- }
236
- }
237
-
238
- return { idTable, idxTable, typeTable }
239
- }
240
-
241
- async function processNodesInSlide(nodeKey, nodeValue, warpObj) {
242
- let json
243
-
244
- switch (nodeKey) {
245
- case 'p:sp': // Shape, Text
246
- json = processSpNode(nodeValue, warpObj)
247
- break
248
- case 'p:cxnSp': // Shape, Text (with connection)
249
- json = processCxnSpNode(nodeValue, warpObj)
250
- break
251
- case 'p:pic': // Picture
252
- json = processPicNode(nodeValue, warpObj)
253
- break
254
- case 'p:graphicFrame': // Chart, Diagram, Table
255
- json = await processGraphicFrameNode(nodeValue, warpObj)
256
- break
257
- case 'p:grpSp':
258
- json = await processGroupSpNode(nodeValue, warpObj)
259
- break
260
- default:
261
- }
262
-
263
- return json
264
- }
265
-
266
- async function processGroupSpNode(node, warpObj) {
267
- const xfrmNode = node['p:grpSpPr']['a:xfrm']
268
- const x = parseInt(xfrmNode['a:off']['attrs']['x']) * FACTOR
269
- const y = parseInt(xfrmNode['a:off']['attrs']['y']) * FACTOR
270
- const chx = parseInt(xfrmNode['a:chOff']['attrs']['x']) * FACTOR
271
- const chy = parseInt(xfrmNode['a:chOff']['attrs']['y']) * FACTOR
272
- const cx = parseInt(xfrmNode['a:ext']['attrs']['cx']) * FACTOR
273
- const cy = parseInt(xfrmNode['a:ext']['attrs']['cy']) * FACTOR
274
- const chcx = parseInt(xfrmNode['a:chExt']['attrs']['cx']) * FACTOR
275
- const chcy = parseInt(xfrmNode['a:chExt']['attrs']['cy']) * FACTOR
276
-
277
- const elements = []
278
- for (const nodeKey in node) {
279
- if (node[nodeKey].constructor === Array) {
280
- for (const item of node[nodeKey]) {
281
- const ret = await processNodesInSlide(nodeKey, item, warpObj)
282
- if (ret) elements.push(ret)
283
- }
284
- }
285
- else {
286
- const ret = await processNodesInSlide(nodeKey, node[nodeKey], warpObj)
287
- if (ret) elements.push(ret)
288
- }
289
- }
290
-
291
- return {
292
- type: 'group',
293
- top: y - chy,
294
- left: x - chx,
295
- width: cx - chcx,
296
- height: cy - chcy,
297
- elements,
298
- }
299
- }
300
-
301
- function processSpNode(node, warpObj) {
302
- const id = node['p:nvSpPr']['p:cNvPr']['attrs']['id']
303
- const name = node['p:nvSpPr']['p:cNvPr']['attrs']['name']
304
- const idx = node['p:nvSpPr']['p:nvPr']['p:ph'] ? node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['idx'] : undefined
305
- let type = node['p:nvSpPr']['p:nvPr']['p:ph'] ? node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['type'] : undefined
306
-
307
- let slideLayoutSpNode, slideMasterSpNode
308
-
309
- if (type) {
310
- if (idx) {
311
- slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
312
- slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
313
- }
314
- else {
315
- slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
316
- slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
317
- }
318
- }
319
- else if (idx) {
320
- slideLayoutSpNode = warpObj['slideLayoutTables']['idxTable'][idx]
321
- slideMasterSpNode = warpObj['slideMasterTables']['idxTable'][idx]
322
- }
323
-
324
- if (!type) {
325
- const txBoxVal = getTextByPathList(node, ['p:nvSpPr', 'p:cNvSpPr', 'attrs', 'txBox'])
326
- if (txBoxVal === '1') type = 'text'
327
- }
328
- if (!type) type = getTextByPathList(slideLayoutSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
329
- if (!type) type = getTextByPathList(slideMasterSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
330
-
331
- return genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, type, warpObj)
332
- }
333
-
334
- function processCxnSpNode(node, warpObj) {
335
- const id = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['id']
336
- const name = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['name']
337
-
338
- return genShape(node, undefined, undefined, id, name, undefined, undefined, warpObj)
339
- }
340
-
341
- function genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, type, warpObj) {
342
- const xfrmList = ['p:spPr', 'a:xfrm']
343
- const slideXfrmNode = getTextByPathList(node, xfrmList)
344
- const slideLayoutXfrmNode = getTextByPathList(slideLayoutSpNode, xfrmList)
345
- const slideMasterXfrmNode = getTextByPathList(slideMasterSpNode, xfrmList)
346
-
347
- const shapType = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst'])
348
-
349
- const { top, left } = getPosition(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
350
- const { width, height } = getSize(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
351
-
352
- let isFlipV = false
353
- let isFlipH = false
354
- if (getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1') {
355
- isFlipV = true
356
- }
357
- if (getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1') {
358
- isFlipH = true
359
- }
360
-
361
- const rotate = angleToDegrees(getTextByPathList(slideXfrmNode, ['attrs', 'rot']))
362
-
363
- const txtXframeNode = getTextByPathList(node, ['p:txXfrm'])
364
- let txtRotate
365
- if (txtXframeNode) {
366
- const txtXframeRot = getTextByPathList(txtXframeNode, ['attrs', 'rot'])
367
- if (txtXframeRot) txtRotate = angleToDegrees(txtXframeRot) + 90
368
- }
369
- else txtRotate = rotate
370
-
371
- let content = ''
372
- if (node['p:txBody']) content = genTextBody(node['p:txBody'], slideLayoutSpNode, slideMasterSpNode, type, warpObj)
373
-
374
- if (shapType) {
375
- const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
376
- const cx = parseInt(ext['cx']) * FACTOR
377
- const cy = parseInt(ext['cy']) * FACTOR
378
-
379
- const { borderColor, borderWidth, borderType } = getBorder(node, true)
380
- const fillColor = getShapeFill(node, true)
381
-
382
- return {
383
- type: type === 'text' ? 'text' : 'shape',
384
- left,
385
- top,
386
- width,
387
- height,
388
- cx,
389
- cy,
390
- borderColor,
391
- borderWidth,
392
- borderType,
393
- fillColor,
394
- content,
395
- isFlipV,
396
- isFlipH,
397
- rotate,
398
- shapType,
399
- id,
400
- name,
401
- idx,
402
- }
403
- }
404
-
405
- const { borderColor, borderWidth, borderType } = getBorder(node, false)
406
- const fillColor = getShapeFill(node, false)
407
-
408
- return {
409
- type: 'text',
410
- left,
411
- top,
412
- width,
413
- height,
414
- borderColor,
415
- borderWidth,
416
- borderType,
417
- fillColor,
418
- isFlipV,
419
- isFlipH,
420
- rotate: txtRotate,
421
- content,
422
- id,
423
- name,
424
- idx,
425
- }
426
- }
427
-
428
- async function processPicNode(node, warpObj) {
429
- const rid = node['p:blipFill']['a:blip']['attrs']['r:embed']
430
- const imgName = warpObj['slideResObj'][rid]['target']
431
- const imgFileExt = extractFileExtension(imgName).toLowerCase()
432
- const zip = warpObj['zip']
433
- const imgArrayBuffer = await zip.file(imgName).async('arraybuffer')
434
- const xfrmNode = node['p:spPr']['a:xfrm']
435
- let mimeType = ''
436
-
437
- switch (imgFileExt) {
438
- case 'jpg':
439
- case 'jpeg':
440
- mimeType = 'image/jpeg'
441
- break
442
- case 'png':
443
- mimeType = 'image/png'
444
- break
445
- case 'gif':
446
- mimeType = 'image/gif'
447
- break
448
- case 'emf':
449
- mimeType = 'image/x-emf'
450
- break
451
- case 'wmf':
452
- mimeType = 'image/x-wmf'
453
- break
454
- default:
455
- mimeType = 'image/*'
456
- }
457
- const { top, left } = getPosition(xfrmNode, undefined, undefined)
458
- const { width, height } = getSize(xfrmNode, undefined, undefined)
459
- const src = `data:${mimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
460
-
461
- let rotate = 0
462
- const rotateNode = getTextByPathList(node, ['p:spPr', 'a:xfrm', 'attrs', 'rot'])
463
- if (rotateNode) rotate = angleToDegrees(rotateNode)
464
-
465
- return {
466
- type: 'image',
467
- top,
468
- left,
469
- width,
470
- height,
471
- src,
472
- rotate,
473
- }
474
- }
475
-
476
- async function processGraphicFrameNode(node, warpObj) {
477
- const graphicTypeUri = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'attrs', 'uri'])
478
-
479
- let result
480
- switch (graphicTypeUri) {
481
- case 'http://schemas.openxmlformats.org/drawingml/2006/table':
482
- result = genTable(node, warpObj)
483
- break
484
- case 'http://schemas.openxmlformats.org/drawingml/2006/chart':
485
- result = await genChart(node, warpObj)
486
- break
487
- case 'http://schemas.openxmlformats.org/drawingml/2006/diagram':
488
- result = genDiagram(node, warpObj)
489
- break
490
- default:
491
- }
492
- return result
493
- }
494
-
495
- function genTextBody(textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, warpObj) {
496
- if (!textBodyNode) return ''
497
-
498
- let text = ''
499
- const slideMasterTextStyles = warpObj['slideMasterTextStyles']
500
-
501
- const pNode = textBodyNode['a:p']
502
- const pNodes = pNode.constructor === Array ? pNode : [pNode]
503
-
504
- let isList = ''
505
-
506
- for (const pNode of pNodes) {
507
- const rNode = pNode['a:r']
508
- const align = getHorizontalAlign(pNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles)
509
-
510
- const listType = getListType(pNode)
511
- if (listType) {
512
- if (!isList) {
513
- text += `<${listType}>`
514
- isList = listType
515
- }
516
- else if (isList && isList !== listType) {
517
- text += `</${isList}>`
518
- text += `<${listType}>`
519
- isList = listType
520
- }
521
- text += `<li style="text-align: ${align};">`
522
- }
523
- else {
524
- if (isList) {
525
- text += `</${isList}>`
526
- isList = ''
527
- }
528
- text += `<p style="text-align: ${align};">`
529
- }
530
-
531
- if (!rNode) text += genSpanElement(pNode, slideLayoutSpNode, type, warpObj)
532
- else if (rNode.constructor === Array) {
533
- for (const rNodeItem of rNode) text += genSpanElement(rNodeItem, slideLayoutSpNode, type, warpObj)
534
- }
535
- else text += genSpanElement(rNode, slideLayoutSpNode, type, warpObj)
536
-
537
- if (listType) text += '</li>'
538
- else text += '</p>'
539
- }
540
- return text
541
- }
542
-
543
- function getListType(node) {
544
- const pPrNode = node['a:pPr']
545
- if (!pPrNode) return ''
546
-
547
- if (pPrNode['a:buChar']) return 'ul'
548
- if (pPrNode['a:buAutoNum']) return 'ol'
549
-
550
- return ''
551
- }
552
-
553
- function genSpanElement(node, slideLayoutSpNode, type, warpObj) {
554
- const slideMasterTextStyles = warpObj['slideMasterTextStyles']
555
-
556
- let text = node['a:t']
557
- if (typeof text !== 'string') text = getTextByPathList(node, ['a:fld', 'a:t'])
558
- if (typeof text !== 'string') text = '&nbsp;'
559
-
560
- let styleText = ''
561
- const fontColor = getFontColor(node)
562
- const fontSize = getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles)
563
- const fontType = getFontType(node, type)
564
- const fontBold = getFontBold(node)
565
- const fontItalic = getFontItalic(node)
566
- const fontDecoration = getFontDecoration(node)
567
- if (fontColor) styleText += `color: ${fontColor};`
568
- if (fontSize) styleText += `font-size: ${fontSize};`
569
- if (fontType) styleText += `font-family: ${fontType};`
570
- if (fontBold) styleText += `font-weight: ${fontBold};`
571
- if (fontItalic) styleText += `font-style: ${fontItalic};`
572
- if (fontDecoration) styleText += `text-decoration: ${fontDecoration};`
573
-
574
- const linkID = getTextByPathList(node, ['a:rPr', 'a:hlinkClick', 'attrs', 'r:id'])
575
- if (linkID) {
576
- const linkURL = warpObj['slideResObj'][linkID]['target']
577
- return `<span style="${styleText}"><a href="${linkURL}" target="_blank">${text.replace(/\s/i, '&nbsp;')}</a></span>`
578
- }
579
- return `<span style="${styleText}">${text.replace(/\s/i, '&nbsp;')}</span>`
580
- }
581
-
582
- function genTable(node, warpObj) {
583
- const tableNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl'])
584
- const xfrmNode = getTextByPathList(node, ['p:xfrm'])
585
- const { top, left } = getPosition(xfrmNode, undefined, undefined)
586
- const { width, height } = getSize(xfrmNode, undefined, undefined)
587
-
588
- const trNodes = tableNode['a:tr']
589
-
590
- const data = []
591
- if (trNodes.constructor === Array) {
592
- for (const trNode of trNodes) {
593
- const tcNodes = trNode['a:tc']
594
- const tr = []
595
-
596
- if (tcNodes.constructor === Array) {
597
- for (const tcNode of tcNodes) {
598
- const text = genTextBody(tcNode['a:txBody'], undefined, undefined, undefined, warpObj)
599
- const rowSpan = getTextByPathList(tcNode, ['attrs', 'rowSpan'])
600
- const colSpan = getTextByPathList(tcNode, ['attrs', 'gridSpan'])
601
- const vMerge = getTextByPathList(tcNode, ['attrs', 'vMerge'])
602
- const hMerge = getTextByPathList(tcNode, ['attrs', 'hMerge'])
603
-
604
- tr.push({ text, rowSpan, colSpan, vMerge, hMerge })
605
- }
606
- }
607
- else {
608
- const text = genTextBody(tcNodes['a:txBody'])
609
- tr.push({ text })
610
- }
611
-
612
- data.push(tr)
613
- }
614
- }
615
- else {
616
- const tcNodes = trNodes['a:tc']
617
- const tr = []
618
-
619
- if (tcNodes.constructor === Array) {
620
- for (const tcNode of tcNodes) {
621
- const text = genTextBody(tcNode['a:txBody'])
622
- tr.push({ text })
623
- }
624
- }
625
- else {
626
- const text = genTextBody(tcNodes['a:txBody'])
627
- tr.push({ text })
628
- }
629
- data.push(tr)
630
- }
631
-
632
- return {
633
- type: 'table',
634
- top,
635
- left,
636
- width,
637
- height,
638
- data,
639
- }
640
- }
641
-
642
- async function genChart(node, warpObj) {
643
- const xfrmNode = getTextByPathList(node, ['p:xfrm'])
644
- const { top, left } = getPosition(xfrmNode, undefined, undefined)
645
- const { width, height } = getSize(xfrmNode, undefined, undefined)
646
-
647
- const rid = node['a:graphic']['a:graphicData']['c:chart']['attrs']['r:id']
648
- const refName = warpObj['slideResObj'][rid]['target']
649
- const content = await readXmlFile(warpObj['zip'], refName)
650
- const plotArea = getTextByPathList(content, ['c:chartSpace', 'c:chart', 'c:plotArea'])
651
-
652
- let chart = null
653
- for (const key in plotArea) {
654
- switch (key) {
655
- case 'c:lineChart':
656
- chart = {
657
- type: 'lineChart',
658
- data: extractChartData(plotArea[key]['c:ser']),
659
- }
660
- break
661
- case 'c:barChart':
662
- chart = {
663
- type: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']) === 'stacked' ? 'stackedBarChart' : 'barChart',
664
- data: extractChartData(plotArea[key]['c:ser']),
665
- }
666
- break
667
- case 'c:pieChart':
668
- chart = {
669
- type: 'pieChart',
670
- data: extractChartData(plotArea[key]['c:ser']),
671
- }
672
- break
673
- case 'c:pie3DChart':
674
- chart = {
675
- type: 'pie3DChart',
676
- data: extractChartData(plotArea[key]['c:ser']),
677
- }
678
- break
679
- case 'c:areaChart':
680
- chart = {
681
- type: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']) === 'percentStacked' ? 'stackedAreaChart' : 'areaChart',
682
- data: extractChartData(plotArea[key]['c:ser']),
683
- }
684
- break
685
- case 'c:scatterChart':
686
- chart = {
687
- type: 'scatterChart',
688
- data: extractChartData(plotArea[key]['c:ser']),
689
- }
690
- break
691
- case 'c:catAx':
692
- break
693
- case 'c:valAx':
694
- break
695
- default:
696
- }
697
- }
698
-
699
- if (!chart) return {}
700
- return {
701
- type: 'chart',
702
- top,
703
- left,
704
- width,
705
- height,
706
- data: chart.data,
707
- chartType: chart.type,
708
- }
709
- }
710
-
711
- function genDiagram(node) {
712
- const xfrmNode = getTextByPathList(node, ['p:xfrm'])
713
- const { left, top } = getPosition(xfrmNode, undefined, undefined)
714
- const { width, height } = getSize(xfrmNode, undefined, undefined)
715
-
716
- return {
717
- type: 'diagram',
718
- left,
719
- top,
720
- width,
721
- height,
722
- }
723
- }
724
-
725
- function getPosition(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
726
- let off
727
-
728
- if (slideSpNode) off = slideSpNode['a:off']['attrs']
729
- else if (slideLayoutSpNode) off = slideLayoutSpNode['a:off']['attrs']
730
- else if (slideMasterSpNode) off = slideMasterSpNode['a:off']['attrs']
731
-
732
- if (!off) return { top: 0, left: 0 }
733
-
734
- return {
735
- top: parseInt(off['y']) * FACTOR,
736
- left: parseInt(off['x']) * FACTOR,
737
- }
738
- }
739
-
740
- function getSize(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
741
- let ext
742
-
743
- if (slideSpNode) ext = slideSpNode['a:ext']['attrs']
744
- else if (slideLayoutSpNode) ext = slideLayoutSpNode['a:ext']['attrs']
745
- else if (slideMasterSpNode) ext = slideMasterSpNode['a:ext']['attrs']
746
-
747
- if (!ext) return { width: 0, height: 0 }
748
-
749
- return {
750
- width: parseInt(ext['cx']) * FACTOR,
751
- height: parseInt(ext['cy']) * FACTOR,
752
- }
753
- }
754
-
755
- function getHorizontalAlign(node, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles) {
756
- let algn = getTextByPathList(node, ['a:pPr', 'attrs', 'algn'])
757
-
758
- if (!algn) algn = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn'])
759
- if (!algn) algn = getTextByPathList(slideMasterSpNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn'])
760
- if (!algn) {
761
- switch (type) {
762
- case 'title':
763
- case 'subTitle':
764
- case 'ctrTitle':
765
- algn = getTextByPathList(slideMasterTextStyles, ['p:titleStyle', 'a:lvl1pPr', 'attrs', 'alng'])
766
- break
767
- default:
768
- algn = getTextByPathList(slideMasterTextStyles, ['p:otherStyle', 'a:lvl1pPr', 'attrs', 'alng'])
769
- }
770
- }
771
- if (!algn) {
772
- if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') return 'center'
773
- else if (type === 'sldNum') return 'right'
774
- }
775
- return algn === 'ctr' ? 'center' : algn === 'r' ? 'right' : 'left'
776
- }
777
-
778
- function getFontType(node, type) {
779
- let typeface = getTextByPathList(node, ['a:rPr', 'a:latin', 'attrs', 'typeface'])
780
-
781
- if (!typeface) {
782
- const fontSchemeNode = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:fontScheme'])
783
-
784
- if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
785
- typeface = getTextByPathList(fontSchemeNode, ['a:majorFont', 'a:latin', 'attrs', 'typeface'])
786
- }
787
- else if (type === 'body') {
788
- typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
789
- }
790
- else {
791
- typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
792
- }
793
- }
794
-
795
- return typeface || ''
796
- }
797
-
798
- function getFontColor(node) {
799
- const color = getTextByPathList(node, ['a:rPr', 'a:solidFill', 'a:srgbClr', 'attrs', 'val'])
800
- return color ? `#${color}` : ''
801
- }
802
-
803
- function getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles) {
804
- let fontSize
805
-
806
- if (node['a:rPr']) fontSize = parseInt(node['a:rPr']['attrs']['sz']) / 100
807
-
808
- if ((isNaN(fontSize) || !fontSize)) {
809
- const sz = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:lstStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
810
- fontSize = parseInt(sz) / 100
811
- }
812
-
813
- if (isNaN(fontSize) || !fontSize) {
814
- let sz
815
- if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
816
- sz = getTextByPathList(slideMasterTextStyles, ['p:titleStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
817
- }
818
- else if (type === 'body') {
819
- sz = getTextByPathList(slideMasterTextStyles, ['p:bodyStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
820
- }
821
- else if (type === 'dt' || type === 'sldNum') {
822
- sz = '1200'
823
- }
824
- else if (!type) {
825
- sz = getTextByPathList(slideMasterTextStyles, ['p:otherStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
826
- }
827
- if (sz) fontSize = parseInt(sz) / 100
828
- }
829
-
830
- const baseline = getTextByPathList(node, ['a:rPr', 'attrs', 'baseline'])
831
- if (baseline && !isNaN(fontSize)) fontSize -= 10
832
-
833
- return (isNaN(fontSize) || !fontSize) ? '24px' : (fontSize / 0.75 + 'px')
834
- }
835
-
836
- function getFontBold(node) {
837
- return (node['a:rPr'] && node['a:rPr']['attrs']['b'] === '1') ? 'bold' : ''
838
- }
839
-
840
- function getFontItalic(node) {
841
- return (node['a:rPr'] && node['a:rPr']['attrs']['i'] === '1') ? 'italic' : ''
842
- }
843
-
844
- function getFontDecoration(node) {
845
- return (node['a:rPr'] && node['a:rPr']['attrs']['u'] === 'sng') ? 'underline' : ''
846
- }
847
-
848
- function getBorder(node, isSvgMode) {
849
- const lineNode = node['p:spPr']['a:ln']
850
-
851
- let borderWidth = parseInt(getTextByPathList(lineNode, ['attrs', 'w'])) / 12700
852
- if (isNaN(borderWidth)) borderWidth = 0
853
-
854
- let borderColor = getTextByPathList(lineNode, ['a:solidFill', 'a:srgbClr', 'attrs', 'val'])
855
- if (!borderColor) {
856
- const schemeClrNode = getTextByPathList(lineNode, ['a:solidFill', 'a:schemeClr'])
857
- const schemeClr = 'a:' + getTextByPathList(schemeClrNode, ['attrs', 'val'])
858
- borderColor = getSchemeColorFromTheme(schemeClr)
859
- }
860
-
861
- if (!borderColor) {
862
- const schemeClrNode = getTextByPathList(node, ['p:style', 'a:lnRef', 'a:schemeClr'])
863
- const schemeClr = 'a:' + getTextByPathList(schemeClrNode, ['attrs', 'val'])
864
- borderColor = getSchemeColorFromTheme(schemeClr)
865
-
866
- if (borderColor) {
867
- let shade = getTextByPathList(schemeClrNode, ['a:shade', 'attrs', 'val'])
868
-
869
- if (shade) {
870
- shade = parseInt(shade) / 100000
871
-
872
- const color = tinycolor('#' + borderColor).toHsl()
873
- borderColor = tinycolor({ h: color.h, s: color.s, l: color.l * shade, a: color.a }).toHex()
874
- }
875
- }
876
- }
877
-
878
- if (!borderColor) {
879
- if (isSvgMode) borderColor = 'none'
880
- else borderColor = '#000'
881
- }
882
- else {
883
- borderColor = `#${borderColor}`
884
- }
885
-
886
- const type = getTextByPathList(lineNode, ['a:prstDash', 'attrs', 'val'])
887
- let borderType = 'solid'
888
- let strokeDasharray = '0'
889
- switch (type) {
890
- case 'solid':
891
- borderType = 'solid'
892
- strokeDasharray = '0'
893
- break
894
- case 'dash':
895
- borderType = 'dashed'
896
- strokeDasharray = '5'
897
- break
898
- case 'dashDot':
899
- borderType = 'dashed'
900
- strokeDasharray = '5, 5, 1, 5'
901
- break
902
- case 'dot':
903
- borderType = 'dotted'
904
- strokeDasharray = '1, 5'
905
- break
906
- case 'lgDash':
907
- borderType = 'dashed'
908
- strokeDasharray = '10, 5'
909
- break
910
- case 'lgDashDotDot':
911
- borderType = 'dashed'
912
- strokeDasharray = '10, 5, 1, 5, 1, 5'
913
- break
914
- case 'sysDash':
915
- borderType = 'dashed'
916
- strokeDasharray = '5, 2'
917
- break
918
- case 'sysDashDot':
919
- borderType = 'dashed'
920
- strokeDasharray = '5, 2, 1, 5'
921
- break
922
- case 'sysDashDotDot':
923
- borderType = 'dashed'
924
- strokeDasharray = '5, 2, 1, 5, 1, 5'
925
- break
926
- case 'sysDot':
927
- borderType = 'dotted'
928
- strokeDasharray = '2, 5'
929
- break
930
- default:
931
- }
932
-
933
- return {
934
- borderColor,
935
- borderWidth,
936
- borderType,
937
- strokeDasharray,
938
- }
939
- }
940
-
941
- function getSlideBackgroundFill(slideContent, slideLayoutContent, slideMasterContent) {
942
- let bgColor = getSolidFill(getTextByPathList(slideContent, ['p:sld', 'p:cSld', 'p:bg', 'p:bgPr', 'a:solidFill']))
943
- if (!bgColor) bgColor = getSolidFill(getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:cSld', 'p:bg', 'p:bgPr', 'a:solidFill']))
944
- if (!bgColor) bgColor = getSolidFill(getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:cSld', 'p:bg', 'p:bgPr', 'a:solidFill']))
945
- if (!bgColor) bgColor = 'fff'
946
- return bgColor
947
- }
948
-
949
- function getShapeFill(node, isSvgMode) {
950
- if (getTextByPathList(node, ['p:spPr', 'a:noFill'])) {
951
- return isSvgMode ? 'none' : 'background-color: initial;'
952
- }
953
-
954
- let fillColor
955
- if (!fillColor) {
956
- fillColor = getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:srgbClr', 'attrs', 'val'])
957
- }
958
-
959
- if (!fillColor) {
960
- const schemeClr = 'a:' + getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'attrs', 'val'])
961
- fillColor = getSchemeColorFromTheme(schemeClr)
962
- }
963
-
964
- if (!fillColor) {
965
- const schemeClr = 'a:' + getTextByPathList(node, ['p:style', 'a:fillRef', 'a:schemeClr', 'attrs', 'val'])
966
- fillColor = getSchemeColorFromTheme(schemeClr)
967
- }
968
-
969
- if (fillColor) {
970
- fillColor = `#${fillColor}`
971
-
972
- let lumMod = parseInt(getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'a:lumMod', 'attrs', 'val'])) / 100000
973
- let lumOff = parseInt(getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'a:lumOff', 'attrs', 'val'])) / 100000
974
- if (isNaN(lumMod)) lumMod = 1.0
975
- if (isNaN(lumOff)) lumOff = 0
976
-
977
- const color = tinycolor(fillColor).toHsl()
978
- const lum = color.l * (1 + lumOff)
979
- return tinycolor({ h: color.h, s: color.s, l: lum, a: color.a }).toHexString()
980
- }
981
-
982
- if (isSvgMode) return 'none'
983
- return fillColor
984
- }
985
-
986
- function getSolidFill(solidFill) {
987
- if (!solidFill) return solidFill
988
-
989
- let color = 'fff'
990
-
991
- if (solidFill['a:srgbClr']) {
992
- color = getTextByPathList(solidFill['a:srgbClr'], ['attrs', 'val'])
993
- }
994
- else if (solidFill['a:schemeClr']) {
995
- const schemeClr = 'a:' + getTextByPathList(solidFill['a:schemeClr'], ['attrs', 'val'])
996
- color = getSchemeColorFromTheme(schemeClr)
997
- }
998
-
999
- return color
1000
- }
1001
-
1002
- function getSchemeColorFromTheme(schemeClr) {
1003
- switch (schemeClr) {
1004
- case 'a:tx1':
1005
- schemeClr = 'a:dk1'
1006
- break
1007
- case 'a:tx2':
1008
- schemeClr = 'a:dk2'
1009
- break
1010
- case 'a:bg1':
1011
- schemeClr = 'a:lt1'
1012
- break
1013
- case 'a:bg2':
1014
- schemeClr = 'a:lt2'
1015
- break
1016
- default:
1017
- break
1018
- }
1019
- const refNode = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:clrScheme', schemeClr])
1020
- let color = getTextByPathList(refNode, ['a:srgbClr', 'attrs', 'val'])
1021
- if (!color) color = getTextByPathList(refNode, ['a:sysClr', 'attrs', 'lastClr'])
1022
- return color
1023
- }
1024
-
1025
- function extractChartData(serNode) {
1026
- const dataMat = []
1027
- if (!serNode) return dataMat
1028
-
1029
- if (serNode['c:xVal']) {
1030
- let dataRow = []
1031
- eachElement(serNode['c:xVal']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1032
- dataRow.push(parseFloat(innerNode['c:v']))
1033
- return ''
1034
- })
1035
- dataMat.push(dataRow)
1036
- dataRow = []
1037
- eachElement(serNode['c:yVal']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1038
- dataRow.push(parseFloat(innerNode['c:v']))
1039
- return ''
1040
- })
1041
- dataMat.push(dataRow)
1042
- }
1043
- else {
1044
- eachElement(serNode, (innerNode, index) => {
1045
- const dataRow = []
1046
- const colName = getTextByPathList(innerNode, ['c:tx', 'c:strRef', 'c:strCache', 'c:pt', 'c:v']) || index
1047
-
1048
- const rowNames = {}
1049
- if (getTextByPathList(innerNode, ['c:cat', 'c:strRef', 'c:strCache', 'c:pt'])) {
1050
- eachElement(innerNode['c:cat']['c:strRef']['c:strCache']['c:pt'], innerNode => {
1051
- rowNames[innerNode['attrs']['idx']] = innerNode['c:v']
1052
- return ''
1053
- })
1054
- }
1055
- else if (getTextByPathList(innerNode, ['c:cat', 'c:numRef', 'c:numCache', 'c:pt'])) {
1056
- eachElement(innerNode['c:cat']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1057
- rowNames[innerNode['attrs']['idx']] = innerNode['c:v']
1058
- return ''
1059
- })
1060
- }
1061
-
1062
- if (getTextByPathList(innerNode, ['c:val', 'c:numRef', 'c:numCache', 'c:pt'])) {
1063
- eachElement(innerNode['c:val']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1064
- dataRow.push({
1065
- x: innerNode['attrs']['idx'],
1066
- y: parseFloat(innerNode['c:v']),
1067
- })
1068
- return ''
1069
- })
1070
- }
1071
-
1072
- dataMat.push({
1073
- key: colName,
1074
- values: dataRow,
1075
- xlabels: rowNames,
1076
- })
1077
- return ''
1078
- })
1079
- }
1080
-
1081
- return dataMat
1
+ import JSZip from 'jszip'
2
+ import * as txml from 'txml/dist/txml.mjs'
3
+ import tinycolor from 'tinycolor2'
4
+
5
+ import {
6
+ extractFileExtension,
7
+ base64ArrayBuffer,
8
+ eachElement,
9
+ getTextByPathList,
10
+ angleToDegrees,
11
+ escapeHtml,
12
+ getMimeType,
13
+ } from './utils'
14
+
15
+ const FACTOR = 75 / 914400
16
+
17
+ let themeContent = null
18
+ let defaultTextStyle = null
19
+
20
+ export async function parse(file) {
21
+ const slides = []
22
+
23
+ const zip = await JSZip.loadAsync(file)
24
+
25
+ const filesInfo = await getContentTypes(zip)
26
+ const { width, height, defaultTextStyle: _defaultTextStyle } = await getSlideSize(zip)
27
+ themeContent = await loadTheme(zip)
28
+ defaultTextStyle = _defaultTextStyle
29
+
30
+ for (const filename of filesInfo.slides) {
31
+ const singleSlide = await processSingleSlide(zip, filename)
32
+ slides.push(singleSlide)
33
+ }
34
+
35
+ return {
36
+ slides,
37
+ size: { width, height },
38
+ }
39
+ }
40
+
41
+ function simplifyLostLess(children, parentAttributes = {}) {
42
+ const out = {}
43
+ if (!children.length) return out
44
+
45
+ if (children.length === 1 && typeof children[0] === 'string') {
46
+ return Object.keys(parentAttributes).length ? {
47
+ attrs: parentAttributes,
48
+ value: children[0],
49
+ } : children[0]
50
+ }
51
+ for (const child of children) {
52
+ if (typeof child !== 'object') return
53
+ if (child.tagName === '?xml') continue
54
+
55
+ if (!out[child.tagName]) out[child.tagName] = []
56
+
57
+ const kids = simplifyLostLess(child.children || [], child.attributes)
58
+ out[child.tagName].push(kids)
59
+
60
+ if (Object.keys(child.attributes).length) {
61
+ kids.attrs = child.attributes
62
+ }
63
+ }
64
+ for (const child in out) {
65
+ if (out[child].length === 1) out[child] = out[child][0]
66
+ }
67
+
68
+ return out
69
+ }
70
+
71
+ async function readXmlFile(zip, filename) {
72
+ const data = await zip.file(filename).async('string')
73
+ return simplifyLostLess(txml.parse(data))
74
+ }
75
+
76
+ async function getContentTypes(zip) {
77
+ const ContentTypesJson = await readXmlFile(zip, '[Content_Types].xml')
78
+ const subObj = ContentTypesJson['Types']['Override']
79
+ const slidesLocArray = []
80
+ const slideLayoutsLocArray = []
81
+
82
+ for (const item of subObj) {
83
+ switch (item['attrs']['ContentType']) {
84
+ case 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml':
85
+ slidesLocArray.push(item['attrs']['PartName'].substr(1))
86
+ break
87
+ case 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml':
88
+ slideLayoutsLocArray.push(item['attrs']['PartName'].substr(1))
89
+ break
90
+ default:
91
+ }
92
+ }
93
+ return {
94
+ slides: slidesLocArray,
95
+ slideLayouts: slideLayoutsLocArray,
96
+ }
97
+ }
98
+
99
+ async function getSlideSize(zip) {
100
+ const content = await readXmlFile(zip, 'ppt/presentation.xml')
101
+ const sldSzAttrs = content['p:presentation']['p:sldSz']['attrs']
102
+ const defaultTextStyle = content['p:presentation']['p:defaultTextStyle']
103
+ return {
104
+ width: parseInt(sldSzAttrs['cx']) * FACTOR,
105
+ height: parseInt(sldSzAttrs['cy']) * FACTOR,
106
+ defaultTextStyle,
107
+ }
108
+ }
109
+
110
+ async function loadTheme(zip) {
111
+ const preResContent = await readXmlFile(zip, 'ppt/_rels/presentation.xml.rels')
112
+ const relationshipArray = preResContent['Relationships']['Relationship']
113
+ let themeURI
114
+
115
+ if (relationshipArray.constructor === Array) {
116
+ for (const relationshipItem of relationshipArray) {
117
+ if (relationshipItem['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
118
+ themeURI = relationshipItem['attrs']['Target']
119
+ break
120
+ }
121
+ }
122
+ }
123
+ else if (relationshipArray['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
124
+ themeURI = relationshipArray['attrs']['Target']
125
+ }
126
+
127
+ if (!themeURI) throw Error(`Can't open theme file.`)
128
+
129
+ return await readXmlFile(zip, 'ppt/' + themeURI)
130
+ }
131
+
132
+ async function processSingleSlide(zip, sldFileName) {
133
+ const resName = sldFileName.replace('slides/slide', 'slides/_rels/slide') + '.rels'
134
+ const resContent = await readXmlFile(zip, resName)
135
+ let relationshipArray = resContent['Relationships']['Relationship']
136
+ let layoutFilename = ''
137
+ let diagramFilename = ''
138
+ const slideResObj = {}
139
+
140
+ if (relationshipArray.constructor === Array) {
141
+ for (const relationshipArrayItem of relationshipArray) {
142
+ switch (relationshipArrayItem['attrs']['Type']) {
143
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout':
144
+ layoutFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
145
+ break
146
+ case 'http://schemas.microsoft.com/office/2007/relationships/diagramDrawing':
147
+ diagramFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
148
+ slideResObj[relationshipArrayItem['attrs']['Id']] = {
149
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
150
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
151
+ }
152
+ break
153
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide':
154
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image':
155
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart':
156
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink':
157
+ default:
158
+ slideResObj[relationshipArrayItem['attrs']['Id']] = {
159
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
160
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
161
+ }
162
+ }
163
+ }
164
+ }
165
+ else layoutFilename = relationshipArray['attrs']['Target'].replace('../', 'ppt/')
166
+
167
+ const slideLayoutContent = await readXmlFile(zip, layoutFilename)
168
+ const slideLayoutTables = await indexNodes(slideLayoutContent)
169
+
170
+ const slideLayoutResFilename = layoutFilename.replace('slideLayouts/slideLayout', 'slideLayouts/_rels/slideLayout') + '.rels'
171
+ const slideLayoutResContent = await readXmlFile(zip, slideLayoutResFilename)
172
+ relationshipArray = slideLayoutResContent['Relationships']['Relationship']
173
+
174
+ let masterFilename = ''
175
+ const layoutResObj = {}
176
+ if (relationshipArray.constructor === Array) {
177
+ for (const relationshipArrayItem of relationshipArray) {
178
+ switch (relationshipArrayItem['attrs']['Type']) {
179
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster':
180
+ masterFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
181
+ break
182
+ default:
183
+ layoutResObj[relationshipArrayItem['attrs']['Id']] = {
184
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
185
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
186
+ }
187
+ }
188
+ }
189
+ }
190
+ else masterFilename = relationshipArray['attrs']['Target'].replace('../', 'ppt/')
191
+
192
+ const slideMasterContent = await readXmlFile(zip, masterFilename)
193
+ const slideMasterTextStyles = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:txStyles'])
194
+ const slideMasterTables = indexNodes(slideMasterContent)
195
+
196
+ const slideMasterResFilename = masterFilename.replace('slideMasters/slideMaster', 'slideMasters/_rels/slideMaster') + '.rels'
197
+ const slideMasterResContent = await readXmlFile(zip, slideMasterResFilename)
198
+ relationshipArray = slideMasterResContent['Relationships']['Relationship']
199
+ const masterResObj = {}
200
+ if (relationshipArray.constructor === Array) {
201
+ for (const relationshipArrayItem of relationshipArray) {
202
+ switch (relationshipArrayItem['attrs']['Type']) {
203
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
204
+ break
205
+ default:
206
+ masterResObj[relationshipArrayItem['attrs']['Id']] = {
207
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
208
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
209
+ }
210
+ }
211
+ }
212
+ }
213
+
214
+ const diagramResObj = {}
215
+ let digramFileContent = {}
216
+ if (diagramFilename) {
217
+ const diagName = diagramFilename.split('/').pop()
218
+ const diagramResFileName = diagramFilename.replace(diagName, '_rels/' + diagName) + '.rels'
219
+ digramFileContent = await readXmlFile(zip, diagramFilename)
220
+ if (digramFileContent && digramFileContent && digramFileContent !== '') {
221
+ let digramFileContentObjToStr = JSON.stringify(digramFileContent)
222
+ digramFileContentObjToStr = digramFileContentObjToStr.replace(/dsp:/g, 'p:')
223
+ digramFileContent = JSON.parse(digramFileContentObjToStr)
224
+ }
225
+
226
+ const digramResContent = await readXmlFile(zip, diagramResFileName)
227
+ if (digramResContent) {
228
+ relationshipArray = digramResContent['Relationships']['Relationship']
229
+ if (relationshipArray.constructor === Array) {
230
+ for (const relationshipArrayItem of relationshipArray) {
231
+ diagramResObj[relationshipArrayItem['attrs']['Id']] = {
232
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
233
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
234
+ }
235
+ }
236
+ }
237
+ else {
238
+ diagramResObj[relationshipArray['attrs']['Id']] = {
239
+ type: relationshipArray['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
240
+ target: relationshipArray['attrs']['Target'].replace('../', 'ppt/'),
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ const slideContent = await readXmlFile(zip, sldFileName)
247
+ const nodes = slideContent['p:sld']['p:cSld']['p:spTree']
248
+ const warpObj = {
249
+ zip,
250
+ slideLayoutContent: slideLayoutContent,
251
+ slideLayoutTables: slideLayoutTables,
252
+ slideMasterContent: slideMasterContent,
253
+ slideMasterTables: slideMasterTables,
254
+ slideContent: slideContent,
255
+ slideResObj: slideResObj,
256
+ slideMasterTextStyles: slideMasterTextStyles,
257
+ layoutResObj: layoutResObj,
258
+ masterResObj: masterResObj,
259
+ themeContent: themeContent,
260
+ digramFileContent: digramFileContent,
261
+ diagramResObj: diagramResObj,
262
+ defaultTextStyle: defaultTextStyle,
263
+ }
264
+ const bgColor = await getSlideBackgroundFill(warpObj)
265
+
266
+ const elements = []
267
+ for (const nodeKey in nodes) {
268
+ if (nodes[nodeKey].constructor === Array) {
269
+ for (const node of nodes[nodeKey]) {
270
+ const ret = await processNodesInSlide(nodeKey, node, warpObj)
271
+ if (ret) elements.push(ret)
272
+ }
273
+ }
274
+ else {
275
+ const ret = await processNodesInSlide(nodeKey, nodes[nodeKey], warpObj)
276
+ if (ret) elements.push(ret)
277
+ }
278
+ }
279
+
280
+ return {
281
+ fill: bgColor,
282
+ elements,
283
+ }
284
+ }
285
+
286
+ function indexNodes(content) {
287
+
288
+ const keys = Object.keys(content)
289
+ const spTreeNode = content[keys[0]]['p:cSld']['p:spTree']
290
+
291
+ const idTable = {}
292
+ const idxTable = {}
293
+ const typeTable = {}
294
+
295
+ for (const key in spTreeNode) {
296
+ if (key === 'p:nvGrpSpPr' || key === 'p:grpSpPr') continue
297
+
298
+ const targetNode = spTreeNode[key]
299
+
300
+ if (targetNode.constructor === Array) {
301
+ for (const targetNodeItem of targetNode) {
302
+ const nvSpPrNode = targetNodeItem['p:nvSpPr']
303
+ const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
304
+ const idx = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'idx'])
305
+ const type = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'type'])
306
+
307
+ if (id) idTable[id] = targetNodeItem
308
+ if (idx) idxTable[idx] = targetNodeItem
309
+ if (type) typeTable[type] = targetNodeItem
310
+ }
311
+ }
312
+ else {
313
+ const nvSpPrNode = targetNode['p:nvSpPr']
314
+ const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
315
+ const idx = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'idx'])
316
+ const type = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'type'])
317
+
318
+ if (id) idTable[id] = targetNode
319
+ if (idx) idxTable[idx] = targetNode
320
+ if (type) typeTable[type] = targetNode
321
+ }
322
+ }
323
+
324
+ return { idTable, idxTable, typeTable }
325
+ }
326
+
327
+ async function processNodesInSlide(nodeKey, nodeValue, warpObj) {
328
+ let json
329
+
330
+ switch (nodeKey) {
331
+ case 'p:sp': // Shape, Text
332
+ json = processSpNode(nodeValue, warpObj)
333
+ break
334
+ case 'p:cxnSp': // Shape, Text (with connection)
335
+ json = processCxnSpNode(nodeValue, warpObj)
336
+ break
337
+ case 'p:pic': // Picture
338
+ json = processPicNode(nodeValue, warpObj)
339
+ break
340
+ case 'p:graphicFrame': // Chart, Diagram, Table
341
+ json = await processGraphicFrameNode(nodeValue, warpObj)
342
+ break
343
+ case 'p:grpSp':
344
+ json = await processGroupSpNode(nodeValue, warpObj)
345
+ break
346
+ default:
347
+ }
348
+
349
+ return json
350
+ }
351
+
352
+ async function processGroupSpNode(node, warpObj) {
353
+ const xfrmNode = node['p:grpSpPr']['a:xfrm']
354
+ const x = parseInt(xfrmNode['a:off']['attrs']['x']) * FACTOR
355
+ const y = parseInt(xfrmNode['a:off']['attrs']['y']) * FACTOR
356
+ const chx = parseInt(xfrmNode['a:chOff']['attrs']['x']) * FACTOR
357
+ const chy = parseInt(xfrmNode['a:chOff']['attrs']['y']) * FACTOR
358
+ const cx = parseInt(xfrmNode['a:ext']['attrs']['cx']) * FACTOR
359
+ const cy = parseInt(xfrmNode['a:ext']['attrs']['cy']) * FACTOR
360
+ const chcx = parseInt(xfrmNode['a:chExt']['attrs']['cx']) * FACTOR
361
+ const chcy = parseInt(xfrmNode['a:chExt']['attrs']['cy']) * FACTOR
362
+
363
+ const elements = []
364
+ for (const nodeKey in node) {
365
+ if (node[nodeKey].constructor === Array) {
366
+ for (const item of node[nodeKey]) {
367
+ const ret = await processNodesInSlide(nodeKey, item, warpObj)
368
+ if (ret) elements.push(ret)
369
+ }
370
+ }
371
+ else {
372
+ const ret = await processNodesInSlide(nodeKey, node[nodeKey], warpObj)
373
+ if (ret) elements.push(ret)
374
+ }
375
+ }
376
+
377
+ return {
378
+ type: 'group',
379
+ top: y - chy,
380
+ left: x - chx,
381
+ width: cx - chcx,
382
+ height: cy - chcy,
383
+ elements,
384
+ }
385
+ }
386
+
387
+ function processSpNode(node, warpObj) {
388
+ const id = node['p:nvSpPr']['p:cNvPr']['attrs']['id']
389
+ const name = node['p:nvSpPr']['p:cNvPr']['attrs']['name']
390
+ const idx = node['p:nvSpPr']['p:nvPr']['p:ph'] ? node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['idx'] : undefined
391
+ let type = node['p:nvSpPr']['p:nvPr']['p:ph'] ? node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['type'] : undefined
392
+
393
+ let slideLayoutSpNode, slideMasterSpNode
394
+
395
+ if (type) {
396
+ if (idx) {
397
+ slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
398
+ slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
399
+ }
400
+ else {
401
+ slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
402
+ slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
403
+ }
404
+ }
405
+ else if (idx) {
406
+ slideLayoutSpNode = warpObj['slideLayoutTables']['idxTable'][idx]
407
+ slideMasterSpNode = warpObj['slideMasterTables']['idxTable'][idx]
408
+ }
409
+
410
+ if (!type) {
411
+ const txBoxVal = getTextByPathList(node, ['p:nvSpPr', 'p:cNvSpPr', 'attrs', 'txBox'])
412
+ if (txBoxVal === '1') type = 'text'
413
+ }
414
+ if (!type) type = getTextByPathList(slideLayoutSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
415
+ if (!type) type = getTextByPathList(slideMasterSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
416
+
417
+ return genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, type, warpObj)
418
+ }
419
+
420
+ function processCxnSpNode(node, warpObj) {
421
+ const id = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['id']
422
+ const name = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['name']
423
+
424
+ return genShape(node, undefined, undefined, id, name, undefined, undefined, warpObj)
425
+ }
426
+
427
+ function genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, type, warpObj) {
428
+ const xfrmList = ['p:spPr', 'a:xfrm']
429
+ const slideXfrmNode = getTextByPathList(node, xfrmList)
430
+ const slideLayoutXfrmNode = getTextByPathList(slideLayoutSpNode, xfrmList)
431
+ const slideMasterXfrmNode = getTextByPathList(slideMasterSpNode, xfrmList)
432
+
433
+ const shapType = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst'])
434
+
435
+ const { top, left } = getPosition(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
436
+ const { width, height } = getSize(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
437
+
438
+ let isFlipV = false
439
+ let isFlipH = false
440
+ if (getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1') {
441
+ isFlipV = true
442
+ }
443
+ if (getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1') {
444
+ isFlipH = true
445
+ }
446
+
447
+ const rotate = angleToDegrees(getTextByPathList(slideXfrmNode, ['attrs', 'rot']))
448
+
449
+ const txtXframeNode = getTextByPathList(node, ['p:txXfrm'])
450
+ let txtRotate
451
+ if (txtXframeNode) {
452
+ const txtXframeRot = getTextByPathList(txtXframeNode, ['attrs', 'rot'])
453
+ if (txtXframeRot) txtRotate = angleToDegrees(txtXframeRot) + 90
454
+ }
455
+ else txtRotate = rotate
456
+
457
+ let content = ''
458
+ if (node['p:txBody']) content = genTextBody(node['p:txBody'], slideLayoutSpNode, slideMasterSpNode, type, warpObj)
459
+
460
+ if (shapType) {
461
+ const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
462
+ const cx = parseInt(ext['cx']) * FACTOR
463
+ const cy = parseInt(ext['cy']) * FACTOR
464
+
465
+ const { borderColor, borderWidth, borderType } = getBorder(node)
466
+ const fillColor = getShapeFill(node, true)
467
+
468
+ return {
469
+ type: type === 'text' ? 'text' : 'shape',
470
+ left,
471
+ top,
472
+ width,
473
+ height,
474
+ cx,
475
+ cy,
476
+ borderColor,
477
+ borderWidth,
478
+ borderType,
479
+ fillColor,
480
+ content,
481
+ isFlipV,
482
+ isFlipH,
483
+ rotate,
484
+ shapType,
485
+ id,
486
+ name,
487
+ idx,
488
+ }
489
+ }
490
+
491
+ const { borderColor, borderWidth, borderType } = getBorder(node)
492
+ const fillColor = getShapeFill(node, false)
493
+
494
+ return {
495
+ type: 'text',
496
+ left,
497
+ top,
498
+ width,
499
+ height,
500
+ borderColor,
501
+ borderWidth,
502
+ borderType,
503
+ fillColor,
504
+ isFlipV,
505
+ isFlipH,
506
+ rotate: txtRotate,
507
+ content,
508
+ id,
509
+ name,
510
+ idx,
511
+ }
512
+ }
513
+
514
+ async function processPicNode(node, warpObj) {
515
+ const rid = node['p:blipFill']['a:blip']['attrs']['r:embed']
516
+ const imgName = warpObj['slideResObj'][rid]['target']
517
+ const imgFileExt = extractFileExtension(imgName).toLowerCase()
518
+ const zip = warpObj['zip']
519
+ const imgArrayBuffer = await zip.file(imgName).async('arraybuffer')
520
+ const xfrmNode = node['p:spPr']['a:xfrm']
521
+ let mimeType = ''
522
+
523
+ switch (imgFileExt) {
524
+ case 'jpg':
525
+ case 'jpeg':
526
+ mimeType = 'image/jpeg'
527
+ break
528
+ case 'png':
529
+ mimeType = 'image/png'
530
+ break
531
+ case 'gif':
532
+ mimeType = 'image/gif'
533
+ break
534
+ case 'emf':
535
+ mimeType = 'image/x-emf'
536
+ break
537
+ case 'wmf':
538
+ mimeType = 'image/x-wmf'
539
+ break
540
+ default:
541
+ mimeType = 'image/*'
542
+ }
543
+ const { top, left } = getPosition(xfrmNode, undefined, undefined)
544
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
545
+ const src = `data:${mimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
546
+
547
+ let rotate = 0
548
+ const rotateNode = getTextByPathList(node, ['p:spPr', 'a:xfrm', 'attrs', 'rot'])
549
+ if (rotateNode) rotate = angleToDegrees(rotateNode)
550
+
551
+ return {
552
+ type: 'image',
553
+ top,
554
+ left,
555
+ width,
556
+ height,
557
+ src,
558
+ rotate,
559
+ }
560
+ }
561
+
562
+ async function processGraphicFrameNode(node, warpObj) {
563
+ const graphicTypeUri = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'attrs', 'uri'])
564
+
565
+ let result
566
+ switch (graphicTypeUri) {
567
+ case 'http://schemas.openxmlformats.org/drawingml/2006/table':
568
+ result = genTable(node, warpObj)
569
+ break
570
+ case 'http://schemas.openxmlformats.org/drawingml/2006/chart':
571
+ result = await genChart(node, warpObj)
572
+ break
573
+ case 'http://schemas.openxmlformats.org/drawingml/2006/diagram':
574
+ result = genDiagram(node, warpObj)
575
+ break
576
+ default:
577
+ }
578
+ return result
579
+ }
580
+
581
+ function genTextBody(textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, warpObj) {
582
+ if (!textBodyNode) return ''
583
+
584
+ let text = ''
585
+ const slideMasterTextStyles = warpObj['slideMasterTextStyles']
586
+
587
+ const pNode = textBodyNode['a:p']
588
+ const pNodes = pNode.constructor === Array ? pNode : [pNode]
589
+
590
+ let isList = ''
591
+
592
+ for (const pNode of pNodes) {
593
+ const rNode = pNode['a:r']
594
+ const align = getHorizontalAlign(pNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles)
595
+
596
+ const listType = getListType(pNode)
597
+ if (listType) {
598
+ if (!isList) {
599
+ text += `<${listType}>`
600
+ isList = listType
601
+ }
602
+ else if (isList && isList !== listType) {
603
+ text += `</${isList}>`
604
+ text += `<${listType}>`
605
+ isList = listType
606
+ }
607
+ text += `<li style="text-align: ${align};">`
608
+ }
609
+ else {
610
+ if (isList) {
611
+ text += `</${isList}>`
612
+ isList = ''
613
+ }
614
+ text += `<p style="text-align: ${align};">`
615
+ }
616
+
617
+ if (!rNode) text += genSpanElement(pNode, slideLayoutSpNode, type, warpObj)
618
+ else if (rNode.constructor === Array) {
619
+ for (const rNodeItem of rNode) text += genSpanElement(rNodeItem, slideLayoutSpNode, type, warpObj)
620
+ }
621
+ else text += genSpanElement(rNode, slideLayoutSpNode, type, warpObj)
622
+
623
+ if (listType) text += '</li>'
624
+ else text += '</p>'
625
+ }
626
+ return text
627
+ }
628
+
629
+ function getListType(node) {
630
+ const pPrNode = node['a:pPr']
631
+ if (!pPrNode) return ''
632
+
633
+ if (pPrNode['a:buChar']) return 'ul'
634
+ if (pPrNode['a:buAutoNum']) return 'ol'
635
+
636
+ return ''
637
+ }
638
+
639
+ function genSpanElement(node, slideLayoutSpNode, type, warpObj) {
640
+ const slideMasterTextStyles = warpObj['slideMasterTextStyles']
641
+
642
+ let text = node['a:t']
643
+ if (typeof text !== 'string') text = getTextByPathList(node, ['a:fld', 'a:t'])
644
+ if (typeof text !== 'string') text = '&nbsp;'
645
+
646
+ let styleText = ''
647
+ const fontColor = getFontColor(node)
648
+ const fontSize = getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles)
649
+ const fontType = getFontType(node, type)
650
+ const fontBold = getFontBold(node)
651
+ const fontItalic = getFontItalic(node)
652
+ const fontDecoration = getFontDecoration(node)
653
+ if (fontColor) styleText += `color: ${fontColor};`
654
+ if (fontSize) styleText += `font-size: ${fontSize};`
655
+ if (fontType) styleText += `font-family: ${fontType};`
656
+ if (fontBold) styleText += `font-weight: ${fontBold};`
657
+ if (fontItalic) styleText += `font-style: ${fontItalic};`
658
+ if (fontDecoration) styleText += `text-decoration: ${fontDecoration};`
659
+
660
+ const linkID = getTextByPathList(node, ['a:rPr', 'a:hlinkClick', 'attrs', 'r:id'])
661
+ if (linkID) {
662
+ const linkURL = warpObj['slideResObj'][linkID]['target']
663
+ return `<span style="${styleText}"><a href="${linkURL}" target="_blank">${text.replace(/\s/i, '&nbsp;')}</a></span>`
664
+ }
665
+ return `<span style="${styleText}">${text.replace(/\s/i, '&nbsp;')}</span>`
666
+ }
667
+
668
+ function genTable(node, warpObj) {
669
+ const tableNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl'])
670
+ const xfrmNode = getTextByPathList(node, ['p:xfrm'])
671
+ const { top, left } = getPosition(xfrmNode, undefined, undefined)
672
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
673
+
674
+ const trNodes = tableNode['a:tr']
675
+
676
+ const data = []
677
+ if (trNodes.constructor === Array) {
678
+ for (const trNode of trNodes) {
679
+ const tcNodes = trNode['a:tc']
680
+ const tr = []
681
+
682
+ if (tcNodes.constructor === Array) {
683
+ for (const tcNode of tcNodes) {
684
+ const text = genTextBody(tcNode['a:txBody'], undefined, undefined, undefined, warpObj)
685
+ const rowSpan = getTextByPathList(tcNode, ['attrs', 'rowSpan'])
686
+ const colSpan = getTextByPathList(tcNode, ['attrs', 'gridSpan'])
687
+ const vMerge = getTextByPathList(tcNode, ['attrs', 'vMerge'])
688
+ const hMerge = getTextByPathList(tcNode, ['attrs', 'hMerge'])
689
+
690
+ tr.push({ text, rowSpan, colSpan, vMerge, hMerge })
691
+ }
692
+ }
693
+ else {
694
+ const text = genTextBody(tcNodes['a:txBody'])
695
+ tr.push({ text })
696
+ }
697
+
698
+ data.push(tr)
699
+ }
700
+ }
701
+ else {
702
+ const tcNodes = trNodes['a:tc']
703
+ const tr = []
704
+
705
+ if (tcNodes.constructor === Array) {
706
+ for (const tcNode of tcNodes) {
707
+ const text = genTextBody(tcNode['a:txBody'])
708
+ tr.push({ text })
709
+ }
710
+ }
711
+ else {
712
+ const text = genTextBody(tcNodes['a:txBody'])
713
+ tr.push({ text })
714
+ }
715
+ data.push(tr)
716
+ }
717
+
718
+ return {
719
+ type: 'table',
720
+ top,
721
+ left,
722
+ width,
723
+ height,
724
+ data,
725
+ }
726
+ }
727
+
728
+ async function genChart(node, warpObj) {
729
+ const xfrmNode = getTextByPathList(node, ['p:xfrm'])
730
+ const { top, left } = getPosition(xfrmNode, undefined, undefined)
731
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
732
+
733
+ const rid = node['a:graphic']['a:graphicData']['c:chart']['attrs']['r:id']
734
+ const refName = warpObj['slideResObj'][rid]['target']
735
+ const content = await readXmlFile(warpObj['zip'], refName)
736
+ const plotArea = getTextByPathList(content, ['c:chartSpace', 'c:chart', 'c:plotArea'])
737
+
738
+ let chart = null
739
+ for (const key in plotArea) {
740
+ switch (key) {
741
+ case 'c:lineChart':
742
+ chart = {
743
+ type: 'lineChart',
744
+ data: extractChartData(plotArea[key]['c:ser']),
745
+ }
746
+ break
747
+ case 'c:barChart':
748
+ chart = {
749
+ type: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']) === 'stacked' ? 'stackedBarChart' : 'barChart',
750
+ data: extractChartData(plotArea[key]['c:ser']),
751
+ }
752
+ break
753
+ case 'c:pieChart':
754
+ chart = {
755
+ type: 'pieChart',
756
+ data: extractChartData(plotArea[key]['c:ser']),
757
+ }
758
+ break
759
+ case 'c:pie3DChart':
760
+ chart = {
761
+ type: 'pie3DChart',
762
+ data: extractChartData(plotArea[key]['c:ser']),
763
+ }
764
+ break
765
+ case 'c:areaChart':
766
+ chart = {
767
+ type: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']) === 'percentStacked' ? 'stackedAreaChart' : 'areaChart',
768
+ data: extractChartData(plotArea[key]['c:ser']),
769
+ }
770
+ break
771
+ case 'c:scatterChart':
772
+ chart = {
773
+ type: 'scatterChart',
774
+ data: extractChartData(plotArea[key]['c:ser']),
775
+ }
776
+ break
777
+ case 'c:catAx':
778
+ break
779
+ case 'c:valAx':
780
+ break
781
+ default:
782
+ }
783
+ }
784
+
785
+ if (!chart) return {}
786
+ return {
787
+ type: 'chart',
788
+ top,
789
+ left,
790
+ width,
791
+ height,
792
+ data: chart.data,
793
+ chartType: chart.type,
794
+ }
795
+ }
796
+
797
+ function genDiagram(node) {
798
+ const xfrmNode = getTextByPathList(node, ['p:xfrm'])
799
+ const { left, top } = getPosition(xfrmNode, undefined, undefined)
800
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
801
+
802
+ return {
803
+ type: 'diagram',
804
+ left,
805
+ top,
806
+ width,
807
+ height,
808
+ }
809
+ }
810
+
811
+ function getPosition(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
812
+ let off
813
+
814
+ if (slideSpNode) off = slideSpNode['a:off']['attrs']
815
+ else if (slideLayoutSpNode) off = slideLayoutSpNode['a:off']['attrs']
816
+ else if (slideMasterSpNode) off = slideMasterSpNode['a:off']['attrs']
817
+
818
+ if (!off) return { top: 0, left: 0 }
819
+
820
+ return {
821
+ top: parseInt(off['y']) * FACTOR,
822
+ left: parseInt(off['x']) * FACTOR,
823
+ }
824
+ }
825
+
826
+ function getSize(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
827
+ let ext
828
+
829
+ if (slideSpNode) ext = slideSpNode['a:ext']['attrs']
830
+ else if (slideLayoutSpNode) ext = slideLayoutSpNode['a:ext']['attrs']
831
+ else if (slideMasterSpNode) ext = slideMasterSpNode['a:ext']['attrs']
832
+
833
+ if (!ext) return { width: 0, height: 0 }
834
+
835
+ return {
836
+ width: parseInt(ext['cx']) * FACTOR,
837
+ height: parseInt(ext['cy']) * FACTOR,
838
+ }
839
+ }
840
+
841
+ function getHorizontalAlign(node, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles) {
842
+ let algn = getTextByPathList(node, ['a:pPr', 'attrs', 'algn'])
843
+
844
+ if (!algn) algn = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn'])
845
+ if (!algn) algn = getTextByPathList(slideMasterSpNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn'])
846
+ if (!algn) {
847
+ switch (type) {
848
+ case 'title':
849
+ case 'subTitle':
850
+ case 'ctrTitle':
851
+ algn = getTextByPathList(slideMasterTextStyles, ['p:titleStyle', 'a:lvl1pPr', 'attrs', 'alng'])
852
+ break
853
+ default:
854
+ algn = getTextByPathList(slideMasterTextStyles, ['p:otherStyle', 'a:lvl1pPr', 'attrs', 'alng'])
855
+ }
856
+ }
857
+ if (!algn) {
858
+ if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') return 'center'
859
+ else if (type === 'sldNum') return 'right'
860
+ }
861
+ return algn === 'ctr' ? 'center' : algn === 'r' ? 'right' : 'left'
862
+ }
863
+
864
+ function getFontType(node, type) {
865
+ let typeface = getTextByPathList(node, ['a:rPr', 'a:latin', 'attrs', 'typeface'])
866
+
867
+ if (!typeface) {
868
+ const fontSchemeNode = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:fontScheme'])
869
+
870
+ if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
871
+ typeface = getTextByPathList(fontSchemeNode, ['a:majorFont', 'a:latin', 'attrs', 'typeface'])
872
+ }
873
+ else if (type === 'body') {
874
+ typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
875
+ }
876
+ else {
877
+ typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
878
+ }
879
+ }
880
+
881
+ return typeface || ''
882
+ }
883
+
884
+ function getFontColor(node) {
885
+ const color = getTextByPathList(node, ['a:rPr', 'a:solidFill', 'a:srgbClr', 'attrs', 'val'])
886
+ return color ? `#${color}` : ''
887
+ }
888
+
889
+ function getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles) {
890
+ let fontSize
891
+
892
+ if (node['a:rPr']) fontSize = parseInt(node['a:rPr']['attrs']['sz']) / 100
893
+
894
+ if ((isNaN(fontSize) || !fontSize)) {
895
+ const sz = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:lstStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
896
+ fontSize = parseInt(sz) / 100
897
+ }
898
+
899
+ if (isNaN(fontSize) || !fontSize) {
900
+ let sz
901
+ if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
902
+ sz = getTextByPathList(slideMasterTextStyles, ['p:titleStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
903
+ }
904
+ else if (type === 'body') {
905
+ sz = getTextByPathList(slideMasterTextStyles, ['p:bodyStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
906
+ }
907
+ else if (type === 'dt' || type === 'sldNum') {
908
+ sz = '1200'
909
+ }
910
+ else if (!type) {
911
+ sz = getTextByPathList(slideMasterTextStyles, ['p:otherStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
912
+ }
913
+ if (sz) fontSize = parseInt(sz) / 100
914
+ }
915
+
916
+ const baseline = getTextByPathList(node, ['a:rPr', 'attrs', 'baseline'])
917
+ if (baseline && !isNaN(fontSize)) fontSize -= 10
918
+
919
+ return (isNaN(fontSize) || !fontSize) ? '18.75px' : (fontSize / 0.75 * (75 / 96) + 'px')
920
+ }
921
+
922
+ function getFontBold(node) {
923
+ return (node['a:rPr'] && node['a:rPr']['attrs']['b'] === '1') ? 'bold' : ''
924
+ }
925
+
926
+ function getFontItalic(node) {
927
+ return (node['a:rPr'] && node['a:rPr']['attrs']['i'] === '1') ? 'italic' : ''
928
+ }
929
+
930
+ function getFontDecoration(node) {
931
+ return (node['a:rPr'] && node['a:rPr']['attrs']['u'] === 'sng') ? 'underline' : ''
932
+ }
933
+
934
+ function getBorder(node) {
935
+ const lineNode = node['p:spPr']['a:ln']
936
+
937
+ let borderWidth = parseInt(getTextByPathList(lineNode, ['attrs', 'w'])) / 12700
938
+ if (isNaN(borderWidth)) {
939
+ if (lineNode) borderWidth = 0
940
+ else borderWidth = 1
941
+ }
942
+
943
+ let borderColor = getTextByPathList(lineNode, ['a:solidFill', 'a:srgbClr', 'attrs', 'val'])
944
+ if (!borderColor) {
945
+ const schemeClrNode = getTextByPathList(lineNode, ['a:solidFill', 'a:schemeClr'])
946
+ const schemeClr = 'a:' + getTextByPathList(schemeClrNode, ['attrs', 'val'])
947
+ borderColor = getSchemeColorFromTheme(schemeClr)
948
+ }
949
+
950
+ if (!borderColor) {
951
+ const schemeClrNode = getTextByPathList(node, ['p:style', 'a:lnRef', 'a:schemeClr'])
952
+ const schemeClr = 'a:' + getTextByPathList(schemeClrNode, ['attrs', 'val'])
953
+ borderColor = getSchemeColorFromTheme(schemeClr)
954
+
955
+ if (borderColor) {
956
+ let shade = getTextByPathList(schemeClrNode, ['a:shade', 'attrs', 'val'])
957
+
958
+ if (shade) {
959
+ shade = parseInt(shade) / 100000
960
+
961
+ const color = tinycolor('#' + borderColor).toHsl()
962
+ borderColor = tinycolor({ h: color.h, s: color.s, l: color.l * shade, a: color.a }).toHex()
963
+ }
964
+ }
965
+ }
966
+
967
+ if (!borderColor) borderColor = '#000'
968
+ else borderColor = `#${borderColor}`
969
+
970
+ const type = getTextByPathList(lineNode, ['a:prstDash', 'attrs', 'val'])
971
+ let borderType = 'solid'
972
+ let strokeDasharray = '0'
973
+ switch (type) {
974
+ case 'solid':
975
+ borderType = 'solid'
976
+ strokeDasharray = '0'
977
+ break
978
+ case 'dash':
979
+ borderType = 'dashed'
980
+ strokeDasharray = '5'
981
+ break
982
+ case 'dashDot':
983
+ borderType = 'dashed'
984
+ strokeDasharray = '5, 5, 1, 5'
985
+ break
986
+ case 'dot':
987
+ borderType = 'dotted'
988
+ strokeDasharray = '1, 5'
989
+ break
990
+ case 'lgDash':
991
+ borderType = 'dashed'
992
+ strokeDasharray = '10, 5'
993
+ break
994
+ case 'lgDashDotDot':
995
+ borderType = 'dashed'
996
+ strokeDasharray = '10, 5, 1, 5, 1, 5'
997
+ break
998
+ case 'sysDash':
999
+ borderType = 'dashed'
1000
+ strokeDasharray = '5, 2'
1001
+ break
1002
+ case 'sysDashDot':
1003
+ borderType = 'dashed'
1004
+ strokeDasharray = '5, 2, 1, 5'
1005
+ break
1006
+ case 'sysDashDotDot':
1007
+ borderType = 'dashed'
1008
+ strokeDasharray = '5, 2, 1, 5, 1, 5'
1009
+ break
1010
+ case 'sysDot':
1011
+ borderType = 'dotted'
1012
+ strokeDasharray = '2, 5'
1013
+ break
1014
+ default:
1015
+ }
1016
+
1017
+ return {
1018
+ borderColor,
1019
+ borderWidth,
1020
+ borderType,
1021
+ strokeDasharray,
1022
+ }
1023
+ }
1024
+
1025
+ function getFillType(node) {
1026
+ let fillType = ''
1027
+ if (node['a:noFill']) fillType = 'NO_FILL'
1028
+ if (node['a:solidFill']) fillType = 'SOLID_FILL'
1029
+ if (node['a:gradFill']) fillType = 'GRADIENT_FILL'
1030
+ if (node['a:pattFill']) fillType = 'PATTERN_FILL'
1031
+ if (node['a:blipFill']) fillType = 'PIC_FILL'
1032
+ if (node['a:grpFill']) fillType = 'GROUP_FILL'
1033
+
1034
+ return fillType
1035
+ }
1036
+
1037
+ async function getPicFill(type, node, warpObj) {
1038
+ let img
1039
+ const rId = node['a:blip']['attrs']['r:embed']
1040
+ let imgPath
1041
+ if (type === 'slideBg' || type === 'slide') {
1042
+ imgPath = getTextByPathList(warpObj, ['slideResObj', rId, 'target'])
1043
+ }
1044
+ else if (type === 'slideLayoutBg') {
1045
+ imgPath = getTextByPathList(warpObj, ['layoutResObj', rId, 'target'])
1046
+ }
1047
+ else if (type === 'slideMasterBg') {
1048
+ imgPath = getTextByPathList(warpObj, ['masterResObj', rId, 'target'])
1049
+ }
1050
+ else if (type === 'themeBg') {
1051
+ imgPath = getTextByPathList(warpObj, ['themeResObj', rId, 'target'])
1052
+ }
1053
+ else if (type === 'diagramBg') {
1054
+ imgPath = getTextByPathList(warpObj, ['diagramResObj', rId, 'target'])
1055
+ }
1056
+ if (!imgPath) return imgPath
1057
+
1058
+ img = getTextByPathList(warpObj, ['loaded-images', imgPath])
1059
+ if (!img) {
1060
+ imgPath = escapeHtml(imgPath)
1061
+
1062
+ const imgExt = imgPath.split('.').pop()
1063
+ if (imgExt === 'xml') return undefined
1064
+
1065
+ const imgArrayBuffer = await warpObj['zip'].file(imgPath).async('arraybuffer')
1066
+ const imgMimeType = getMimeType(imgExt)
1067
+ img = 'data:' + imgMimeType + 'base64,' + base64ArrayBuffer(imgArrayBuffer)
1068
+ }
1069
+ return img
1070
+ }
1071
+
1072
+ async function getBgPicFill(bgPr, sorce, warpObj) {
1073
+ const picBase64 = await getPicFill(sorce, bgPr['a:blipFill'], warpObj)
1074
+ const aBlipNode = bgPr['a:blipFill']['a:blip']
1075
+
1076
+ const aphaModFixNode = getTextByPathList(aBlipNode, ['a:alphaModFix', 'attrs'])
1077
+ let opacity = 1
1078
+ if (aphaModFixNode && aphaModFixNode['amt'] && aphaModFixNode['amt'] !== '') {
1079
+ opacity = parseInt(aphaModFixNode['amt']) / 100000
1080
+ }
1081
+
1082
+ return {
1083
+ picBase64,
1084
+ opacity,
1085
+ }
1086
+ }
1087
+
1088
+ async function getSlideBackgroundFill(warpObj) {
1089
+ const slideContent = warpObj['slideContent']
1090
+ const slideLayoutContent = warpObj['slideLayoutContent']
1091
+ const slideMasterContent = warpObj['slideMasterContent']
1092
+
1093
+ let bgPr = getTextByPathList(slideContent, ['p:sld', 'p:cSld', 'p:bg', 'p:bgPr'])
1094
+
1095
+ let background = '#fff'
1096
+ let backgroundType = 'color'
1097
+
1098
+ if (bgPr) {
1099
+ const bgFillTyp = getFillType(bgPr)
1100
+
1101
+ if (bgFillTyp === 'SOLID_FILL') {
1102
+ const sldFill = bgPr['a:solidFill']
1103
+ let clrMapOvr
1104
+ const sldClrMapOvr = getTextByPathList(slideContent, ['p:sld', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
1105
+ if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
1106
+ else {
1107
+ const sldClrMapOvr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
1108
+ if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
1109
+ else clrMapOvr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
1110
+ }
1111
+ const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
1112
+ background = `#${sldBgClr}`
1113
+ }
1114
+ else if (bgFillTyp === 'PIC_FILL') {
1115
+ background = await getBgPicFill(bgPr, 'slideBg', warpObj)
1116
+ backgroundType = 'image'
1117
+ }
1118
+ }
1119
+ else {
1120
+ bgPr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:cSld', 'p:bg', 'p:bgPr'])
1121
+
1122
+ let clrMapOvr
1123
+ const sldClrMapOvr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
1124
+ if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
1125
+ else clrMapOvr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
1126
+
1127
+ if (bgPr) {
1128
+ const bgFillTyp = getFillType(bgPr)
1129
+ if (bgFillTyp === 'SOLID_FILL') {
1130
+ const sldFill = bgPr['a:solidFill']
1131
+ const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
1132
+ background = `#${sldBgClr}`
1133
+ }
1134
+ else if (bgFillTyp === 'PIC_FILL') {
1135
+ background = await getBgPicFill(bgPr, 'slideLayoutBg', warpObj)
1136
+ backgroundType = 'image'
1137
+ }
1138
+ }
1139
+ else {
1140
+ bgPr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:cSld', 'p:bg', 'p:bgPr'])
1141
+
1142
+ const clrMap = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
1143
+ if (bgPr) {
1144
+ const bgFillTyp = getFillType(bgPr)
1145
+ if (bgFillTyp === 'SOLID_FILL') {
1146
+ const sldFill = bgPr['a:solidFill']
1147
+ const sldBgClr = getSolidFill(sldFill, clrMap, undefined, warpObj)
1148
+ background = `#${sldBgClr}`
1149
+ }
1150
+ else if (bgFillTyp === 'PIC_FILL') {
1151
+ background = await getBgPicFill(bgPr, 'slideMasterBg', warpObj)
1152
+ backgroundType = 'image'
1153
+ }
1154
+ }
1155
+ }
1156
+ }
1157
+ return {
1158
+ type: backgroundType,
1159
+ value: background,
1160
+ }
1161
+ }
1162
+
1163
+ function getShapeFill(node, isSvgMode) {
1164
+ if (getTextByPathList(node, ['p:spPr', 'a:noFill'])) {
1165
+ return isSvgMode ? 'none' : 'background-color: initial;'
1166
+ }
1167
+
1168
+ let fillColor
1169
+ if (!fillColor) {
1170
+ fillColor = getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:srgbClr', 'attrs', 'val'])
1171
+ }
1172
+
1173
+ if (!fillColor) {
1174
+ const schemeClr = 'a:' + getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'attrs', 'val'])
1175
+ fillColor = getSchemeColorFromTheme(schemeClr)
1176
+ }
1177
+
1178
+ if (!fillColor) {
1179
+ const schemeClr = 'a:' + getTextByPathList(node, ['p:style', 'a:fillRef', 'a:schemeClr', 'attrs', 'val'])
1180
+ fillColor = getSchemeColorFromTheme(schemeClr)
1181
+ }
1182
+
1183
+ if (fillColor) {
1184
+ fillColor = `#${fillColor}`
1185
+
1186
+ let lumMod = parseInt(getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'a:lumMod', 'attrs', 'val'])) / 100000
1187
+ let lumOff = parseInt(getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'a:lumOff', 'attrs', 'val'])) / 100000
1188
+ if (isNaN(lumMod)) lumMod = 1.0
1189
+ if (isNaN(lumOff)) lumOff = 0
1190
+
1191
+ const color = tinycolor(fillColor).toHsl()
1192
+ const lum = color.l * (1 + lumOff)
1193
+ return tinycolor({ h: color.h, s: color.s, l: lum, a: color.a }).toHexString()
1194
+ }
1195
+
1196
+ if (isSvgMode) return 'none'
1197
+ return fillColor
1198
+ }
1199
+
1200
+ function getSolidFill(solidFill) {
1201
+ if (!solidFill) return solidFill
1202
+
1203
+ let color = 'fff'
1204
+
1205
+ if (solidFill['a:srgbClr']) {
1206
+ color = getTextByPathList(solidFill['a:srgbClr'], ['attrs', 'val'])
1207
+ }
1208
+ else if (solidFill['a:schemeClr']) {
1209
+ const schemeClr = 'a:' + getTextByPathList(solidFill['a:schemeClr'], ['attrs', 'val'])
1210
+ color = getSchemeColorFromTheme(schemeClr)
1211
+ }
1212
+
1213
+ return color
1214
+ }
1215
+
1216
+ function getSchemeColorFromTheme(schemeClr) {
1217
+ switch (schemeClr) {
1218
+ case 'a:tx1':
1219
+ schemeClr = 'a:dk1'
1220
+ break
1221
+ case 'a:tx2':
1222
+ schemeClr = 'a:dk2'
1223
+ break
1224
+ case 'a:bg1':
1225
+ schemeClr = 'a:lt1'
1226
+ break
1227
+ case 'a:bg2':
1228
+ schemeClr = 'a:lt2'
1229
+ break
1230
+ default:
1231
+ break
1232
+ }
1233
+ const refNode = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:clrScheme', schemeClr])
1234
+ let color = getTextByPathList(refNode, ['a:srgbClr', 'attrs', 'val'])
1235
+ if (!color) color = getTextByPathList(refNode, ['a:sysClr', 'attrs', 'lastClr'])
1236
+ return color
1237
+ }
1238
+
1239
+ function extractChartData(serNode) {
1240
+ const dataMat = []
1241
+ if (!serNode) return dataMat
1242
+
1243
+ if (serNode['c:xVal']) {
1244
+ let dataRow = []
1245
+ eachElement(serNode['c:xVal']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1246
+ dataRow.push(parseFloat(innerNode['c:v']))
1247
+ return ''
1248
+ })
1249
+ dataMat.push(dataRow)
1250
+ dataRow = []
1251
+ eachElement(serNode['c:yVal']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1252
+ dataRow.push(parseFloat(innerNode['c:v']))
1253
+ return ''
1254
+ })
1255
+ dataMat.push(dataRow)
1256
+ }
1257
+ else {
1258
+ eachElement(serNode, (innerNode, index) => {
1259
+ const dataRow = []
1260
+ const colName = getTextByPathList(innerNode, ['c:tx', 'c:strRef', 'c:strCache', 'c:pt', 'c:v']) || index
1261
+
1262
+ const rowNames = {}
1263
+ if (getTextByPathList(innerNode, ['c:cat', 'c:strRef', 'c:strCache', 'c:pt'])) {
1264
+ eachElement(innerNode['c:cat']['c:strRef']['c:strCache']['c:pt'], innerNode => {
1265
+ rowNames[innerNode['attrs']['idx']] = innerNode['c:v']
1266
+ return ''
1267
+ })
1268
+ }
1269
+ else if (getTextByPathList(innerNode, ['c:cat', 'c:numRef', 'c:numCache', 'c:pt'])) {
1270
+ eachElement(innerNode['c:cat']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1271
+ rowNames[innerNode['attrs']['idx']] = innerNode['c:v']
1272
+ return ''
1273
+ })
1274
+ }
1275
+
1276
+ if (getTextByPathList(innerNode, ['c:val', 'c:numRef', 'c:numCache', 'c:pt'])) {
1277
+ eachElement(innerNode['c:val']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1278
+ dataRow.push({
1279
+ x: innerNode['attrs']['idx'],
1280
+ y: parseFloat(innerNode['c:v']),
1281
+ })
1282
+ return ''
1283
+ })
1284
+ }
1285
+
1286
+ dataMat.push({
1287
+ key: colName,
1288
+ values: dataRow,
1289
+ xlabels: rowNames,
1290
+ })
1291
+ return ''
1292
+ })
1293
+ }
1294
+
1295
+ return dataMat
1082
1296
  }