|
@@ -0,0 +1,1082 @@
|
|
|
+import JSZip from 'jszip'
|
|
|
+import { readXmlFile } from './readXmlFile'
|
|
|
+import { getBorder } from './border'
|
|
|
+import { getSlideBackgroundFill, getShapeFill, getSolidFill, getPicFill } from './fill'
|
|
|
+import { getChartInfo } from './chart'
|
|
|
+import { getVerticalAlign } from './align'
|
|
|
+import { getPosition, getSize } from './position'
|
|
|
+import { genTextBody } from './text'
|
|
|
+import { getCustomShapePath } from './shape'
|
|
|
+import { extractFileExtension, base64ArrayBuffer, getTextByPathList, angleToDegrees, getMimeType, isVideoLink, escapeHtml, hasValidText } from './utils'
|
|
|
+import { getShadow } from './shadow'
|
|
|
+import { getTableBorders, getTableCellParams, getTableRowParams } from './table'
|
|
|
+import { RATIO_EMUs_Points } from './constants'
|
|
|
+import { findOMath, latexFormart, parseOMath } from './math'
|
|
|
+
|
|
|
+export async function parse(file) {
|
|
|
+ const slides = []
|
|
|
+
|
|
|
+ const zip = await JSZip.loadAsync(file)
|
|
|
+
|
|
|
+ const filesInfo = await getContentTypes(zip)
|
|
|
+ const { width, height, defaultTextStyle } = await getSlideInfo(zip)
|
|
|
+ const { themeContent, themeColors } = await getTheme(zip)
|
|
|
+
|
|
|
+ for (const filename of filesInfo.slides) {
|
|
|
+ const singleSlide = await processSingleSlide(zip, filename, themeContent, defaultTextStyle)
|
|
|
+ slides.push(singleSlide)
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ slides,
|
|
|
+ themeColors,
|
|
|
+ size: {
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ },
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function getContentTypes(zip) {
|
|
|
+ const ContentTypesJson = await readXmlFile(zip, '[Content_Types].xml')
|
|
|
+ const subObj = ContentTypesJson['Types']['Override']
|
|
|
+ let slidesLocArray = []
|
|
|
+ let slideLayoutsLocArray = []
|
|
|
+
|
|
|
+ for (const item of subObj) {
|
|
|
+ switch (item['attrs']['ContentType']) {
|
|
|
+ case 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml':
|
|
|
+ slidesLocArray.push(item['attrs']['PartName'].substr(1))
|
|
|
+ break
|
|
|
+ case 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml':
|
|
|
+ slideLayoutsLocArray.push(item['attrs']['PartName'].substr(1))
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const sortSlideXml = (p1, p2) => {
|
|
|
+ const n1 = +/(\d+)\.xml/.exec(p1)[1]
|
|
|
+ const n2 = +/(\d+)\.xml/.exec(p2)[1]
|
|
|
+ return n1 - n2
|
|
|
+ }
|
|
|
+ slidesLocArray = slidesLocArray.sort(sortSlideXml)
|
|
|
+ slideLayoutsLocArray = slideLayoutsLocArray.sort(sortSlideXml)
|
|
|
+
|
|
|
+ return {
|
|
|
+ slides: slidesLocArray,
|
|
|
+ slideLayouts: slideLayoutsLocArray,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function getSlideInfo(zip) {
|
|
|
+ const content = await readXmlFile(zip, 'ppt/presentation.xml')
|
|
|
+ const sldSzAttrs = content['p:presentation']['p:sldSz']['attrs']
|
|
|
+ const defaultTextStyle = content['p:presentation']['p:defaultTextStyle']
|
|
|
+ return {
|
|
|
+ width: parseInt(sldSzAttrs['cx']) * RATIO_EMUs_Points,
|
|
|
+ height: parseInt(sldSzAttrs['cy']) * RATIO_EMUs_Points,
|
|
|
+ defaultTextStyle,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function getTheme(zip) {
|
|
|
+ const preResContent = await readXmlFile(zip, 'ppt/_rels/presentation.xml.rels')
|
|
|
+ const relationshipArray = preResContent['Relationships']['Relationship']
|
|
|
+ let themeURI
|
|
|
+
|
|
|
+ if (relationshipArray.constructor === Array) {
|
|
|
+ for (const relationshipItem of relationshipArray) {
|
|
|
+ if (relationshipItem['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
|
|
|
+ themeURI = relationshipItem['attrs']['Target']
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (relationshipArray['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
|
|
|
+ themeURI = relationshipArray['attrs']['Target']
|
|
|
+ }
|
|
|
+
|
|
|
+ const themeContent = await readXmlFile(zip, 'ppt/' + themeURI)
|
|
|
+
|
|
|
+ const themeColors = []
|
|
|
+ const clrScheme = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:clrScheme'])
|
|
|
+ if (clrScheme) {
|
|
|
+ for (let i = 1; i <= 6; i++) {
|
|
|
+ if (clrScheme[`a:accent${i}`] === undefined) break
|
|
|
+ const color = getTextByPathList(clrScheme, [`a:accent${i}`, 'a:srgbClr', 'attrs', 'val'])
|
|
|
+ if (color) themeColors.push('#' + color)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return { themeContent, themeColors }
|
|
|
+}
|
|
|
+
|
|
|
+async function processSingleSlide(zip, sldFileName, themeContent, defaultTextStyle) {
|
|
|
+ const resName = sldFileName.replace('slides/slide', 'slides/_rels/slide') + '.rels'
|
|
|
+ const resContent = await readXmlFile(zip, resName)
|
|
|
+ let relationshipArray = resContent['Relationships']['Relationship']
|
|
|
+ if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
|
|
|
+
|
|
|
+ let noteFilename = ''
|
|
|
+ let layoutFilename = ''
|
|
|
+ let masterFilename = ''
|
|
|
+ let themeFilename = ''
|
|
|
+ let diagramFilename = ''
|
|
|
+ const slideResObj = {}
|
|
|
+ const layoutResObj = {}
|
|
|
+ const masterResObj = {}
|
|
|
+ const themeResObj = {}
|
|
|
+ const diagramResObj = {}
|
|
|
+
|
|
|
+ for (const relationshipArrayItem of relationshipArray) {
|
|
|
+ switch (relationshipArrayItem['attrs']['Type']) {
|
|
|
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout':
|
|
|
+ layoutFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
|
|
|
+ break
|
|
|
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide':
|
|
|
+ noteFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
|
|
|
+ break
|
|
|
+ case 'http://schemas.microsoft.com/office/2007/relationships/diagramDrawing':
|
|
|
+ diagramFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
|
|
|
+ slideResObj[relationshipArrayItem['attrs']['Id']] = {
|
|
|
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
|
|
|
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image':
|
|
|
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart':
|
|
|
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink':
|
|
|
+ default:
|
|
|
+ slideResObj[relationshipArrayItem['attrs']['Id']] = {
|
|
|
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
|
|
|
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const slideNotesContent = await readXmlFile(zip, noteFilename)
|
|
|
+ const note = getNote(slideNotesContent)
|
|
|
+
|
|
|
+ const slideLayoutContent = await readXmlFile(zip, layoutFilename)
|
|
|
+ const slideLayoutTables = await indexNodes(slideLayoutContent)
|
|
|
+ const slideLayoutResFilename = layoutFilename.replace('slideLayouts/slideLayout', 'slideLayouts/_rels/slideLayout') + '.rels'
|
|
|
+ const slideLayoutResContent = await readXmlFile(zip, slideLayoutResFilename)
|
|
|
+ relationshipArray = slideLayoutResContent['Relationships']['Relationship']
|
|
|
+ if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
|
|
|
+
|
|
|
+ for (const relationshipArrayItem of relationshipArray) {
|
|
|
+ switch (relationshipArrayItem['attrs']['Type']) {
|
|
|
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster':
|
|
|
+ masterFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ layoutResObj[relationshipArrayItem['attrs']['Id']] = {
|
|
|
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
|
|
|
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const slideMasterContent = await readXmlFile(zip, masterFilename)
|
|
|
+ const slideMasterTextStyles = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:txStyles'])
|
|
|
+ const slideMasterTables = indexNodes(slideMasterContent)
|
|
|
+ const slideMasterResFilename = masterFilename.replace('slideMasters/slideMaster', 'slideMasters/_rels/slideMaster') + '.rels'
|
|
|
+ const slideMasterResContent = await readXmlFile(zip, slideMasterResFilename)
|
|
|
+ relationshipArray = slideMasterResContent['Relationships']['Relationship']
|
|
|
+ if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
|
|
|
+
|
|
|
+ for (const relationshipArrayItem of relationshipArray) {
|
|
|
+ switch (relationshipArrayItem['attrs']['Type']) {
|
|
|
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
|
|
|
+ themeFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ masterResObj[relationshipArrayItem['attrs']['Id']] = {
|
|
|
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
|
|
|
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (themeFilename) {
|
|
|
+ const themeName = themeFilename.split('/').pop()
|
|
|
+ const themeResFileName = themeFilename.replace(themeName, '_rels/' + themeName) + '.rels'
|
|
|
+ const themeResContent = await readXmlFile(zip, themeResFileName)
|
|
|
+ if (themeResContent) {
|
|
|
+ relationshipArray = themeResContent['Relationships']['Relationship']
|
|
|
+ if (relationshipArray) {
|
|
|
+ if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
|
|
|
+ for (const relationshipArrayItem of relationshipArray) {
|
|
|
+ themeResObj[relationshipArrayItem['attrs']['Id']] = {
|
|
|
+ 'type': relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
|
|
|
+ 'target': relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let digramFileContent = {}
|
|
|
+ if (diagramFilename) {
|
|
|
+ const diagName = diagramFilename.split('/').pop()
|
|
|
+ const diagramResFileName = diagramFilename.replace(diagName, '_rels/' + diagName) + '.rels'
|
|
|
+ digramFileContent = await readXmlFile(zip, diagramFilename)
|
|
|
+ if (digramFileContent) {
|
|
|
+ const digramFileContentObjToStr = JSON.stringify(digramFileContent).replace(/dsp:/g, 'p:')
|
|
|
+ digramFileContent = JSON.parse(digramFileContentObjToStr)
|
|
|
+ }
|
|
|
+ const digramResContent = await readXmlFile(zip, diagramResFileName)
|
|
|
+ if (digramResContent) {
|
|
|
+ relationshipArray = digramResContent['Relationships']['Relationship']
|
|
|
+ if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
|
|
|
+ for (const relationshipArrayItem of relationshipArray) {
|
|
|
+ diagramResObj[relationshipArrayItem['attrs']['Id']] = {
|
|
|
+ 'type': relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
|
|
|
+ 'target': relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const tableStyles = await readXmlFile(zip, 'ppt/tableStyles.xml')
|
|
|
+
|
|
|
+ const slideContent = await readXmlFile(zip, sldFileName)
|
|
|
+ const nodes = slideContent['p:sld']['p:cSld']['p:spTree']
|
|
|
+ const warpObj = {
|
|
|
+ zip,
|
|
|
+ slideLayoutContent,
|
|
|
+ slideLayoutTables,
|
|
|
+ slideMasterContent,
|
|
|
+ slideMasterTables,
|
|
|
+ slideContent,
|
|
|
+ tableStyles,
|
|
|
+ slideResObj,
|
|
|
+ slideMasterTextStyles,
|
|
|
+ layoutResObj,
|
|
|
+ masterResObj,
|
|
|
+ themeContent,
|
|
|
+ themeResObj,
|
|
|
+ digramFileContent,
|
|
|
+ diagramResObj,
|
|
|
+ defaultTextStyle,
|
|
|
+ }
|
|
|
+ const layoutElements = await getLayoutElements(warpObj)
|
|
|
+ const fill = await getSlideBackgroundFill(warpObj)
|
|
|
+
|
|
|
+ const elements = []
|
|
|
+ for (const nodeKey in nodes) {
|
|
|
+ if (nodes[nodeKey].constructor !== Array) nodes[nodeKey] = [nodes[nodeKey]]
|
|
|
+ for (const node of nodes[nodeKey]) {
|
|
|
+ const ret = await processNodesInSlide(nodeKey, node, nodes, warpObj, 'slide')
|
|
|
+ if (ret) elements.push(ret)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ fill,
|
|
|
+ elements,
|
|
|
+ layoutElements,
|
|
|
+ note,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getNote(noteContent) {
|
|
|
+ let text = ''
|
|
|
+ let spNodes = getTextByPathList(noteContent, ['p:notes', 'p:cSld', 'p:spTree', 'p:sp'])
|
|
|
+ if (!spNodes) return ''
|
|
|
+
|
|
|
+ if (spNodes.constructor !== Array) spNodes = [spNodes]
|
|
|
+ for (const spNode of spNodes) {
|
|
|
+ let rNodes = getTextByPathList(spNode, ['p:txBody', 'a:p', 'a:r'])
|
|
|
+ if (!rNodes) continue
|
|
|
+
|
|
|
+ if (rNodes.constructor !== Array) rNodes = [rNodes]
|
|
|
+ for (const rNode of rNodes) {
|
|
|
+ const t = getTextByPathList(rNode, ['a:t'])
|
|
|
+ if (t && typeof t === 'string') text += t
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return text
|
|
|
+}
|
|
|
+
|
|
|
+async function getLayoutElements(warpObj) {
|
|
|
+ const elements = []
|
|
|
+ const slideLayoutContent = warpObj['slideLayoutContent']
|
|
|
+ const slideMasterContent = warpObj['slideMasterContent']
|
|
|
+ const nodesSldLayout = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:cSld', 'p:spTree'])
|
|
|
+ const nodesSldMaster = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:cSld', 'p:spTree'])
|
|
|
+
|
|
|
+ const showMasterSp = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'attrs', 'showMasterSp'])
|
|
|
+ if (nodesSldLayout) {
|
|
|
+ for (const nodeKey in nodesSldLayout) {
|
|
|
+ if (nodesSldLayout[nodeKey].constructor === Array) {
|
|
|
+ for (let i = 0; i < nodesSldLayout[nodeKey].length; i++) {
|
|
|
+ const ph = getTextByPathList(nodesSldLayout[nodeKey][i], ['p:nvSpPr', 'p:nvPr', 'p:ph'])
|
|
|
+ if (!ph) {
|
|
|
+ const ret = await processNodesInSlide(nodeKey, nodesSldLayout[nodeKey][i], nodesSldLayout, warpObj, 'slideLayoutBg')
|
|
|
+ if (ret) elements.push(ret)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const ph = getTextByPathList(nodesSldLayout[nodeKey], ['p:nvSpPr', 'p:nvPr', 'p:ph'])
|
|
|
+ if (!ph) {
|
|
|
+ const ret = await processNodesInSlide(nodeKey, nodesSldLayout[nodeKey], nodesSldLayout, warpObj, 'slideLayoutBg')
|
|
|
+ if (ret) elements.push(ret)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (nodesSldMaster && showMasterSp !== '0') {
|
|
|
+ for (const nodeKey in nodesSldMaster) {
|
|
|
+ if (nodesSldMaster[nodeKey].constructor === Array) {
|
|
|
+ for (let i = 0; i < nodesSldMaster[nodeKey].length; i++) {
|
|
|
+ const ph = getTextByPathList(nodesSldMaster[nodeKey][i], ['p:nvSpPr', 'p:nvPr', 'p:ph'])
|
|
|
+ if (!ph) {
|
|
|
+ const ret = await processNodesInSlide(nodeKey, nodesSldMaster[nodeKey][i], nodesSldMaster, warpObj, 'slideMasterBg')
|
|
|
+ if (ret) elements.push(ret)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const ph = getTextByPathList(nodesSldMaster[nodeKey], ['p:nvSpPr', 'p:nvPr', 'p:ph'])
|
|
|
+ if (!ph) {
|
|
|
+ const ret = await processNodesInSlide(nodeKey, nodesSldMaster[nodeKey], nodesSldMaster, warpObj, 'slideMasterBg')
|
|
|
+ if (ret) elements.push(ret)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return elements
|
|
|
+}
|
|
|
+
|
|
|
+function indexNodes(content) {
|
|
|
+ const keys = Object.keys(content)
|
|
|
+ const spTreeNode = content[keys[0]]['p:cSld']['p:spTree']
|
|
|
+ const idTable = {}
|
|
|
+ const idxTable = {}
|
|
|
+ const typeTable = {}
|
|
|
+
|
|
|
+ for (const key in spTreeNode) {
|
|
|
+ if (key === 'p:nvGrpSpPr' || key === 'p:grpSpPr') continue
|
|
|
+
|
|
|
+ const targetNode = spTreeNode[key]
|
|
|
+
|
|
|
+ if (targetNode.constructor === Array) {
|
|
|
+ for (const targetNodeItem of targetNode) {
|
|
|
+ const nvSpPrNode = targetNodeItem['p:nvSpPr']
|
|
|
+ const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
|
|
|
+ const idx = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'idx'])
|
|
|
+ const type = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'type'])
|
|
|
+
|
|
|
+ if (id) idTable[id] = targetNodeItem
|
|
|
+ if (idx) idxTable[idx] = targetNodeItem
|
|
|
+ if (type) typeTable[type] = targetNodeItem
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const nvSpPrNode = targetNode['p:nvSpPr']
|
|
|
+ const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
|
|
|
+ const idx = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'idx'])
|
|
|
+ const type = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'type'])
|
|
|
+
|
|
|
+ if (id) idTable[id] = targetNode
|
|
|
+ if (idx) idxTable[idx] = targetNode
|
|
|
+ if (type) typeTable[type] = targetNode
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return { idTable, idxTable, typeTable }
|
|
|
+}
|
|
|
+
|
|
|
+async function processNodesInSlide(nodeKey, nodeValue, nodes, warpObj, source) {
|
|
|
+ let json
|
|
|
+
|
|
|
+ switch (nodeKey) {
|
|
|
+ case 'p:sp': // Shape, Text
|
|
|
+ json = await processSpNode(nodeValue, nodes, warpObj, source)
|
|
|
+ break
|
|
|
+ case 'p:cxnSp': // Shape, Text
|
|
|
+ json = await processCxnSpNode(nodeValue, nodes, warpObj, source)
|
|
|
+ break
|
|
|
+ case 'p:pic': // Image, Video, Audio
|
|
|
+ json = await processPicNode(nodeValue, warpObj, source)
|
|
|
+ break
|
|
|
+ case 'p:graphicFrame': // Chart, Diagram, Table
|
|
|
+ json = await processGraphicFrameNode(nodeValue, warpObj, source)
|
|
|
+ break
|
|
|
+ case 'p:grpSp':
|
|
|
+ json = await processGroupSpNode(nodeValue, warpObj, source)
|
|
|
+ break
|
|
|
+ case 'mc:AlternateContent':
|
|
|
+ if (getTextByPathList(nodeValue, ['mc:Fallback', 'p:grpSpPr', 'a:xfrm'])) {
|
|
|
+ json = await processGroupSpNode(getTextByPathList(nodeValue, ['mc:Fallback']), warpObj, source)
|
|
|
+ }
|
|
|
+ else if (getTextByPathList(nodeValue, ['mc:Choice'])) {
|
|
|
+ json = await processMathNode(nodeValue, warpObj, source)
|
|
|
+ }
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ }
|
|
|
+
|
|
|
+ return json
|
|
|
+}
|
|
|
+
|
|
|
+async function processMathNode(node, warpObj, source) {
|
|
|
+ const choice = getTextByPathList(node, ['mc:Choice'])
|
|
|
+ const fallback = getTextByPathList(node, ['mc:Fallback'])
|
|
|
+
|
|
|
+ const order = node['attrs']['order']
|
|
|
+ const xfrmNode = getTextByPathList(choice, ['p:sp', 'p:spPr', 'a:xfrm'])
|
|
|
+ const { top, left } = getPosition(xfrmNode, undefined, undefined)
|
|
|
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
|
+
|
|
|
+ const oMath = findOMath(choice)[0]
|
|
|
+ const latex = latexFormart(parseOMath(oMath))
|
|
|
+
|
|
|
+ const blipFill = getTextByPathList(fallback, ['p:sp', 'p:spPr', 'a:blipFill'])
|
|
|
+ const picBase64 = await getPicFill(source, blipFill, warpObj)
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'math',
|
|
|
+ top,
|
|
|
+ left,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ latex,
|
|
|
+ picBase64,
|
|
|
+ order,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function processGroupSpNode(node, warpObj, source) {
|
|
|
+ const order = node['attrs']['order']
|
|
|
+ const xfrmNode = getTextByPathList(node, ['p:grpSpPr', 'a:xfrm'])
|
|
|
+ if (!xfrmNode) return null
|
|
|
+
|
|
|
+ const x = parseInt(xfrmNode['a:off']['attrs']['x']) * RATIO_EMUs_Points
|
|
|
+ const y = parseInt(xfrmNode['a:off']['attrs']['y']) * RATIO_EMUs_Points
|
|
|
+ const chx = parseInt(xfrmNode['a:chOff']['attrs']['x']) * RATIO_EMUs_Points
|
|
|
+ const chy = parseInt(xfrmNode['a:chOff']['attrs']['y']) * RATIO_EMUs_Points
|
|
|
+ const cx = parseInt(xfrmNode['a:ext']['attrs']['cx']) * RATIO_EMUs_Points
|
|
|
+ const cy = parseInt(xfrmNode['a:ext']['attrs']['cy']) * RATIO_EMUs_Points
|
|
|
+ const chcx = parseInt(xfrmNode['a:chExt']['attrs']['cx']) * RATIO_EMUs_Points
|
|
|
+ const chcy = parseInt(xfrmNode['a:chExt']['attrs']['cy']) * RATIO_EMUs_Points
|
|
|
+
|
|
|
+ const isFlipV = getTextByPathList(xfrmNode, ['attrs', 'flipV']) === '1'
|
|
|
+ const isFlipH = getTextByPathList(xfrmNode, ['attrs', 'flipH']) === '1'
|
|
|
+
|
|
|
+ let rotate = getTextByPathList(xfrmNode, ['attrs', 'rot']) || 0
|
|
|
+ if (rotate) rotate = angleToDegrees(rotate)
|
|
|
+
|
|
|
+ const ws = cx / chcx
|
|
|
+ const hs = cy / chcy
|
|
|
+
|
|
|
+
|
|
|
+ const elements = []
|
|
|
+ for (const nodeKey in node) {
|
|
|
+ if (node[nodeKey].constructor === Array) {
|
|
|
+ for (const item of node[nodeKey]) {
|
|
|
+ const ret = await processNodesInSlide(nodeKey, item, node, warpObj, source)
|
|
|
+ if (ret) {
|
|
|
+ // 如果是嵌套的组合,需要调整其子元素的位置和大小
|
|
|
+ if (ret.type === 'group') {
|
|
|
+ ret.elements = ret.elements.map(element => ({
|
|
|
+ ...element,
|
|
|
+ left: element.left * ws,
|
|
|
+ top: element.top * hs,
|
|
|
+ width: element.width * ws,
|
|
|
+ height: element.height * hs,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ elements.push(ret)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const ret = await processNodesInSlide(nodeKey, node[nodeKey], node, warpObj, source)
|
|
|
+ if (ret) {
|
|
|
+ // 如果是嵌套的组合,需要调整其子元素的位置和大小
|
|
|
+ if (ret.type === 'group') {
|
|
|
+ ret.elements = ret.elements.map(element => ({
|
|
|
+ ...element,
|
|
|
+ left: element.left * ws,
|
|
|
+ top: element.top * hs,
|
|
|
+ width: element.width * ws,
|
|
|
+ height: element.height * hs,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ elements.push(ret)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'group',
|
|
|
+ top: y,
|
|
|
+ left: x,
|
|
|
+ width: cx,
|
|
|
+ height: cy,
|
|
|
+ rotate,
|
|
|
+ order,
|
|
|
+ isFlipV,
|
|
|
+ isFlipH,
|
|
|
+ elements: elements.map(element => ({
|
|
|
+ ...element,
|
|
|
+ left: (element.left - chx) * ws,
|
|
|
+ top: (element.top - chy) * hs,
|
|
|
+ width: element.width * ws,
|
|
|
+ height: element.height * hs,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function processSpNode(node, pNode, warpObj, source) {
|
|
|
+ const name = getTextByPathList(node, ['p:nvSpPr', 'p:cNvPr', 'attrs', 'name'])
|
|
|
+ const idx = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'idx'])
|
|
|
+ let type = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
|
|
|
+ const order = getTextByPathList(node, ['attrs', 'order'])
|
|
|
+
|
|
|
+ let slideLayoutSpNode, slideMasterSpNode
|
|
|
+
|
|
|
+ if (type) {
|
|
|
+ if (idx) {
|
|
|
+ slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
|
|
|
+ slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
|
|
|
+ slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (idx) {
|
|
|
+ slideLayoutSpNode = warpObj['slideLayoutTables']['idxTable'][idx]
|
|
|
+ slideMasterSpNode = warpObj['slideMasterTables']['idxTable'][idx]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!type) {
|
|
|
+ const txBoxVal = getTextByPathList(node, ['p:nvSpPr', 'p:cNvSpPr', 'attrs', 'txBox'])
|
|
|
+ if (txBoxVal === '1') type = 'text'
|
|
|
+ }
|
|
|
+ if (!type) type = getTextByPathList(slideLayoutSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
|
|
|
+ if (!type) type = getTextByPathList(slideMasterSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
|
|
|
+
|
|
|
+ if (!type) {
|
|
|
+ if (source === 'diagramBg') type = 'diagram'
|
|
|
+ else type = 'obj'
|
|
|
+ }
|
|
|
+
|
|
|
+ return await genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source)
|
|
|
+}
|
|
|
+
|
|
|
+async function processCxnSpNode(node, pNode, warpObj, source) {
|
|
|
+ const name = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['name']
|
|
|
+ const type = (node['p:nvCxnSpPr']['p:nvPr']['p:ph'] === undefined) ? undefined : node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['type']
|
|
|
+ const order = node['attrs']['order']
|
|
|
+
|
|
|
+ return await genShape(node, pNode, undefined, undefined, name, type, order, warpObj, source)
|
|
|
+}
|
|
|
+
|
|
|
+async function genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source) {
|
|
|
+ const xfrmList = ['p:spPr', 'a:xfrm']
|
|
|
+ const slideXfrmNode = getTextByPathList(node, xfrmList)
|
|
|
+ const slideLayoutXfrmNode = getTextByPathList(slideLayoutSpNode, xfrmList)
|
|
|
+ const slideMasterXfrmNode = getTextByPathList(slideMasterSpNode, xfrmList)
|
|
|
+
|
|
|
+ const shapType = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst'])
|
|
|
+ const custShapType = getTextByPathList(node, ['p:spPr', 'a:custGeom'])
|
|
|
+
|
|
|
+ const { top, left } = getPosition(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
|
|
|
+ const { width, height } = getSize(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
|
|
|
+
|
|
|
+ const isFlipV = getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1'
|
|
|
+ const isFlipH = getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1'
|
|
|
+
|
|
|
+ const rotate = angleToDegrees(getTextByPathList(slideXfrmNode, ['attrs', 'rot']))
|
|
|
+
|
|
|
+ const txtXframeNode = getTextByPathList(node, ['p:txXfrm'])
|
|
|
+ let txtRotate
|
|
|
+ if (txtXframeNode) {
|
|
|
+ const txtXframeRot = getTextByPathList(txtXframeNode, ['attrs', 'rot'])
|
|
|
+ if (txtXframeRot) txtRotate = angleToDegrees(txtXframeRot) + 90
|
|
|
+ }
|
|
|
+ else txtRotate = rotate
|
|
|
+
|
|
|
+ let content = ''
|
|
|
+ if (node['p:txBody']) content = genTextBody(node['p:txBody'], node, slideLayoutSpNode, type, warpObj)
|
|
|
+
|
|
|
+ const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
|
|
|
+ const fill = await getShapeFill(node, pNode, undefined, warpObj, source) || ''
|
|
|
+
|
|
|
+ let shadow
|
|
|
+ const outerShdwNode = getTextByPathList(node, ['p:spPr', 'a:effectLst', 'a:outerShdw'])
|
|
|
+ if (outerShdwNode) shadow = getShadow(outerShdwNode, warpObj)
|
|
|
+
|
|
|
+ const vAlign = getVerticalAlign(node, slideLayoutSpNode, slideMasterSpNode, type)
|
|
|
+ const isVertical = getTextByPathList(node, ['p:txBody', 'a:bodyPr', 'attrs', 'vert']) === 'eaVert'
|
|
|
+
|
|
|
+ const data = {
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ borderColor,
|
|
|
+ borderWidth,
|
|
|
+ borderType,
|
|
|
+ borderStrokeDasharray: strokeDasharray,
|
|
|
+ fill,
|
|
|
+ content,
|
|
|
+ isFlipV,
|
|
|
+ isFlipH,
|
|
|
+ rotate,
|
|
|
+ vAlign,
|
|
|
+ name,
|
|
|
+ order,
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shadow) data.shadow = shadow
|
|
|
+
|
|
|
+ if (custShapType && type !== 'diagram') {
|
|
|
+ const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
|
|
|
+ const w = parseInt(ext['cx']) * RATIO_EMUs_Points
|
|
|
+ const h = parseInt(ext['cy']) * RATIO_EMUs_Points
|
|
|
+ const d = getCustomShapePath(custShapType, w, h)
|
|
|
+ if (data.content && !hasValidText(data.content)) data.content = ''
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...data,
|
|
|
+ type: 'shape',
|
|
|
+ shapType: 'custom',
|
|
|
+ path: d,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (shapType && (type === 'obj' || !type)) {
|
|
|
+ if (data.content && !hasValidText(data.content)) data.content = ''
|
|
|
+ return {
|
|
|
+ ...data,
|
|
|
+ type: 'shape',
|
|
|
+ shapType,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ ...data,
|
|
|
+ type: 'text',
|
|
|
+ isVertical,
|
|
|
+ rotate: txtRotate,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function processPicNode(node, warpObj, source) {
|
|
|
+ let resObj
|
|
|
+ if (source === 'slideMasterBg') resObj = warpObj['masterResObj']
|
|
|
+ else if (source === 'slideLayoutBg') resObj = warpObj['layoutResObj']
|
|
|
+ else resObj = warpObj['slideResObj']
|
|
|
+
|
|
|
+ const order = node['attrs']['order']
|
|
|
+
|
|
|
+ const rid = node['p:blipFill']['a:blip']['attrs']['r:embed']
|
|
|
+ const imgName = resObj[rid]['target']
|
|
|
+ const imgFileExt = extractFileExtension(imgName).toLowerCase()
|
|
|
+ const zip = warpObj['zip']
|
|
|
+ const imgArrayBuffer = await zip.file(imgName).async('arraybuffer')
|
|
|
+ const xfrmNode = node['p:spPr']['a:xfrm']
|
|
|
+
|
|
|
+ const mimeType = getMimeType(imgFileExt)
|
|
|
+ const { top, left } = getPosition(xfrmNode, undefined, undefined)
|
|
|
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
|
+ const src = `data:${mimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
|
|
|
+
|
|
|
+ const isFlipV = getTextByPathList(xfrmNode, ['attrs', 'flipV']) === '1'
|
|
|
+ const isFlipH = getTextByPathList(xfrmNode, ['attrs', 'flipH']) === '1'
|
|
|
+
|
|
|
+ let rotate = 0
|
|
|
+ const rotateNode = getTextByPathList(node, ['p:spPr', 'a:xfrm', 'attrs', 'rot'])
|
|
|
+ if (rotateNode) rotate = angleToDegrees(rotateNode)
|
|
|
+
|
|
|
+ const videoNode = getTextByPathList(node, ['p:nvPicPr', 'p:nvPr', 'a:videoFile'])
|
|
|
+ let videoRid, videoFile, videoFileExt, videoMimeType, uInt8ArrayVideo, videoBlob
|
|
|
+ let isVdeoLink = false
|
|
|
+
|
|
|
+ if (videoNode) {
|
|
|
+ videoRid = videoNode['attrs']['r:link']
|
|
|
+ videoFile = resObj[videoRid]['target']
|
|
|
+ if (isVideoLink(videoFile)) {
|
|
|
+ videoFile = escapeHtml(videoFile)
|
|
|
+ isVdeoLink = true
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ videoFileExt = extractFileExtension(videoFile).toLowerCase()
|
|
|
+ if (videoFileExt === 'mp4' || videoFileExt === 'webm' || videoFileExt === 'ogg') {
|
|
|
+ uInt8ArrayVideo = await zip.file(videoFile).async('arraybuffer')
|
|
|
+ videoMimeType = getMimeType(videoFileExt)
|
|
|
+ videoBlob = URL.createObjectURL(new Blob([uInt8ArrayVideo], {
|
|
|
+ type: videoMimeType
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const audioNode = getTextByPathList(node, ['p:nvPicPr', 'p:nvPr', 'a:audioFile'])
|
|
|
+ let audioRid, audioFile, audioFileExt, uInt8ArrayAudio, audioBlob
|
|
|
+ if (audioNode) {
|
|
|
+ audioRid = audioNode['attrs']['r:link']
|
|
|
+ audioFile = resObj[audioRid]['target']
|
|
|
+ audioFileExt = extractFileExtension(audioFile).toLowerCase()
|
|
|
+ if (audioFileExt === 'mp3' || audioFileExt === 'wav' || audioFileExt === 'ogg') {
|
|
|
+ uInt8ArrayAudio = await zip.file(audioFile).async('arraybuffer')
|
|
|
+ audioBlob = URL.createObjectURL(new Blob([uInt8ArrayAudio]))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (videoNode && !isVdeoLink) {
|
|
|
+ return {
|
|
|
+ type: 'video',
|
|
|
+ top,
|
|
|
+ left,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ rotate,
|
|
|
+ blob: videoBlob,
|
|
|
+ order,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (videoNode && isVdeoLink) {
|
|
|
+ return {
|
|
|
+ type: 'video',
|
|
|
+ top,
|
|
|
+ left,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ rotate,
|
|
|
+ src: videoFile,
|
|
|
+ order,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (audioNode) {
|
|
|
+ return {
|
|
|
+ type: 'audio',
|
|
|
+ top,
|
|
|
+ left,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ rotate,
|
|
|
+ blob: audioBlob,
|
|
|
+ order,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let rect
|
|
|
+ const srcRectAttrs = getTextByPathList(node, ['p:blipFill', 'a:srcRect', 'attrs'])
|
|
|
+ if (srcRectAttrs && (srcRectAttrs.t || srcRectAttrs.b || srcRectAttrs.l || srcRectAttrs.r)) {
|
|
|
+ rect = {}
|
|
|
+ if (srcRectAttrs.t) rect.t = srcRectAttrs.t / 1000
|
|
|
+ if (srcRectAttrs.b) rect.b = srcRectAttrs.b / 1000
|
|
|
+ if (srcRectAttrs.l) rect.l = srcRectAttrs.l / 1000
|
|
|
+ if (srcRectAttrs.r) rect.r = srcRectAttrs.r / 1000
|
|
|
+ }
|
|
|
+ const geom = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst']) || 'rect'
|
|
|
+
|
|
|
+ const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, undefined, warpObj)
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'image',
|
|
|
+ top,
|
|
|
+ left,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ rotate,
|
|
|
+ src,
|
|
|
+ isFlipV,
|
|
|
+ isFlipH,
|
|
|
+ order,
|
|
|
+ rect,
|
|
|
+ geom,
|
|
|
+ borderColor,
|
|
|
+ borderWidth,
|
|
|
+ borderType,
|
|
|
+ borderStrokeDasharray: strokeDasharray,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function processGraphicFrameNode(node, warpObj, source) {
|
|
|
+ const graphicTypeUri = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'attrs', 'uri'])
|
|
|
+
|
|
|
+ let result
|
|
|
+ switch (graphicTypeUri) {
|
|
|
+ case 'http://schemas.openxmlformats.org/drawingml/2006/table':
|
|
|
+ result = await genTable(node, warpObj)
|
|
|
+ break
|
|
|
+ case 'http://schemas.openxmlformats.org/drawingml/2006/chart':
|
|
|
+ result = await genChart(node, warpObj)
|
|
|
+ break
|
|
|
+ case 'http://schemas.openxmlformats.org/drawingml/2006/diagram':
|
|
|
+ result = await genDiagram(node, warpObj)
|
|
|
+ break
|
|
|
+ case 'http://schemas.openxmlformats.org/presentationml/2006/ole':
|
|
|
+ let oleObjNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'mc:AlternateContent', 'mc:Fallback', 'p:oleObj'])
|
|
|
+ if (!oleObjNode) oleObjNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'p:oleObj'])
|
|
|
+ if (oleObjNode) result = await processGroupSpNode(oleObjNode, warpObj, source)
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+async function genTable(node, warpObj) {
|
|
|
+ const order = node['attrs']['order']
|
|
|
+ const tableNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl'])
|
|
|
+ const xfrmNode = getTextByPathList(node, ['p:xfrm'])
|
|
|
+ const { top, left } = getPosition(xfrmNode, undefined, undefined)
|
|
|
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
|
+
|
|
|
+ const getTblPr = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl', 'a:tblPr'])
|
|
|
+ let getColsGrid = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl', 'a:tblGrid', 'a:gridCol'])
|
|
|
+ if (getColsGrid.constructor !== Array) getColsGrid = [getColsGrid]
|
|
|
+
|
|
|
+ const colWidths = []
|
|
|
+ if (getColsGrid) {
|
|
|
+ for (const item of getColsGrid) {
|
|
|
+ const colWidthParam = getTextByPathList(item, ['attrs', 'w']) || 0
|
|
|
+ const colWidth = parseInt(colWidthParam) * RATIO_EMUs_Points
|
|
|
+ colWidths.push(colWidth)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const firstRowAttr = getTblPr['attrs'] ? getTblPr['attrs']['firstRow'] : undefined
|
|
|
+ const firstColAttr = getTblPr['attrs'] ? getTblPr['attrs']['firstCol'] : undefined
|
|
|
+ const lastRowAttr = getTblPr['attrs'] ? getTblPr['attrs']['lastRow'] : undefined
|
|
|
+ const lastColAttr = getTblPr['attrs'] ? getTblPr['attrs']['lastCol'] : undefined
|
|
|
+ const bandRowAttr = getTblPr['attrs'] ? getTblPr['attrs']['bandRow'] : undefined
|
|
|
+ const bandColAttr = getTblPr['attrs'] ? getTblPr['attrs']['bandCol'] : undefined
|
|
|
+ const tblStylAttrObj = {
|
|
|
+ isFrstRowAttr: (firstRowAttr && firstRowAttr === '1') ? 1 : 0,
|
|
|
+ isFrstColAttr: (firstColAttr && firstColAttr === '1') ? 1 : 0,
|
|
|
+ isLstRowAttr: (lastRowAttr && lastRowAttr === '1') ? 1 : 0,
|
|
|
+ isLstColAttr: (lastColAttr && lastColAttr === '1') ? 1 : 0,
|
|
|
+ isBandRowAttr: (bandRowAttr && bandRowAttr === '1') ? 1 : 0,
|
|
|
+ isBandColAttr: (bandColAttr && bandColAttr === '1') ? 1 : 0,
|
|
|
+ }
|
|
|
+
|
|
|
+ let thisTblStyle
|
|
|
+ const tbleStyleId = getTblPr['a:tableStyleId']
|
|
|
+ if (tbleStyleId) {
|
|
|
+ const tbleStylList = warpObj['tableStyles']['a:tblStyleLst']['a:tblStyle']
|
|
|
+ if (tbleStylList) {
|
|
|
+ if (tbleStylList.constructor === Array) {
|
|
|
+ for (let k = 0; k < tbleStylList.length; k++) {
|
|
|
+ if (tbleStylList[k]['attrs']['styleId'] === tbleStyleId) {
|
|
|
+ thisTblStyle = tbleStylList[k]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if (tbleStylList['attrs']['styleId'] === tbleStyleId) {
|
|
|
+ thisTblStyle = tbleStylList
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (thisTblStyle) thisTblStyle['tblStylAttrObj'] = tblStylAttrObj
|
|
|
+
|
|
|
+ let borders = {}
|
|
|
+ const tblStyl = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle'])
|
|
|
+ const tblBorderStyl = getTextByPathList(tblStyl, ['a:tcBdr'])
|
|
|
+ if (tblBorderStyl) borders = getTableBorders(tblBorderStyl, warpObj)
|
|
|
+
|
|
|
+ let tbl_bgcolor = ''
|
|
|
+ let tbl_bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:tblBg', 'a:fillRef'])
|
|
|
+ if (tbl_bgFillschemeClr) {
|
|
|
+ tbl_bgcolor = getSolidFill(tbl_bgFillschemeClr, undefined, undefined, warpObj)
|
|
|
+ }
|
|
|
+ if (tbl_bgFillschemeClr === undefined) {
|
|
|
+ tbl_bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle', 'a:fill', 'a:solidFill'])
|
|
|
+ tbl_bgcolor = getSolidFill(tbl_bgFillschemeClr, undefined, undefined, warpObj)
|
|
|
+ }
|
|
|
+
|
|
|
+ let trNodes = tableNode['a:tr']
|
|
|
+ if (trNodes.constructor !== Array) trNodes = [trNodes]
|
|
|
+
|
|
|
+ const data = []
|
|
|
+ const rowHeights = []
|
|
|
+ for (let i = 0; i < trNodes.length; i++) {
|
|
|
+ const trNode = trNodes[i]
|
|
|
+
|
|
|
+ const rowHeightParam = getTextByPathList(trNodes[i], ['attrs', 'h']) || 0
|
|
|
+ const rowHeight = parseInt(rowHeightParam) * RATIO_EMUs_Points
|
|
|
+ rowHeights.push(rowHeight)
|
|
|
+
|
|
|
+ const {
|
|
|
+ fillColor,
|
|
|
+ fontColor,
|
|
|
+ fontBold,
|
|
|
+ } = getTableRowParams(trNodes, i, tblStylAttrObj, thisTblStyle, warpObj)
|
|
|
+
|
|
|
+ const tcNodes = trNode['a:tc']
|
|
|
+ const tr = []
|
|
|
+
|
|
|
+ if (tcNodes.constructor === Array) {
|
|
|
+ for (let j = 0; j < tcNodes.length; j++) {
|
|
|
+ const tcNode = tcNodes[j]
|
|
|
+ let a_sorce
|
|
|
+ if (j === 0 && tblStylAttrObj['isFrstColAttr'] === 1) {
|
|
|
+ a_sorce = 'a:firstCol'
|
|
|
+ if (tblStylAttrObj['isLstRowAttr'] === 1 && i === (trNodes.length - 1) && getTextByPathList(thisTblStyle, ['a:seCell'])) {
|
|
|
+ a_sorce = 'a:seCell'
|
|
|
+ }
|
|
|
+ else if (tblStylAttrObj['isFrstRowAttr'] === 1 && i === 0 &&
|
|
|
+ getTextByPathList(thisTblStyle, ['a:neCell'])) {
|
|
|
+ a_sorce = 'a:neCell'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (
|
|
|
+ (j > 0 && tblStylAttrObj['isBandColAttr'] === 1) &&
|
|
|
+ !(tblStylAttrObj['isFrstColAttr'] === 1 && i === 0) &&
|
|
|
+ !(tblStylAttrObj['isLstRowAttr'] === 1 && i === (trNodes.length - 1)) &&
|
|
|
+ j !== (tcNodes.length - 1)
|
|
|
+ ) {
|
|
|
+ if ((j % 2) !== 0) {
|
|
|
+ let aBandNode = getTextByPathList(thisTblStyle, ['a:band2V'])
|
|
|
+ if (aBandNode === undefined) {
|
|
|
+ aBandNode = getTextByPathList(thisTblStyle, ['a:band1V'])
|
|
|
+ if (aBandNode) a_sorce = 'a:band2V'
|
|
|
+ }
|
|
|
+ else a_sorce = 'a:band2V'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (j === (tcNodes.length - 1) && tblStylAttrObj['isLstColAttr'] === 1) {
|
|
|
+ a_sorce = 'a:lastCol'
|
|
|
+ if (tblStylAttrObj['isLstRowAttr'] === 1 && i === (trNodes.length - 1) && getTextByPathList(thisTblStyle, ['a:swCell'])) {
|
|
|
+ a_sorce = 'a:swCell'
|
|
|
+ }
|
|
|
+ else if (tblStylAttrObj['isFrstRowAttr'] === 1 && i === 0 && getTextByPathList(thisTblStyle, ['a:nwCell'])) {
|
|
|
+ a_sorce = 'a:nwCell'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const text = genTextBody(tcNode['a:txBody'], tcNode, undefined, undefined, warpObj)
|
|
|
+ const cell = await getTableCellParams(tcNode, thisTblStyle, a_sorce, warpObj)
|
|
|
+ const td = { text }
|
|
|
+ if (cell.rowSpan) td.rowSpan = cell.rowSpan
|
|
|
+ if (cell.colSpan) td.colSpan = cell.colSpan
|
|
|
+ if (cell.vMerge) td.vMerge = cell.vMerge
|
|
|
+ if (cell.hMerge) td.hMerge = cell.hMerge
|
|
|
+ if (cell.fontBold || fontBold) td.fontBold = cell.fontBold || fontBold
|
|
|
+ if (cell.fontColor || fontColor) td.fontColor = cell.fontColor || fontColor
|
|
|
+ if (cell.fillColor || fillColor || tbl_bgcolor) td.fillColor = cell.fillColor || fillColor || tbl_bgcolor
|
|
|
+ if (cell.borders) td.borders = cell.borders
|
|
|
+
|
|
|
+ tr.push(td)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ let a_sorce
|
|
|
+ if (tblStylAttrObj['isFrstColAttr'] === 1 && tblStylAttrObj['isLstRowAttr'] !== 1) {
|
|
|
+ a_sorce = 'a:firstCol'
|
|
|
+ }
|
|
|
+ else if (tblStylAttrObj['isBandColAttr'] === 1 && tblStylAttrObj['isLstRowAttr'] !== 1) {
|
|
|
+ let aBandNode = getTextByPathList(thisTblStyle, ['a:band2V'])
|
|
|
+ if (!aBandNode) {
|
|
|
+ aBandNode = getTextByPathList(thisTblStyle, ['a:band1V'])
|
|
|
+ if (aBandNode) a_sorce = 'a:band2V'
|
|
|
+ }
|
|
|
+ else a_sorce = 'a:band2V'
|
|
|
+ }
|
|
|
+ if (tblStylAttrObj['isLstColAttr'] === 1 && tblStylAttrObj['isLstRowAttr'] !== 1) {
|
|
|
+ a_sorce = 'a:lastCol'
|
|
|
+ }
|
|
|
+
|
|
|
+ const text = genTextBody(tcNodes['a:txBody'], tcNodes, undefined, undefined, warpObj)
|
|
|
+ const cell = await getTableCellParams(tcNodes, thisTblStyle, a_sorce, warpObj)
|
|
|
+ const td = { text }
|
|
|
+ if (cell.rowSpan) td.rowSpan = cell.rowSpan
|
|
|
+ if (cell.colSpan) td.colSpan = cell.colSpan
|
|
|
+ if (cell.vMerge) td.vMerge = cell.vMerge
|
|
|
+ if (cell.hMerge) td.hMerge = cell.hMerge
|
|
|
+ if (cell.fontBold || fontBold) td.fontBold = cell.fontBold || fontBold
|
|
|
+ if (cell.fontColor || fontColor) td.fontColor = cell.fontColor || fontColor
|
|
|
+ if (cell.fillColor || fillColor || tbl_bgcolor) td.fillColor = cell.fillColor || fillColor || tbl_bgcolor
|
|
|
+ if (cell.borders) td.borders = cell.borders
|
|
|
+
|
|
|
+ tr.push(td)
|
|
|
+ }
|
|
|
+ data.push(tr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'table',
|
|
|
+ top,
|
|
|
+ left,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ data,
|
|
|
+ order,
|
|
|
+ borders,
|
|
|
+ rowHeights,
|
|
|
+ colWidths,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function genChart(node, warpObj) {
|
|
|
+ const order = node['attrs']['order']
|
|
|
+ const xfrmNode = getTextByPathList(node, ['p:xfrm'])
|
|
|
+ const { top, left } = getPosition(xfrmNode, undefined, undefined)
|
|
|
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
|
+
|
|
|
+ const rid = node['a:graphic']['a:graphicData']['c:chart']['attrs']['r:id']
|
|
|
+ let refName = getTextByPathList(warpObj['slideResObj'], [rid, 'target'])
|
|
|
+ if (!refName) refName = getTextByPathList(warpObj['layoutResObj'], [rid, 'target'])
|
|
|
+ if (!refName) refName = getTextByPathList(warpObj['masterResObj'], [rid, 'target'])
|
|
|
+ if (!refName) return {}
|
|
|
+
|
|
|
+ const content = await readXmlFile(warpObj['zip'], refName)
|
|
|
+ const plotArea = getTextByPathList(content, ['c:chartSpace', 'c:chart', 'c:plotArea'])
|
|
|
+
|
|
|
+ const chart = getChartInfo(plotArea, warpObj)
|
|
|
+
|
|
|
+ if (!chart) return {}
|
|
|
+
|
|
|
+ const data = {
|
|
|
+ type: 'chart',
|
|
|
+ top,
|
|
|
+ left,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ data: chart.data,
|
|
|
+ colors: chart.colors,
|
|
|
+ chartType: chart.type,
|
|
|
+ order,
|
|
|
+ }
|
|
|
+ if (chart.marker !== undefined) data.marker = chart.marker
|
|
|
+ if (chart.barDir !== undefined) data.barDir = chart.barDir
|
|
|
+ if (chart.holeSize !== undefined) data.holeSize = chart.holeSize
|
|
|
+ if (chart.grouping !== undefined) data.grouping = chart.grouping
|
|
|
+ if (chart.style !== undefined) data.style = chart.style
|
|
|
+
|
|
|
+ return data
|
|
|
+}
|
|
|
+
|
|
|
+async function genDiagram(node, warpObj) {
|
|
|
+ const order = node['attrs']['order']
|
|
|
+ const xfrmNode = getTextByPathList(node, ['p:xfrm'])
|
|
|
+ const { left, top } = getPosition(xfrmNode, undefined, undefined)
|
|
|
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
|
|
|
+
|
|
|
+ const dgmDrwSpArray = getTextByPathList(warpObj['digramFileContent'], ['p:drawing', 'p:spTree', 'p:sp'])
|
|
|
+ const elements = []
|
|
|
+ if (dgmDrwSpArray) {
|
|
|
+ for (const item of dgmDrwSpArray) {
|
|
|
+ const el = await processSpNode(item, node, warpObj, 'diagramBg')
|
|
|
+ if (el) elements.push(el)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'diagram',
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ elements,
|
|
|
+ order,
|
|
|
+ }
|
|
|
+}
|