pptxtojson 0.0.1
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/.babelrc.cjs +15 -0
- package/.eslintignore +3 -0
- package/.eslintrc.cjs +77 -0
- package/LICENSE +674 -0
- package/README.md +14 -0
- package/dist/index.esm.js +1 -0
- package/dist/index.js +1 -0
- package/favicon.ico +0 -0
- package/index.html +119 -0
- package/package.json +35 -0
- package/rollup.config.js +43 -0
- package/src/pptxtojson.js +1082 -0
- package/src/utils.js +72 -0
- package/test.pptx +0 -0
|
@@ -0,0 +1,1082 @@
|
|
|
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 = 96 / 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) type = getTextByPathList(slideLayoutSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
|
|
325
|
+
if (!type) type = getTextByPathList(slideMasterSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
|
|
326
|
+
|
|
327
|
+
return genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, type, warpObj)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function processCxnSpNode(node, warpObj) {
|
|
331
|
+
const id = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['id']
|
|
332
|
+
const name = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['name']
|
|
333
|
+
|
|
334
|
+
return genShape(node, undefined, undefined, id, name, undefined, undefined, warpObj)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, type, warpObj) {
|
|
338
|
+
const xfrmList = ['p:spPr', 'a:xfrm']
|
|
339
|
+
const slideXfrmNode = getTextByPathList(node, xfrmList)
|
|
340
|
+
const slideLayoutXfrmNode = getTextByPathList(slideLayoutSpNode, xfrmList)
|
|
341
|
+
const slideMasterXfrmNode = getTextByPathList(slideMasterSpNode, xfrmList)
|
|
342
|
+
|
|
343
|
+
const shapType = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst'])
|
|
344
|
+
|
|
345
|
+
const { top, left } = getPosition(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
|
|
346
|
+
const { width, height } = getSize(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
|
|
347
|
+
|
|
348
|
+
let isFlipV = false
|
|
349
|
+
let isFlipH = false
|
|
350
|
+
if (getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1') {
|
|
351
|
+
isFlipV = true
|
|
352
|
+
}
|
|
353
|
+
if (getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1') {
|
|
354
|
+
isFlipH = true
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const rotate = angleToDegrees(getTextByPathList(slideXfrmNode, ['attrs', 'rot']))
|
|
358
|
+
|
|
359
|
+
const txtXframeNode = getTextByPathList(node, ['p:txXfrm'])
|
|
360
|
+
let txtRotate
|
|
361
|
+
if (txtXframeNode) {
|
|
362
|
+
const txtXframeRot = getTextByPathList(txtXframeNode, ['attrs', 'rot'])
|
|
363
|
+
if (txtXframeRot) txtRotate = angleToDegrees(txtXframeRot) + 90
|
|
364
|
+
}
|
|
365
|
+
else txtRotate = rotate
|
|
366
|
+
|
|
367
|
+
let content = ''
|
|
368
|
+
if (node['p:txBody']) content = genTextBody(node['p:txBody'], slideLayoutSpNode, slideMasterSpNode, type, warpObj)
|
|
369
|
+
|
|
370
|
+
if (shapType) {
|
|
371
|
+
const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
|
|
372
|
+
const cx = parseInt(ext['cx']) * FACTOR
|
|
373
|
+
const cy = parseInt(ext['cy']) * FACTOR
|
|
374
|
+
|
|
375
|
+
const { borderColor, borderWidth, borderType } = getBorder(node, true)
|
|
376
|
+
const fillColor = getShapeFill(node, true)
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
type: 'shape',
|
|
380
|
+
left,
|
|
381
|
+
top,
|
|
382
|
+
width,
|
|
383
|
+
height,
|
|
384
|
+
cx,
|
|
385
|
+
cy,
|
|
386
|
+
borderColor,
|
|
387
|
+
borderWidth,
|
|
388
|
+
borderType,
|
|
389
|
+
fillColor,
|
|
390
|
+
content,
|
|
391
|
+
isFlipV,
|
|
392
|
+
isFlipH,
|
|
393
|
+
rotate,
|
|
394
|
+
shapType,
|
|
395
|
+
id,
|
|
396
|
+
name,
|
|
397
|
+
idx,
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const { borderColor, borderWidth, borderType } = getBorder(node, false)
|
|
402
|
+
const fillColor = getShapeFill(node, false)
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
type: 'text',
|
|
406
|
+
left,
|
|
407
|
+
top,
|
|
408
|
+
width,
|
|
409
|
+
height,
|
|
410
|
+
borderColor,
|
|
411
|
+
borderWidth,
|
|
412
|
+
borderType,
|
|
413
|
+
fillColor,
|
|
414
|
+
isFlipV,
|
|
415
|
+
isFlipH,
|
|
416
|
+
rotate: txtRotate,
|
|
417
|
+
content,
|
|
418
|
+
id,
|
|
419
|
+
name,
|
|
420
|
+
idx,
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function processPicNode(node, warpObj) {
|
|
425
|
+
const rid = node['p:blipFill']['a:blip']['attrs']['r:embed']
|
|
426
|
+
const imgName = warpObj['slideResObj'][rid]['target']
|
|
427
|
+
const imgFileExt = extractFileExtension(imgName).toLowerCase()
|
|
428
|
+
const zip = warpObj['zip']
|
|
429
|
+
const imgArrayBuffer = await zip.file(imgName).async('arraybuffer')
|
|
430
|
+
const xfrmNode = node['p:spPr']['a:xfrm']
|
|
431
|
+
let mimeType = ''
|
|
432
|
+
|
|
433
|
+
switch (imgFileExt) {
|
|
434
|
+
case 'jpg':
|
|
435
|
+
case 'jpeg':
|
|
436
|
+
mimeType = 'image/jpeg'
|
|
437
|
+
break
|
|
438
|
+
case 'png':
|
|
439
|
+
mimeType = 'image/png'
|
|
440
|
+
break
|
|
441
|
+
case 'gif':
|
|
442
|
+
mimeType = 'image/gif'
|
|
443
|
+
break
|
|
444
|
+
case 'emf':
|
|
445
|
+
mimeType = 'image/x-emf'
|
|
446
|
+
break
|
|
447
|
+
case 'wmf':
|
|
448
|
+
mimeType = 'image/x-wmf'
|
|
449
|
+
break
|
|
450
|
+
default:
|
|
451
|
+
mimeType = 'image/*'
|
|
452
|
+
}
|
|
453
|
+
const { top, left } = getPosition(xfrmNode, undefined, undefined)
|
|
454
|
+
const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
455
|
+
const src = `data:${mimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
|
|
456
|
+
|
|
457
|
+
let rotate = 0
|
|
458
|
+
const rotateNode = getTextByPathList(node, ['p:spPr', 'a:xfrm', 'attrs', 'rot'])
|
|
459
|
+
if (rotateNode) rotate = angleToDegrees(rotateNode)
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
type: 'image',
|
|
463
|
+
top,
|
|
464
|
+
left,
|
|
465
|
+
width,
|
|
466
|
+
height,
|
|
467
|
+
src,
|
|
468
|
+
rotate,
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function processGraphicFrameNode(node, warpObj) {
|
|
473
|
+
const graphicTypeUri = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'attrs', 'uri'])
|
|
474
|
+
|
|
475
|
+
let result
|
|
476
|
+
switch (graphicTypeUri) {
|
|
477
|
+
case 'http://schemas.openxmlformats.org/drawingml/2006/table':
|
|
478
|
+
result = genTable(node, warpObj)
|
|
479
|
+
break
|
|
480
|
+
case 'http://schemas.openxmlformats.org/drawingml/2006/chart':
|
|
481
|
+
result = await genChart(node, warpObj)
|
|
482
|
+
break
|
|
483
|
+
case 'http://schemas.openxmlformats.org/drawingml/2006/diagram':
|
|
484
|
+
result = genDiagram(node, warpObj)
|
|
485
|
+
break
|
|
486
|
+
default:
|
|
487
|
+
}
|
|
488
|
+
return result
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function genTextBody(textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, warpObj) {
|
|
492
|
+
if (!textBodyNode) return ''
|
|
493
|
+
|
|
494
|
+
let text = ''
|
|
495
|
+
const slideMasterTextStyles = warpObj['slideMasterTextStyles']
|
|
496
|
+
|
|
497
|
+
if (textBodyNode['a:p'].constructor === Array) {
|
|
498
|
+
for (const pNode of textBodyNode['a:p']) {
|
|
499
|
+
const rNode = pNode['a:r']
|
|
500
|
+
text += `<div class="${getHorizontalAlign(pNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles)}">`
|
|
501
|
+
text += genBuChar(pNode)
|
|
502
|
+
if (!rNode) text += genSpanElement(pNode, slideLayoutSpNode, type, warpObj)
|
|
503
|
+
else if (rNode.constructor === Array) {
|
|
504
|
+
for (const rNodeItem of rNode) text += genSpanElement(rNodeItem, slideLayoutSpNode, type, warpObj)
|
|
505
|
+
}
|
|
506
|
+
else text += genSpanElement(rNode, slideLayoutSpNode, type, warpObj)
|
|
507
|
+
text += '</div>'
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
const pNode = textBodyNode['a:p']
|
|
512
|
+
const rNode = pNode['a:r']
|
|
513
|
+
text += `<div class="${getHorizontalAlign(pNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles)}">`
|
|
514
|
+
text += genBuChar(pNode)
|
|
515
|
+
if (!rNode) text += genSpanElement(pNode, slideLayoutSpNode, type, warpObj)
|
|
516
|
+
else if (rNode.constructor === Array) {
|
|
517
|
+
for (const rNodeItem of rNode) text += genSpanElement(rNodeItem, slideLayoutSpNode, type, warpObj)
|
|
518
|
+
}
|
|
519
|
+
else text += genSpanElement(rNode, slideLayoutSpNode, type, warpObj)
|
|
520
|
+
text += '</div>'
|
|
521
|
+
}
|
|
522
|
+
return text
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function genBuChar(node) {
|
|
526
|
+
const pPrNode = node['a:pPr']
|
|
527
|
+
|
|
528
|
+
let lvl = parseInt(getTextByPathList(pPrNode, ['attrs', 'lvl']))
|
|
529
|
+
if (isNaN(lvl)) lvl = 0
|
|
530
|
+
|
|
531
|
+
const buChar = getTextByPathList(pPrNode, ['a:buChar', 'attrs', 'char'])
|
|
532
|
+
if (buChar) {
|
|
533
|
+
const buFontAttrs = getTextByPathList(pPrNode, ['a:buFont', 'attrs'])
|
|
534
|
+
|
|
535
|
+
let marginLeft = parseInt(getTextByPathList(pPrNode, ['attrs', 'marL'])) * FACTOR
|
|
536
|
+
if (buFontAttrs) {
|
|
537
|
+
let marginRight = parseInt(buFontAttrs['pitchFamily'])
|
|
538
|
+
|
|
539
|
+
if (isNaN(marginLeft)) marginLeft = 328600 * FACTOR
|
|
540
|
+
if (isNaN(marginRight)) marginRight = 0
|
|
541
|
+
|
|
542
|
+
const typeface = buFontAttrs['typeface']
|
|
543
|
+
|
|
544
|
+
return `<span style="font-family: ${typeface}; margin-left: ${marginLeft * lvl}px; margin-right: ${marginRight}px; font-size: 20pt;">${buChar}</span>`
|
|
545
|
+
}
|
|
546
|
+
marginLeft = 328600 * FACTOR * lvl
|
|
547
|
+
return `<span style="margin-left: ${marginLeft}px;">${buChar}</span>`
|
|
548
|
+
}
|
|
549
|
+
return `<span style="margin-left: ${328600 * FACTOR * lvl}px; margin-right: 0;"></span>`
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function genSpanElement(node, slideLayoutSpNode, type, warpObj) {
|
|
553
|
+
const slideMasterTextStyles = warpObj['slideMasterTextStyles']
|
|
554
|
+
|
|
555
|
+
let text = node['a:t']
|
|
556
|
+
if (typeof text !== 'string') text = getTextByPathList(node, ['a:fld', 'a:t'])
|
|
557
|
+
if (typeof text !== 'string') text = ' '
|
|
558
|
+
|
|
559
|
+
const styleText = `
|
|
560
|
+
color: ${getFontColor(node)};
|
|
561
|
+
font-size: ${getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles)};
|
|
562
|
+
font-family: ${getFontType(node, type)};
|
|
563
|
+
font-weight: ${getFontBold(node)};
|
|
564
|
+
font-style: ${getFontItalic(node)};
|
|
565
|
+
text-decoration: ${getFontDecoration(node)};
|
|
566
|
+
vertical-align: ${getTextVerticalAlign(node)};
|
|
567
|
+
`
|
|
568
|
+
|
|
569
|
+
const linkID = getTextByPathList(node, ['a:rPr', 'a:hlinkClick', 'attrs', 'r:id'])
|
|
570
|
+
if (linkID) {
|
|
571
|
+
const linkURL = warpObj['slideResObj'][linkID]['target']
|
|
572
|
+
return `<span class="text-block" style="${styleText}"><a href="${linkURL}" target="_blank">${text.replace(/\s/i, ' ')}</a></span>`
|
|
573
|
+
}
|
|
574
|
+
return `<span class="text-block" style="${styleText}">${text.replace(/\s/i, ' ')}</span>`
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function genTable(node, warpObj) {
|
|
578
|
+
const tableNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl'])
|
|
579
|
+
const xfrmNode = getTextByPathList(node, ['p:xfrm'])
|
|
580
|
+
const { top, left } = getPosition(xfrmNode, undefined, undefined)
|
|
581
|
+
const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
582
|
+
|
|
583
|
+
const trNodes = tableNode['a:tr']
|
|
584
|
+
|
|
585
|
+
const data = []
|
|
586
|
+
if (trNodes.constructor === Array) {
|
|
587
|
+
for (const trNode of trNodes) {
|
|
588
|
+
const tcNodes = trNode['a:tc']
|
|
589
|
+
const tr = []
|
|
590
|
+
|
|
591
|
+
if (tcNodes.constructor === Array) {
|
|
592
|
+
for (const tcNode of tcNodes) {
|
|
593
|
+
const text = genTextBody(tcNode['a:txBody'], undefined, undefined, undefined, warpObj)
|
|
594
|
+
const rowSpan = getTextByPathList(tcNode, ['attrs', 'rowSpan'])
|
|
595
|
+
const colSpan = getTextByPathList(tcNode, ['attrs', 'gridSpan'])
|
|
596
|
+
const vMerge = getTextByPathList(tcNode, ['attrs', 'vMerge'])
|
|
597
|
+
const hMerge = getTextByPathList(tcNode, ['attrs', 'hMerge'])
|
|
598
|
+
|
|
599
|
+
tr.push({ text, rowSpan, colSpan, vMerge, hMerge })
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
const text = genTextBody(tcNodes['a:txBody'])
|
|
604
|
+
tr.push({ text })
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
data.push(tr)
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
const tcNodes = trNodes['a:tc']
|
|
612
|
+
const tr = []
|
|
613
|
+
|
|
614
|
+
if (tcNodes.constructor === Array) {
|
|
615
|
+
for (const tcNode of tcNodes) {
|
|
616
|
+
const text = genTextBody(tcNode['a:txBody'])
|
|
617
|
+
tr.push({ text })
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
const text = genTextBody(tcNodes['a:txBody'])
|
|
622
|
+
tr.push({ text })
|
|
623
|
+
}
|
|
624
|
+
data.push(tr)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return {
|
|
628
|
+
type: 'table',
|
|
629
|
+
top,
|
|
630
|
+
left,
|
|
631
|
+
width,
|
|
632
|
+
height,
|
|
633
|
+
data,
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async function genChart(node, warpObj) {
|
|
638
|
+
const xfrmNode = getTextByPathList(node, ['p:xfrm'])
|
|
639
|
+
const { top, left } = getPosition(xfrmNode, undefined, undefined)
|
|
640
|
+
const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
641
|
+
|
|
642
|
+
const rid = node['a:graphic']['a:graphicData']['c:chart']['attrs']['r:id']
|
|
643
|
+
const refName = warpObj['slideResObj'][rid]['target']
|
|
644
|
+
const content = await readXmlFile(warpObj['zip'], refName)
|
|
645
|
+
const plotArea = getTextByPathList(content, ['c:chartSpace', 'c:chart', 'c:plotArea'])
|
|
646
|
+
|
|
647
|
+
let chart = null
|
|
648
|
+
for (const key in plotArea) {
|
|
649
|
+
switch (key) {
|
|
650
|
+
case 'c:lineChart':
|
|
651
|
+
chart = {
|
|
652
|
+
type: 'lineChart',
|
|
653
|
+
data: extractChartData(plotArea[key]['c:ser']),
|
|
654
|
+
}
|
|
655
|
+
break
|
|
656
|
+
case 'c:barChart':
|
|
657
|
+
chart = {
|
|
658
|
+
type: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']) === 'stacked' ? 'stackedBarChart' : 'barChart',
|
|
659
|
+
data: extractChartData(plotArea[key]['c:ser']),
|
|
660
|
+
}
|
|
661
|
+
break
|
|
662
|
+
case 'c:pieChart':
|
|
663
|
+
chart = {
|
|
664
|
+
type: 'pieChart',
|
|
665
|
+
data: extractChartData(plotArea[key]['c:ser']),
|
|
666
|
+
}
|
|
667
|
+
break
|
|
668
|
+
case 'c:pie3DChart':
|
|
669
|
+
chart = {
|
|
670
|
+
type: 'pie3DChart',
|
|
671
|
+
data: extractChartData(plotArea[key]['c:ser']),
|
|
672
|
+
}
|
|
673
|
+
break
|
|
674
|
+
case 'c:areaChart':
|
|
675
|
+
chart = {
|
|
676
|
+
type: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']) === 'percentStacked' ? 'stackedAreaChart' : 'areaChart',
|
|
677
|
+
data: extractChartData(plotArea[key]['c:ser']),
|
|
678
|
+
}
|
|
679
|
+
break
|
|
680
|
+
case 'c:scatterChart':
|
|
681
|
+
chart = {
|
|
682
|
+
type: 'scatterChart',
|
|
683
|
+
data: extractChartData(plotArea[key]['c:ser']),
|
|
684
|
+
}
|
|
685
|
+
break
|
|
686
|
+
case 'c:catAx':
|
|
687
|
+
break
|
|
688
|
+
case 'c:valAx':
|
|
689
|
+
break
|
|
690
|
+
default:
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (!chart) return {}
|
|
695
|
+
return {
|
|
696
|
+
type: 'chart',
|
|
697
|
+
top,
|
|
698
|
+
left,
|
|
699
|
+
width,
|
|
700
|
+
height,
|
|
701
|
+
data: chart.data,
|
|
702
|
+
chartType: chart.type,
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function genDiagram(node) {
|
|
707
|
+
const xfrmNode = getTextByPathList(node, ['p:xfrm'])
|
|
708
|
+
const { left, top } = getPosition(xfrmNode, undefined, undefined)
|
|
709
|
+
const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
710
|
+
|
|
711
|
+
return {
|
|
712
|
+
type: 'diagram',
|
|
713
|
+
left,
|
|
714
|
+
top,
|
|
715
|
+
width,
|
|
716
|
+
height,
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function getPosition(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
|
|
721
|
+
let off
|
|
722
|
+
|
|
723
|
+
if (slideSpNode) off = slideSpNode['a:off']['attrs']
|
|
724
|
+
else if (slideLayoutSpNode) off = slideLayoutSpNode['a:off']['attrs']
|
|
725
|
+
else if (slideMasterSpNode) off = slideMasterSpNode['a:off']['attrs']
|
|
726
|
+
|
|
727
|
+
if (!off) return { top: 0, left: 0 }
|
|
728
|
+
|
|
729
|
+
return {
|
|
730
|
+
top: parseInt(off['y']) * FACTOR,
|
|
731
|
+
left: parseInt(off['x']) * FACTOR,
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function getSize(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
|
|
736
|
+
let ext
|
|
737
|
+
|
|
738
|
+
if (slideSpNode) ext = slideSpNode['a:ext']['attrs']
|
|
739
|
+
else if (slideLayoutSpNode) ext = slideLayoutSpNode['a:ext']['attrs']
|
|
740
|
+
else if (slideMasterSpNode) ext = slideMasterSpNode['a:ext']['attrs']
|
|
741
|
+
|
|
742
|
+
if (!ext) return { width: 0, height: 0 }
|
|
743
|
+
|
|
744
|
+
return {
|
|
745
|
+
width: parseInt(ext['cx']) * FACTOR,
|
|
746
|
+
height: parseInt(ext['cy']) * FACTOR,
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function getHorizontalAlign(node, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles) {
|
|
751
|
+
let algn = getTextByPathList(node, ['a:pPr', 'attrs', 'algn'])
|
|
752
|
+
|
|
753
|
+
if (!algn) algn = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn'])
|
|
754
|
+
if (!algn) algn = getTextByPathList(slideMasterSpNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn'])
|
|
755
|
+
if (!algn) {
|
|
756
|
+
switch (type) {
|
|
757
|
+
case 'title':
|
|
758
|
+
case 'subTitle':
|
|
759
|
+
case 'ctrTitle':
|
|
760
|
+
algn = getTextByPathList(slideMasterTextStyles, ['p:titleStyle', 'a:lvl1pPr', 'attrs', 'alng'])
|
|
761
|
+
break
|
|
762
|
+
default:
|
|
763
|
+
algn = getTextByPathList(slideMasterTextStyles, ['p:otherStyle', 'a:lvl1pPr', 'attrs', 'alng'])
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (!algn) {
|
|
767
|
+
if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') return 'h-mid'
|
|
768
|
+
else if (type === 'sldNum') return 'h-right'
|
|
769
|
+
}
|
|
770
|
+
return algn === 'ctr' ? 'h-mid' : algn === 'r' ? 'h-right' : 'h-left'
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function getFontType(node, type) {
|
|
774
|
+
let typeface = getTextByPathList(node, ['a:rPr', 'a:latin', 'attrs', 'typeface'])
|
|
775
|
+
|
|
776
|
+
if (!typeface) {
|
|
777
|
+
const fontSchemeNode = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:fontScheme'])
|
|
778
|
+
|
|
779
|
+
if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
|
|
780
|
+
typeface = getTextByPathList(fontSchemeNode, ['a:majorFont', 'a:latin', 'attrs', 'typeface'])
|
|
781
|
+
}
|
|
782
|
+
else if (type === 'body') {
|
|
783
|
+
typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return typeface || 'inherit'
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function getFontColor(node) {
|
|
794
|
+
const color = getTextByPathList(node, ['a:rPr', 'a:solidFill', 'a:srgbClr', 'attrs', 'val'])
|
|
795
|
+
return color ? `#${color}` : '#000'
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles) {
|
|
799
|
+
let fontSize
|
|
800
|
+
|
|
801
|
+
if (node['a:rPr']) fontSize = parseInt(node['a:rPr']['attrs']['sz']) / 100
|
|
802
|
+
|
|
803
|
+
if ((isNaN(fontSize) || !fontSize)) {
|
|
804
|
+
const sz = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:lstStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
|
|
805
|
+
fontSize = parseInt(sz) / 100
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (isNaN(fontSize) || !fontSize) {
|
|
809
|
+
let sz
|
|
810
|
+
if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
|
|
811
|
+
sz = getTextByPathList(slideMasterTextStyles, ['p:titleStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
|
|
812
|
+
}
|
|
813
|
+
else if (type === 'body') {
|
|
814
|
+
sz = getTextByPathList(slideMasterTextStyles, ['p:bodyStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
|
|
815
|
+
}
|
|
816
|
+
else if (type === 'dt' || type === 'sldNum') {
|
|
817
|
+
sz = '1200'
|
|
818
|
+
}
|
|
819
|
+
else if (!type) {
|
|
820
|
+
sz = getTextByPathList(slideMasterTextStyles, ['p:otherStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
|
|
821
|
+
}
|
|
822
|
+
if (sz) fontSize = parseInt(sz) / 100
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const baseline = getTextByPathList(node, ['a:rPr', 'attrs', 'baseline'])
|
|
826
|
+
if (baseline && !isNaN(fontSize)) fontSize -= 10
|
|
827
|
+
|
|
828
|
+
return (isNaN(fontSize) || !fontSize) ? 'inherit' : (fontSize + 'pt')
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function getFontBold(node) {
|
|
832
|
+
return (node['a:rPr'] && node['a:rPr']['attrs']['b'] === '1') ? 'bold' : 'initial'
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function getFontItalic(node) {
|
|
836
|
+
return (node['a:rPr'] && node['a:rPr']['attrs']['i'] === '1') ? 'italic' : 'normal'
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function getFontDecoration(node) {
|
|
840
|
+
return (node['a:rPr'] && node['a:rPr']['attrs']['u'] === 'sng') ? 'underline' : 'initial'
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function getTextVerticalAlign(node) {
|
|
844
|
+
const baseline = getTextByPathList(node, ['a:rPr', 'attrs', 'baseline'])
|
|
845
|
+
return baseline ? (parseInt(baseline) / 1000) + '%' : 'baseline'
|
|
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
|
|
1082
|
+
}
|