1327 lines
47 KiB
JavaScript
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,
|
|
}
|
|
} |