Files
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

1327 lines
47 KiB
JavaScript

import JSZip from 'jszip'
import { readXmlFile } from './readXmlFile'
import { getBorder } from './border'
import { getSlideBackgroundFill, getShapeFill, getSolidFill, getPicFill, getPicFilters } from './fill'
import { getChartInfo } from './chart'
import { getVerticalAlign, getTextAutoFit } from './align'
import { getPosition, getSize } from './position'
import { genTextBody } from './text'
import { getCustomShapePath, identifyShape } from './shape'
import { extractFileExtension, base64ArrayBuffer, getTextByPathList, angleToDegrees, getMimeType, isVideoLink, escapeHtml, hasValidText, numberToFixed } from './utils'
import { getShadow } from './shadow'
import { getTableBorders, getTableCellParams, getTableRowParams } from './table'
import { RATIO_EMUs_Points } from './constants'
import { findOMath, latexFormart, parseOMath } from './math'
import { getShapePath } from './shapePath'
import { parseTransition, findTransitionNode } from './animation'
import { getSmartArtTextData } from './diagram'
/**
* @param {ArrayBuffer} file
* @param {{ mediaMode?: 'base64' | 'blob' }} [options]
*/
function createBlobUrlSafe(arrayBuffer, mimeType) {
if (typeof URL !== 'undefined' && typeof Blob !== 'undefined' && URL.createObjectURL) {
return URL.createObjectURL(new Blob([arrayBuffer], { type: mimeType }))
}
const bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : new Uint8Array(arrayBuffer)
let binary = ''
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i])
return `data:${mimeType};base64,${btoa(binary)}`
}
export async function parse(file, options) {
const mediaMode = options?.mediaMode ?? 'base64'
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, mediaMode)
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, mediaMode = 'base64') {
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 diagramFiles = {}
const slideResObj = {}
const layoutResObj = {}
const masterResObj = {}
const themeResObj = {}
const diagramResObj = {}
for (const relationshipArrayItem of relationshipArray) {
const relType = relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', '')
let relTarget = relationshipArrayItem['attrs']['Target']
const isExternal = relationshipArrayItem['attrs']['TargetMode'] === 'External'
if (!isExternal) {
if (relTarget.indexOf('../') !== -1) relTarget = relTarget.replace('../', 'ppt/')
else relTarget = 'ppt/slides/' + relTarget
}
switch (relationshipArrayItem['attrs']['Type']) {
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout':
layoutFilename = relTarget
slideResObj[relationshipArrayItem['attrs']['Id']] = {
type: relType,
target: relTarget
}
break
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide':
noteFilename = relTarget
slideResObj[relationshipArrayItem['attrs']['Id']] = {
type: relType,
target: relTarget
}
break
case 'http://schemas.microsoft.com/office/2007/relationships/diagramDrawing':
diagramFilename = relTarget
slideResObj[relationshipArrayItem['attrs']['Id']] = {
type: relType,
target: relTarget
}
break
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData':
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout':
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQuickStyle':
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors':
diagramFiles[relationshipArrayItem['attrs']['Id']] = relTarget
slideResObj[relationshipArrayItem['attrs']['Id']] = {
type: relType,
target: relTarget
}
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: relType,
target: relTarget,
}
}
}
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) {
const relType = relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', '')
let relTarget = relationshipArrayItem['attrs']['Target']
if (relTarget.indexOf('../') !== -1) relTarget = relTarget.replace('../', 'ppt/')
else relTarget = 'ppt/slideLayouts/' + relTarget
switch (relationshipArrayItem['attrs']['Type']) {
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster':
masterFilename = relTarget
break
default:
layoutResObj[relationshipArrayItem['attrs']['Id']] = {
type: relType,
target: relTarget,
}
}
}
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) {
const relType = relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', '')
let relTarget = relationshipArrayItem['attrs']['Target']
if (relTarget.indexOf('../') !== -1) relTarget = relTarget.replace('../', 'ppt/')
else relTarget = 'ppt/slideMasters/' + relTarget
switch (relationshipArrayItem['attrs']['Type']) {
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
themeFilename = relTarget
break
default:
masterResObj[relationshipArrayItem['attrs']['Id']] = {
type: relType,
target: relTarget,
}
}
}
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/')
}
}
}
}
}
const diagramContent = {
data: null,
layout: null,
quickStyle: null,
colors: null,
drawing: null
}
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/')
}
}
}
}
if (Object.values(diagramFiles).length > 0) {
for (const filePath of Object.values(diagramFiles)) {
const content = await readXmlFile(zip, filePath)
if (filePath.includes('/data')) diagramContent.data = content
else if (filePath.includes('/layout')) diagramContent.layout = content
else if (filePath.includes('/quickStyle')) diagramContent.quickStyle = content
else if (filePath.includes('/colors')) diagramContent.colors = content
}
}
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,
diagramContent,
defaultTextStyle,
mediaMode,
}
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, warpObj, 'slide')
if (ret) elements.push(ret)
}
}
let transitionNode = findTransitionNode(slideContent, 'p:sld')
if (!transitionNode) transitionNode = findTransitionNode(slideLayoutContent, 'p:sldLayout')
if (!transitionNode) transitionNode = findTransitionNode(slideMasterContent, 'p:sldMaster')
const transition = parseTransition(transitionNode)
return {
fill,
elements,
layoutElements,
note,
transition,
}
}
function getHyperlinkFromCNvPr(cNvPr, warpObj) {
const hlinkClick = getTextByPathList(cNvPr, ['a:hlinkClick', 'attrs'])
if (!hlinkClick) return null
const linkId = hlinkClick['r:id']
if (!linkId) return null
const res = warpObj['slideResObj'][linkId]
if (!res) return null
if (res['type'] !== 'hyperlink') return null
const target = res['target']
if (!target || !/^https?:\/\//.test(target)) return null
return target
}
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) {
const phType = getTextByPathList(spNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
if (phType !== 'body') continue
const textBody = getTextByPathList(spNode, ['p:txBody'])
if (!textBody) continue
let pNode = textBody['a:p']
if (!pNode) continue
if (pNode.constructor !== Array) pNode = [pNode]
const listTypes = []
for (const p of pNode) {
const pPr = p['a:pPr']
const algn = getTextByPathList(pPr, ['attrs', 'algn'])
let align = 'left'
if (algn) {
switch (algn) {
case 'r': align = 'right'; break
case 'ctr': align = 'center'; break
case 'just': case 'dist': align = 'justify'; break
default: break
}
}
let listType = ''
if (pPr) {
if (pPr['a:buChar']) listType = 'ul'
else if (pPr['a:buAutoNum']) listType = 'ol'
}
const lvlNode = getTextByPathList(pPr, ['attrs', 'lvl'])
const listLevel = lvlNode !== undefined ? parseInt(lvlNode) : 0
if (listType) {
while (listTypes.length > listLevel + 1) {
text += `</${listTypes.pop()}>`
}
if (listTypes[listLevel] === undefined) {
text += `<${listType}>`
listTypes[listLevel] = listType
}
else if (listTypes[listLevel] !== listType) {
text += `</${listTypes[listLevel]}>`
text += `<${listType}>`
listTypes[listLevel] = listType
}
text += `<li style="text-align:${align};">`
}
else {
while (listTypes.length > 0) {
text += `</${listTypes.pop()}>`
}
text += `<p style="text-align:${align};">`
}
let rNodes = p['a:r']
if (rNodes) {
if (rNodes.constructor !== Array) rNodes = [rNodes]
for (const r of rNodes) {
const t = getTextByPathList(r, ['a:t'])
if (t && typeof t === 'string') text += t
}
}
if (listType) text += '</li>'
else text += '</p>'
}
while (listTypes.length > 0) {
text += `</${listTypes.pop()}>`
}
}
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], 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], 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], 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], 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, warpObj, source, groupHierarchy = []) {
let json
switch (nodeKey) {
case 'p:sp': // Shape, Text
json = await processSpNode(nodeValue, warpObj, source, groupHierarchy)
break
case 'p:cxnSp': // Shape, Text
json = await processCxnSpNode(nodeValue, 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, groupHierarchy)
break
case 'mc:AlternateContent':
if (getTextByPathList(nodeValue, ['mc:Fallback', 'p:grpSpPr', 'a:xfrm'])) {
json = await processGroupSpNode(getTextByPathList(nodeValue, ['mc:Fallback']), warpObj, source, groupHierarchy)
}
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)
let text = ''
if (getTextByPathList(choice, ['p:sp', 'p:txBody', 'a:p', 'a:r'])) {
const sp = getTextByPathList(choice, ['p:sp'])
text = genTextBody(sp['p:txBody'], sp, undefined, undefined, undefined, warpObj)
}
return {
type: 'math',
top,
left,
width,
height,
latex,
picBase64,
text,
order,
}
}
async function processGroupSpNode(node, warpObj, source, parentGroupHierarchy = []) {
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 currentGroupHierarchy = [...parentGroupHierarchy, node]
const elements = []
for (const nodeKey in node) {
if (node[nodeKey].constructor === Array) {
for (const item of node[nodeKey]) {
const ret = await processNodesInSlide(nodeKey, item, warpObj, source, currentGroupHierarchy)
if (ret) elements.push(ret)
}
}
else {
const ret = await processNodesInSlide(nodeKey, node[nodeKey], warpObj, source, currentGroupHierarchy)
if (ret) elements.push(ret)
}
}
const processedElements = elements.map(element => ({
...element,
left: numberToFixed((element.left - chx) * ws),
top: numberToFixed((element.top - chy) * hs),
width: numberToFixed(element.width * ws),
height: numberToFixed(element.height * hs),
...(element.type === 'group' && element.elements ? {
elements: processNestedGroupElements(element.elements, ws, hs)
} : {})
}))
function processNestedGroupElements(elements, ws, hs, depth = 0) {
if (depth > 10) return elements
return elements.map(element => {
const processed = {
...element,
width: numberToFixed(element.width * ws),
height: numberToFixed(element.height * hs),
}
if (element.type === 'group' && element.elements) {
processed.elements = processNestedGroupElements(element.elements, ws, hs, depth + 1)
}
return processed
})
}
return {
type: 'group',
top: numberToFixed(y),
left: numberToFixed(x),
width: numberToFixed(cx),
height: numberToFixed(cy),
rotate,
order,
isFlipV,
isFlipH,
elements: processedElements,
}
}
async function processSpNode(node, warpObj, source, groupHierarchy = []) {
const cNvPr = getTextByPathList(node, ['p:nvSpPr', 'p:cNvPr'])
const name = getTextByPathList(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']['idxTable'][idx]
slideMasterSpNode = warpObj['slideMasterTables']['idxTable'][idx]
if (!slideLayoutSpNode) slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
if (!slideMasterSpNode) 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'
}
const link = getHyperlinkFromCNvPr(cNvPr, warpObj)
return await genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source, link, groupHierarchy)
}
async function processCxnSpNode(node, warpObj, source) {
const cNvPr = getTextByPathList(node, ['p:nvCxnSpPr', 'p:cNvPr'])
const name = getTextByPathList(cNvPr, ['attrs', 'name'])
const type = (node['p:nvCxnSpPr']['p:nvPr']['p:ph'] === undefined) ? undefined : node['p:nvCxnSpPr']['p:nvPr']['p:ph']['attrs']['type']
const order = node['attrs']['order']
const link = getHyperlinkFromCNvPr(cNvPr, warpObj)
return await genShape(node, undefined, undefined, name, type, order, warpObj, source, link)
}
async function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source, link, groupHierarchy = []) {
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 keypoints = {}
if (shapType) {
const shapAdjst_ary = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'a:avLst', 'a:gd'])
if (shapAdjst_ary) {
const adjList = Array.isArray(shapAdjst_ary) ? shapAdjst_ary : [shapAdjst_ary]
for (const adj of adjList) {
const name = getTextByPathList(adj, ['attrs', 'name'])
const fmla = getTextByPathList(adj, ['attrs', 'fmla'])
if (name && fmla && fmla.startsWith('val ')) {
keypoints[name] = parseInt(fmla.substring(4)) / 50000
}
}
}
}
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, slideMasterSpNode, type, warpObj)
const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
const fill = await getShapeFill(node, warpObj, source, groupHierarchy)
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 autoFit = getTextAutoFit(node, slideLayoutSpNode, slideMasterSpNode)
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 (autoFit) data.autoFit = autoFit
if (link) data.link = link
const isHasValidText = data.content && hasValidText(data.content)
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 (!isHasValidText) data.content = ''
return {
...data,
type: 'shape',
shapType: 'custom',
path: d,
}
}
let shapePath = ''
if (shapType) shapePath = getShapePath(shapType, width, height, node)
if (shapType && (type === 'obj' || !type || shapType !== 'rect')) {
if (!isHasValidText) data.content = ''
return {
...data,
type: 'shape',
shapType,
path: shapePath,
keypoints,
}
}
if (shapType && !isHasValidText && (fill || borderWidth)) {
return {
...data,
type: 'shape',
content: '',
shapType,
path: shapePath,
keypoints,
}
}
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 cNvPr = getTextByPathList(node, ['p:nvPicPr', 'p:cNvPr'])
const link = getHyperlinkFromCNvPr(cNvPr, warpObj)
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')
let xfrmNode = node['p:spPr']['a:xfrm']
if (!xfrmNode) {
const idx = getTextByPathList(node, ['p:nvPicPr', 'p:nvPr', 'p:ph', 'attrs', 'idx'])
if (idx) xfrmNode = getTextByPathList(warpObj['slideLayoutTables'], ['idxTable', idx, 'p:spPr', 'a:xfrm'])
}
const mimeType = getMimeType(imgFileExt)
const { top, left } = getPosition(xfrmNode, undefined, undefined)
const { width, height } = getSize(xfrmNode, undefined, undefined)
const src = warpObj.mediaMode === 'blob'
? createBlobUrlSafe(imgArrayBuffer, mimeType)
: `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 = createBlobUrlSafe(uInt8ArrayVideo, 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 = createBlobUrlSafe(uInt8ArrayAudio, 'audio/' + audioFileExt)
}
}
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
}
let geom = 'rect'
const prstGeom = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst'])
const custGeom = getTextByPathList(node, ['p:spPr', 'a:custGeom'])
if (prstGeom) {
geom = prstGeom
}
else if (custGeom) {
geom = identifyShape(custGeom)
if (geom !== 'custom') geom = `custom:${geom}`
}
const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, undefined, warpObj)
const filters = getPicFilters(node['p:blipFill'])
const imageData = {
type: 'image',
top,
left,
width,
height,
rotate,
src,
isFlipV,
isFlipH,
order,
rect,
geom,
borderColor,
borderWidth,
borderType,
borderStrokeDasharray: strokeDasharray,
}
if (filters) imageData.filters = filters
if (link) imageData.link = link
return imageData
}
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, 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, 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)
}
let actualTableWidth = colWidths.reduce((sum, width) => sum + width, 0)
if (actualTableWidth) actualTableWidth = numberToFixed(actualTableWidth)
return {
type: 'table',
top,
left,
width: actualTableWidth || 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 = []
let textList = []
if (dgmDrwSpArray) {
const spList = Array.isArray(dgmDrwSpArray) ? dgmDrwSpArray : [dgmDrwSpArray]
for (const item of spList) {
const el = await processSpNode(item, warpObj, 'diagramBg')
if (el) elements.push(el)
}
}
else if (warpObj.diagramContent && warpObj.diagramContent.data) {
textList = getSmartArtTextData(warpObj.diagramContent.data)
}
return {
type: 'diagram',
left,
top,
width,
height,
elements,
textList,
order,
}
}