pptxtojson.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699
  1. import JSZip from 'jszip'
  2. import { readXmlFile } from './readXmlFile'
  3. import { getBorder } from './border'
  4. import {
  5. getSlideBackgroundFill,
  6. getShapeFill,
  7. getSolidFill,
  8. getPicFill
  9. } from './fill'
  10. import { getChartInfo } from './chart'
  11. import { getVerticalAlign } from './align'
  12. import { getPosition, getSize } from './position'
  13. import { genTextBody } from './text'
  14. import { getCustomShapePath } from './shape'
  15. import {
  16. extractFileExtension,
  17. base64ArrayBuffer,
  18. getTextByPathList,
  19. angleToDegrees,
  20. getMimeType,
  21. isVideoLink,
  22. escapeHtml,
  23. hasValidText
  24. } from './utils'
  25. import { getShadow } from './shadow'
  26. import {
  27. getTableBorders,
  28. getTableCellParams,
  29. getTableRowParams
  30. } from './table'
  31. import { RATIO_EMUs_Points } from './constants'
  32. import { findOMath, latexFormart, parseOMath } from './math'
  33. export async function parse (file) {
  34. const slides = []
  35. const zip = await JSZip.loadAsync(file)
  36. const filesInfo = await getContentTypes(zip)
  37. const { width, height, defaultTextStyle } = await getSlideInfo(zip)
  38. // 获取所有主题文件
  39. const themeMap = await getAllThemes(zip)
  40. for (const filename of filesInfo.slides) {
  41. // 为每个幻灯片获取对应的主题
  42. const { themeContent, themeColors } = await getSlideTheme(
  43. zip,
  44. filename,
  45. themeMap
  46. )
  47. const singleSlide = await processSingleSlide(
  48. zip,
  49. filename,
  50. themeContent,
  51. defaultTextStyle
  52. )
  53. slides.push({
  54. ...singleSlide,
  55. themeColors // 为每个幻灯片添加其对应的主题颜色
  56. })
  57. }
  58. return {
  59. slides,
  60. size: {
  61. width,
  62. height
  63. }
  64. }
  65. }
  66. async function getSlideInfo (zip) {
  67. const content = await readXmlFile(zip, 'ppt/presentation.xml')
  68. const sldSzAttrs = content['p:presentation']['p:sldSz']['attrs']
  69. const defaultTextStyle = content['p:presentation']['p:defaultTextStyle']
  70. return {
  71. width: parseInt(sldSzAttrs['cx']) * RATIO_EMUs_Points,
  72. height: parseInt(sldSzAttrs['cy']) * RATIO_EMUs_Points,
  73. defaultTextStyle
  74. }
  75. }
  76. async function getContentTypes (zip) {
  77. const ContentTypesJson = await readXmlFile(zip, '[Content_Types].xml')
  78. const subObj = ContentTypesJson['Types']['Override']
  79. let slidesLocArray = []
  80. let slideLayoutsLocArray = []
  81. let themeLocArray = []
  82. for (const item of subObj) {
  83. switch (item['attrs']['ContentType']) {
  84. case 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml':
  85. slidesLocArray.push(item['attrs']['PartName'].substr(1))
  86. break
  87. case 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml':
  88. slideLayoutsLocArray.push(item['attrs']['PartName'].substr(1))
  89. break
  90. case 'application/vnd.openxmlformats-officedocument.theme+xml':
  91. themeLocArray.push(item['attrs']['PartName'].substr(1))
  92. break
  93. default:
  94. }
  95. }
  96. const sortSlideXml = (p1, p2) => {
  97. const n1 = +/(\d+)\.xml/.exec(p1)[1]
  98. const n2 = +/(\d+)\.xml/.exec(p2)[1]
  99. return n1 - n2
  100. }
  101. slidesLocArray = slidesLocArray.sort(sortSlideXml)
  102. slideLayoutsLocArray = slideLayoutsLocArray.sort(sortSlideXml)
  103. themeLocArray = themeLocArray.sort(sortSlideXml)
  104. return {
  105. slides: slidesLocArray,
  106. slideLayouts: slideLayoutsLocArray,
  107. themes: themeLocArray
  108. }
  109. }
  110. // 获取所有主题文件并建立映射关系
  111. async function getAllThemes (zip) {
  112. const themeMap = new Map()
  113. const filesInfo = await getContentTypes(zip)
  114. // 从 Content_Types.xml 中获取主题文件路径
  115. for (const themePath of filesInfo.themes) {
  116. const themeContent = await readXmlFile(zip, themePath)
  117. themeMap.set(themePath, { themeContent, masterId: null })
  118. }
  119. // 获取母版与主题的对应关系
  120. const preResContent = await readXmlFile(
  121. zip,
  122. 'ppt/_rels/presentation.xml.rels'
  123. )
  124. const presentationContent = await readXmlFile(zip, 'ppt/presentation.xml')
  125. const masterIdList = getTextByPathList(presentationContent, [
  126. 'p:presentation',
  127. 'p:sldMasterIdLst',
  128. 'p:sldMasterId'
  129. ])
  130. if (masterIdList) {
  131. const relationshipArray = preResContent['Relationships']['Relationship']
  132. const masters = Array.isArray(masterIdList)
  133. ? masterIdList
  134. : [masterIdList]
  135. for (const master of masters) {
  136. const masterId = master['attrs']['r:id']
  137. const masterRel = Array.isArray(relationshipArray)
  138. ? relationshipArray.find((rel) => rel['attrs']['Id'] === masterId)
  139. : relationshipArray['attrs']['Id'] === masterId
  140. ? relationshipArray
  141. : null
  142. if (masterRel) {
  143. const masterTarget = masterRel['attrs']['Target'].replace(
  144. '../',
  145. 'ppt/'
  146. )
  147. const masterRelsPath =
  148. masterTarget.replace(
  149. 'slideMasters/slideMaster',
  150. 'slideMasters/_rels/slideMaster'
  151. ) + '.rels'
  152. const masterRels = await readXmlFile(zip, masterRelsPath)
  153. if (masterRels && masterRels['Relationships']) {
  154. const relationships = masterRels['Relationships']['Relationship']
  155. const themeRel = Array.isArray(relationships)
  156. ? relationships.find((rel) =>
  157. rel['attrs']['Type'].endsWith('/theme')
  158. )
  159. : relationships['attrs']['Type'].endsWith('/theme')
  160. ? relationships
  161. : null
  162. if (themeRel) {
  163. const themeURI = themeRel['attrs']['Target'].replace('../', '')
  164. const themePath = 'ppt/' + themeURI
  165. // 更新主题映射中的 masterId
  166. if (themeMap.has(themePath)) {
  167. const themeData = themeMap.get(themePath)
  168. themeData.masterId = masterId
  169. themeMap.set(masterTarget, themeData)
  170. }
  171. }
  172. }
  173. }
  174. }
  175. }
  176. return themeMap
  177. }
  178. // 获取单个幻灯片对应的主题
  179. async function getSlideTheme (zip, slideFilename, themeMap) {
  180. try {
  181. // 获取幻灯片的布局信息
  182. const slideRelsPath =
  183. slideFilename.replace('slides/slide', 'slides/_rels/slide') + '.rels'
  184. const slideRels = await readXmlFile(zip, slideRelsPath)
  185. if (!slideRels?.Relationships?.Relationship) {
  186. return { themeContent: {}, themeColors: [] }
  187. }
  188. // 获取布局文件路径
  189. const relationships = slideRels['Relationships']['Relationship']
  190. const layoutRel = Array.isArray(relationships)
  191. ? relationships.find((rel) =>
  192. rel['attrs']['Type'].endsWith('/slideLayout')
  193. )
  194. : relationships['attrs']['Type'].endsWith('/slideLayout')
  195. ? relationships
  196. : null
  197. if (!layoutRel) return { themeContent: {}, themeColors: [] }
  198. const layoutPath = 'ppt/' + layoutRel['attrs']['Target'].replace('../', '')
  199. const layoutRelsPath =
  200. layoutPath.replace(
  201. 'slideLayouts/slideLayout',
  202. 'slideLayouts/_rels/slideLayout'
  203. ) + '.rels'
  204. // 获取母版文件路径
  205. const layoutRels = await readXmlFile(zip, layoutRelsPath)
  206. if (!layoutRels?.Relationships?.Relationship) {
  207. return { themeContent: {}, themeColors: [] }
  208. }
  209. const masterRelationships = layoutRels['Relationships']['Relationship']
  210. const masterRel = Array.isArray(masterRelationships)
  211. ? masterRelationships.find((rel) =>
  212. rel['attrs']['Type'].endsWith('/slideMaster')
  213. )
  214. : masterRelationships['attrs']['Type'].endsWith('/slideMaster')
  215. ? masterRelationships
  216. : null
  217. if (!masterRel) return { themeContent: {}, themeColors: [] }
  218. const masterPath = 'ppt/' + masterRel['attrs']['Target'].replace('../', '')
  219. const masterRelsPath =
  220. masterPath.replace(
  221. 'slideMasters/slideMaster',
  222. 'slideMasters/_rels/slideMaster'
  223. ) + '.rels'
  224. // 获取主题关系
  225. const masterRels = await readXmlFile(zip, masterRelsPath)
  226. if (!masterRels?.Relationships?.Relationship) {
  227. return { themeContent: {}, themeColors: [] }
  228. }
  229. const themeRelationships = masterRels['Relationships']['Relationship']
  230. const themeRel = Array.isArray(themeRelationships)
  231. ? themeRelationships.find((rel) =>
  232. rel['attrs']['Type'].endsWith('/theme')
  233. )
  234. : themeRelationships['attrs']['Type'].endsWith('/theme')
  235. ? themeRelationships
  236. : null
  237. if (!themeRel) return { themeContent: {}, themeColors: [] }
  238. const themePath = 'ppt/' + themeRel['attrs']['Target'].replace('../', '')
  239. const themeData = themeMap.get(themePath)
  240. if (!themeData?.themeContent) return { themeContent: {}, themeColors: [] }
  241. // 提取主题颜色
  242. const themeColors = []
  243. const clrScheme = getTextByPathList(themeData.themeContent, [
  244. 'a:theme',
  245. 'a:themeElements',
  246. 'a:clrScheme'
  247. ])
  248. if (clrScheme) {
  249. for (let i = 1; i <= 6; i++) {
  250. if (clrScheme[`a:accent${i}`] === undefined) break
  251. const color = getTextByPathList(clrScheme, [
  252. `a:accent${i}`,
  253. 'a:srgbClr',
  254. 'attrs',
  255. 'val'
  256. ])
  257. if (color) themeColors.push('#' + color)
  258. }
  259. }
  260. return { themeContent: themeData.themeContent, themeColors }
  261. } catch (error) {
  262. return { themeContent: {}, themeColors: [] }
  263. }
  264. }
  265. async function processSingleSlide (
  266. zip,
  267. sldFileName,
  268. themeContent,
  269. defaultTextStyle
  270. ) {
  271. const resName =
  272. sldFileName.replace('slides/slide', 'slides/_rels/slide') + '.rels'
  273. const resContent = await readXmlFile(zip, resName)
  274. let relationshipArray = resContent['Relationships']['Relationship']
  275. if (relationshipArray.constructor !== Array) { relationshipArray = [relationshipArray] }
  276. let noteFilename = ''
  277. let layoutFilename = ''
  278. let masterFilename = ''
  279. let themeFilename = ''
  280. let diagramFilename = ''
  281. const slideResObj = {}
  282. const layoutResObj = {}
  283. const masterResObj = {}
  284. const themeResObj = {}
  285. const diagramResObj = {}
  286. for (const relationshipArrayItem of relationshipArray) {
  287. switch (relationshipArrayItem['attrs']['Type']) {
  288. case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout':
  289. layoutFilename = relationshipArrayItem['attrs']['Target'].replace(
  290. '../',
  291. 'ppt/'
  292. )
  293. break
  294. case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide':
  295. noteFilename = relationshipArrayItem['attrs']['Target'].replace(
  296. '../',
  297. 'ppt/'
  298. )
  299. break
  300. case 'http://schemas.microsoft.com/office/2007/relationships/diagramDrawing':
  301. diagramFilename = relationshipArrayItem['attrs']['Target'].replace(
  302. '../',
  303. 'ppt/'
  304. )
  305. slideResObj[relationshipArrayItem['attrs']['Id']] = {
  306. type: relationshipArrayItem['attrs']['Type'].replace(
  307. 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/',
  308. ''
  309. ),
  310. target: relationshipArrayItem['attrs']['Target'].replace(
  311. '../',
  312. 'ppt/'
  313. )
  314. }
  315. break
  316. case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image':
  317. case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart':
  318. case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink':
  319. default:
  320. slideResObj[relationshipArrayItem['attrs']['Id']] = {
  321. type: relationshipArrayItem['attrs']['Type'].replace(
  322. 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/',
  323. ''
  324. ),
  325. target: relationshipArrayItem['attrs']['Target'].replace(
  326. '../',
  327. 'ppt/'
  328. )
  329. }
  330. }
  331. }
  332. const slideNotesContent = await readXmlFile(zip, noteFilename)
  333. const note = getNote(slideNotesContent)
  334. const slideLayoutContent = await readXmlFile(zip, layoutFilename)
  335. const slideLayoutTables = await indexNodes(slideLayoutContent)
  336. const slideLayoutResFilename =
  337. layoutFilename.replace(
  338. 'slideLayouts/slideLayout',
  339. 'slideLayouts/_rels/slideLayout'
  340. ) + '.rels'
  341. const slideLayoutResContent = await readXmlFile(zip, slideLayoutResFilename)
  342. relationshipArray = slideLayoutResContent['Relationships']['Relationship']
  343. if (relationshipArray.constructor !== Array) { relationshipArray = [relationshipArray] }
  344. for (const relationshipArrayItem of relationshipArray) {
  345. switch (relationshipArrayItem['attrs']['Type']) {
  346. case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster':
  347. masterFilename = relationshipArrayItem['attrs']['Target'].replace(
  348. '../',
  349. 'ppt/'
  350. )
  351. break
  352. default:
  353. layoutResObj[relationshipArrayItem['attrs']['Id']] = {
  354. type: relationshipArrayItem['attrs']['Type'].replace(
  355. 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/',
  356. ''
  357. ),
  358. target: relationshipArrayItem['attrs']['Target'].replace(
  359. '../',
  360. 'ppt/'
  361. )
  362. }
  363. }
  364. }
  365. const slideMasterContent = await readXmlFile(zip, masterFilename)
  366. const slideMasterTextStyles = getTextByPathList(slideMasterContent, [
  367. 'p:sldMaster',
  368. 'p:txStyles'
  369. ])
  370. const slideMasterTables = indexNodes(slideMasterContent)
  371. const slideMasterResFilename =
  372. masterFilename.replace(
  373. 'slideMasters/slideMaster',
  374. 'slideMasters/_rels/slideMaster'
  375. ) + '.rels'
  376. const slideMasterResContent = await readXmlFile(zip, slideMasterResFilename)
  377. relationshipArray = slideMasterResContent['Relationships']['Relationship']
  378. if (relationshipArray.constructor !== Array) { relationshipArray = [relationshipArray] }
  379. for (const relationshipArrayItem of relationshipArray) {
  380. switch (relationshipArrayItem['attrs']['Type']) {
  381. case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
  382. themeFilename = relationshipArrayItem['attrs']['Target'].replace(
  383. '../',
  384. 'ppt/'
  385. )
  386. break
  387. default:
  388. masterResObj[relationshipArrayItem['attrs']['Id']] = {
  389. type: relationshipArrayItem['attrs']['Type'].replace(
  390. 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/',
  391. ''
  392. ),
  393. target: relationshipArrayItem['attrs']['Target'].replace(
  394. '../',
  395. 'ppt/'
  396. )
  397. }
  398. }
  399. }
  400. if (themeFilename) {
  401. const themeName = themeFilename.split('/').pop()
  402. const themeResFileName =
  403. themeFilename.replace(themeName, '_rels/' + themeName) + '.rels'
  404. const themeResContent = await readXmlFile(zip, themeResFileName)
  405. if (themeResContent) {
  406. relationshipArray = themeResContent['Relationships']['Relationship']
  407. if (relationshipArray) {
  408. if (relationshipArray.constructor !== Array) { relationshipArray = [relationshipArray] }
  409. for (const relationshipArrayItem of relationshipArray) {
  410. themeResObj[relationshipArrayItem['attrs']['Id']] = {
  411. type: relationshipArrayItem['attrs']['Type'].replace(
  412. 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/',
  413. ''
  414. ),
  415. target: relationshipArrayItem['attrs']['Target'].replace(
  416. '../',
  417. 'ppt/'
  418. )
  419. }
  420. }
  421. }
  422. }
  423. }
  424. let digramFileContent = {}
  425. if (diagramFilename) {
  426. const diagName = diagramFilename.split('/').pop()
  427. const diagramResFileName =
  428. diagramFilename.replace(diagName, '_rels/' + diagName) + '.rels'
  429. digramFileContent = await readXmlFile(zip, diagramFilename)
  430. if (digramFileContent) {
  431. const digramFileContentObjToStr = JSON.stringify(
  432. digramFileContent
  433. ).replace(/dsp:/g, 'p:')
  434. digramFileContent = JSON.parse(digramFileContentObjToStr)
  435. }
  436. const digramResContent = await readXmlFile(zip, diagramResFileName)
  437. if (digramResContent) {
  438. relationshipArray = digramResContent['Relationships']['Relationship']
  439. if (relationshipArray.constructor !== Array) { relationshipArray = [relationshipArray] }
  440. for (const relationshipArrayItem of relationshipArray) {
  441. diagramResObj[relationshipArrayItem['attrs']['Id']] = {
  442. type: relationshipArrayItem['attrs']['Type'].replace(
  443. 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/',
  444. ''
  445. ),
  446. target: relationshipArrayItem['attrs']['Target'].replace(
  447. '../',
  448. 'ppt/'
  449. )
  450. }
  451. }
  452. }
  453. }
  454. const tableStyles = await readXmlFile(zip, 'ppt/tableStyles.xml')
  455. const slideContent = await readXmlFile(zip, sldFileName)
  456. const nodes = slideContent['p:sld']['p:cSld']['p:spTree']
  457. const warpObj = {
  458. zip,
  459. slideLayoutContent,
  460. slideLayoutTables,
  461. slideMasterContent,
  462. slideMasterTables,
  463. slideContent,
  464. tableStyles,
  465. slideResObj,
  466. slideMasterTextStyles,
  467. layoutResObj,
  468. masterResObj,
  469. themeContent,
  470. themeResObj,
  471. digramFileContent,
  472. diagramResObj,
  473. defaultTextStyle
  474. }
  475. const layoutElements = await getLayoutElements(warpObj)
  476. const fill = await getSlideBackgroundFill(warpObj)
  477. const elements = []
  478. for (const nodeKey in nodes) {
  479. if (nodes[nodeKey].constructor !== Array) nodes[nodeKey] = [nodes[nodeKey]]
  480. for (const node of nodes[nodeKey]) {
  481. const ret = await processNodesInSlide(
  482. nodeKey,
  483. node,
  484. nodes,
  485. warpObj,
  486. 'slide'
  487. )
  488. if (ret) elements.push(ret)
  489. }
  490. }
  491. return {
  492. fill,
  493. elements,
  494. layoutElements,
  495. note
  496. }
  497. }
  498. function getNote (noteContent) {
  499. let text = ''
  500. let spNodes = getTextByPathList(noteContent, [
  501. 'p:notes',
  502. 'p:cSld',
  503. 'p:spTree',
  504. 'p:sp'
  505. ])
  506. if (!spNodes) return ''
  507. if (spNodes.constructor !== Array) spNodes = [spNodes]
  508. for (const spNode of spNodes) {
  509. let rNodes = getTextByPathList(spNode, ['p:txBody', 'a:p', 'a:r'])
  510. if (!rNodes) continue
  511. if (rNodes.constructor !== Array) rNodes = [rNodes]
  512. for (const rNode of rNodes) {
  513. const t = getTextByPathList(rNode, ['a:t'])
  514. if (t && typeof t === 'string') text += t
  515. }
  516. }
  517. return text
  518. }
  519. async function getLayoutElements (warpObj) {
  520. const elements = []
  521. const slideLayoutContent = warpObj['slideLayoutContent']
  522. const slideMasterContent = warpObj['slideMasterContent']
  523. const nodesSldLayout = getTextByPathList(slideLayoutContent, [
  524. 'p:sldLayout',
  525. 'p:cSld',
  526. 'p:spTree'
  527. ])
  528. const nodesSldMaster = getTextByPathList(slideMasterContent, [
  529. 'p:sldMaster',
  530. 'p:cSld',
  531. 'p:spTree'
  532. ])
  533. const showMasterSp = getTextByPathList(slideLayoutContent, [
  534. 'p:sldLayout',
  535. 'attrs',
  536. 'showMasterSp'
  537. ])
  538. if (nodesSldLayout) {
  539. for (const nodeKey in nodesSldLayout) {
  540. if (nodesSldLayout[nodeKey].constructor === Array) {
  541. for (let i = 0; i < nodesSldLayout[nodeKey].length; i++) {
  542. const ph = getTextByPathList(nodesSldLayout[nodeKey][i], [
  543. 'p:nvSpPr',
  544. 'p:nvPr',
  545. 'p:ph'
  546. ])
  547. if (!ph) {
  548. const ret = await processNodesInSlide(
  549. nodeKey,
  550. nodesSldLayout[nodeKey][i],
  551. nodesSldLayout,
  552. warpObj,
  553. 'slideLayoutBg'
  554. )
  555. if (ret) elements.push(ret)
  556. }
  557. }
  558. } else {
  559. const ph = getTextByPathList(nodesSldLayout[nodeKey], [
  560. 'p:nvSpPr',
  561. 'p:nvPr',
  562. 'p:ph'
  563. ])
  564. if (!ph) {
  565. const ret = await processNodesInSlide(
  566. nodeKey,
  567. nodesSldLayout[nodeKey],
  568. nodesSldLayout,
  569. warpObj,
  570. 'slideLayoutBg'
  571. )
  572. if (ret) elements.push(ret)
  573. }
  574. }
  575. }
  576. }
  577. if (nodesSldMaster && showMasterSp !== '0') {
  578. for (const nodeKey in nodesSldMaster) {
  579. if (nodesSldMaster[nodeKey].constructor === Array) {
  580. for (let i = 0; i < nodesSldMaster[nodeKey].length; i++) {
  581. const ph = getTextByPathList(nodesSldMaster[nodeKey][i], [
  582. 'p:nvSpPr',
  583. 'p:nvPr',
  584. 'p:ph'
  585. ])
  586. if (!ph) {
  587. const ret = await processNodesInSlide(
  588. nodeKey,
  589. nodesSldMaster[nodeKey][i],
  590. nodesSldMaster,
  591. warpObj,
  592. 'slideMasterBg'
  593. )
  594. if (ret) elements.push(ret)
  595. }
  596. }
  597. } else {
  598. const ph = getTextByPathList(nodesSldMaster[nodeKey], [
  599. 'p:nvSpPr',
  600. 'p:nvPr',
  601. 'p:ph'
  602. ])
  603. if (!ph) {
  604. const ret = await processNodesInSlide(
  605. nodeKey,
  606. nodesSldMaster[nodeKey],
  607. nodesSldMaster,
  608. warpObj,
  609. 'slideMasterBg'
  610. )
  611. if (ret) elements.push(ret)
  612. }
  613. }
  614. }
  615. }
  616. return elements
  617. }
  618. function indexNodes (content) {
  619. const keys = Object.keys(content)
  620. const spTreeNode = content[keys[0]]['p:cSld']['p:spTree']
  621. const idTable = {}
  622. const idxTable = {}
  623. const typeTable = {}
  624. for (const key in spTreeNode) {
  625. if (key === 'p:nvGrpSpPr' || key === 'p:grpSpPr') continue
  626. const targetNode = spTreeNode[key]
  627. if (targetNode.constructor === Array) {
  628. for (const targetNodeItem of targetNode) {
  629. const nvSpPrNode = targetNodeItem['p:nvSpPr']
  630. const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
  631. const idx = getTextByPathList(nvSpPrNode, [
  632. 'p:nvPr',
  633. 'p:ph',
  634. 'attrs',
  635. 'idx'
  636. ])
  637. const type = getTextByPathList(nvSpPrNode, [
  638. 'p:nvPr',
  639. 'p:ph',
  640. 'attrs',
  641. 'type'
  642. ])
  643. if (id) idTable[id] = targetNodeItem
  644. if (idx) idxTable[idx] = targetNodeItem
  645. if (type) typeTable[type] = targetNodeItem
  646. }
  647. } else {
  648. const nvSpPrNode = targetNode['p:nvSpPr']
  649. const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
  650. const idx = getTextByPathList(nvSpPrNode, [
  651. 'p:nvPr',
  652. 'p:ph',
  653. 'attrs',
  654. 'idx'
  655. ])
  656. const type = getTextByPathList(nvSpPrNode, [
  657. 'p:nvPr',
  658. 'p:ph',
  659. 'attrs',
  660. 'type'
  661. ])
  662. if (id) idTable[id] = targetNode
  663. if (idx) idxTable[idx] = targetNode
  664. if (type) typeTable[type] = targetNode
  665. }
  666. }
  667. return { idTable, idxTable, typeTable }
  668. }
  669. async function processNodesInSlide (nodeKey, nodeValue, nodes, warpObj, source) {
  670. let json
  671. switch (nodeKey) {
  672. case 'p:sp': // Shape, Text
  673. json = await processSpNode(nodeValue, nodes, warpObj, source)
  674. break
  675. case 'p:cxnSp': // Shape, Text
  676. json = await processCxnSpNode(nodeValue, nodes, warpObj, source)
  677. break
  678. case 'p:pic': // Image, Video, Audio
  679. json = await processPicNode(nodeValue, warpObj, source)
  680. break
  681. case 'p:graphicFrame': // Chart, Diagram, Table
  682. json = await processGraphicFrameNode(nodeValue, warpObj, source)
  683. break
  684. case 'p:grpSp':
  685. json = await processGroupSpNode(nodeValue, warpObj, source)
  686. break
  687. case 'mc:AlternateContent':
  688. if (
  689. getTextByPathList(nodeValue, ['mc:Fallback', 'p:grpSpPr', 'a:xfrm'])
  690. ) {
  691. json = await processGroupSpNode(
  692. getTextByPathList(nodeValue, ['mc:Fallback']),
  693. warpObj,
  694. source
  695. )
  696. } else if (getTextByPathList(nodeValue, ['mc:Choice'])) {
  697. json = await processMathNode(nodeValue, warpObj, source)
  698. }
  699. break
  700. default:
  701. }
  702. return json
  703. }
  704. async function processMathNode (node, warpObj, source) {
  705. const choice = getTextByPathList(node, ['mc:Choice'])
  706. const fallback = getTextByPathList(node, ['mc:Fallback'])
  707. const order = node['attrs']['order']
  708. const xfrmNode = getTextByPathList(choice, ['p:sp', 'p:spPr', 'a:xfrm'])
  709. const { top, left } = getPosition(xfrmNode, undefined, undefined)
  710. const { width, height } = getSize(xfrmNode, undefined, undefined)
  711. const oMath = findOMath(choice)[0]
  712. const latex = latexFormart(parseOMath(oMath))
  713. const blipFill = getTextByPathList(fallback, [
  714. 'p:sp',
  715. 'p:spPr',
  716. 'a:blipFill'
  717. ])
  718. const picBase64 = await getPicFill(source, blipFill, warpObj)
  719. return {
  720. type: 'math',
  721. top,
  722. left,
  723. width,
  724. height,
  725. latex,
  726. picBase64,
  727. order
  728. }
  729. }
  730. async function processGroupSpNode (node, warpObj, source) {
  731. const order = node['attrs']['order']
  732. const xfrmNode = getTextByPathList(node, ['p:grpSpPr', 'a:xfrm'])
  733. if (!xfrmNode) return null
  734. const x = parseInt(xfrmNode['a:off']['attrs']['x']) * RATIO_EMUs_Points
  735. const y = parseInt(xfrmNode['a:off']['attrs']['y']) * RATIO_EMUs_Points
  736. const chx = parseInt(xfrmNode['a:chOff']['attrs']['x']) * RATIO_EMUs_Points
  737. const chy = parseInt(xfrmNode['a:chOff']['attrs']['y']) * RATIO_EMUs_Points
  738. const cx = parseInt(xfrmNode['a:ext']['attrs']['cx']) * RATIO_EMUs_Points
  739. const cy = parseInt(xfrmNode['a:ext']['attrs']['cy']) * RATIO_EMUs_Points
  740. const chcx = parseInt(xfrmNode['a:chExt']['attrs']['cx']) * RATIO_EMUs_Points
  741. const chcy = parseInt(xfrmNode['a:chExt']['attrs']['cy']) * RATIO_EMUs_Points
  742. const isFlipV = getTextByPathList(xfrmNode, ['attrs', 'flipV']) === '1'
  743. const isFlipH = getTextByPathList(xfrmNode, ['attrs', 'flipH']) === '1'
  744. let rotate = getTextByPathList(xfrmNode, ['attrs', 'rot']) || 0
  745. if (rotate) rotate = angleToDegrees(rotate)
  746. const ws = cx / chcx
  747. const hs = cy / chcy
  748. const elements = []
  749. for (const nodeKey in node) {
  750. if (node[nodeKey].constructor === Array) {
  751. for (const item of node[nodeKey]) {
  752. const ret = await processNodesInSlide(
  753. nodeKey,
  754. item,
  755. node,
  756. warpObj,
  757. source
  758. )
  759. if (ret) {
  760. // 如果是嵌套的组合,需要调整其子元素的位置和大小
  761. if (ret.type === 'group') {
  762. ret.elements = ret.elements.map((element) => ({
  763. ...element,
  764. left: element.left * ws,
  765. top: element.top * hs,
  766. width: element.width * ws,
  767. height: element.height * hs
  768. }))
  769. }
  770. elements.push(ret)
  771. }
  772. }
  773. } else {
  774. const ret = await processNodesInSlide(
  775. nodeKey,
  776. node[nodeKey],
  777. node,
  778. warpObj,
  779. source
  780. )
  781. if (ret) {
  782. // 如果是嵌套的组合,需要调整其子元素的位置和大小
  783. if (ret.type === 'group') {
  784. ret.elements = ret.elements.map((element) => ({
  785. ...element,
  786. left: element.left * ws,
  787. top: element.top * hs,
  788. width: element.width * ws,
  789. height: element.height * hs
  790. }))
  791. }
  792. elements.push(ret)
  793. }
  794. }
  795. }
  796. return {
  797. type: 'group',
  798. top: y,
  799. left: x,
  800. width: cx,
  801. height: cy,
  802. rotate,
  803. order,
  804. isFlipV,
  805. isFlipH,
  806. elements: elements.map((element) => ({
  807. ...element,
  808. left: (element.left - chx) * ws,
  809. top: (element.top - chy) * hs,
  810. width: element.width * ws,
  811. height: element.height * hs
  812. }))
  813. }
  814. }
  815. async function processSpNode (node, pNode, warpObj, source) {
  816. const name = getTextByPathList(node, [
  817. 'p:nvSpPr',
  818. 'p:cNvPr',
  819. 'attrs',
  820. 'name'
  821. ])
  822. const idx = getTextByPathList(node, [
  823. 'p:nvSpPr',
  824. 'p:nvPr',
  825. 'p:ph',
  826. 'attrs',
  827. 'idx'
  828. ])
  829. let type = getTextByPathList(node, [
  830. 'p:nvSpPr',
  831. 'p:nvPr',
  832. 'p:ph',
  833. 'attrs',
  834. 'type'
  835. ])
  836. const order = getTextByPathList(node, ['attrs', 'order'])
  837. let slideLayoutSpNode, slideMasterSpNode
  838. if (type) {
  839. if (idx) {
  840. slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
  841. slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
  842. } else {
  843. slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
  844. slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
  845. }
  846. } else if (idx) {
  847. slideLayoutSpNode = warpObj['slideLayoutTables']['idxTable'][idx]
  848. slideMasterSpNode = warpObj['slideMasterTables']['idxTable'][idx]
  849. }
  850. if (!type) {
  851. const txBoxVal = getTextByPathList(node, [
  852. 'p:nvSpPr',
  853. 'p:cNvSpPr',
  854. 'attrs',
  855. 'txBox'
  856. ])
  857. if (txBoxVal === '1') type = 'text'
  858. }
  859. if (!type) {
  860. type = getTextByPathList(slideLayoutSpNode, [
  861. 'p:nvSpPr',
  862. 'p:nvPr',
  863. 'p:ph',
  864. 'attrs',
  865. 'type'
  866. ])
  867. }
  868. if (!type) {
  869. type = getTextByPathList(slideMasterSpNode, [
  870. 'p:nvSpPr',
  871. 'p:nvPr',
  872. 'p:ph',
  873. 'attrs',
  874. 'type'
  875. ])
  876. }
  877. if (!type) {
  878. if (source === 'diagramBg') type = 'diagram'
  879. else type = 'obj'
  880. }
  881. return await genShape(
  882. node,
  883. pNode,
  884. slideLayoutSpNode,
  885. slideMasterSpNode,
  886. name,
  887. type,
  888. order,
  889. warpObj,
  890. source
  891. )
  892. }
  893. async function processCxnSpNode (node, pNode, warpObj, source) {
  894. const name = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['name']
  895. const type =
  896. node['p:nvCxnSpPr']['p:nvPr']['p:ph'] === undefined
  897. ? undefined
  898. : node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['type']
  899. const order = node['attrs']['order']
  900. return await genShape(
  901. node,
  902. pNode,
  903. undefined,
  904. undefined,
  905. name,
  906. type,
  907. order,
  908. warpObj,
  909. source
  910. )
  911. }
  912. async function genShape (
  913. node,
  914. pNode,
  915. slideLayoutSpNode,
  916. slideMasterSpNode,
  917. name,
  918. type,
  919. order,
  920. warpObj,
  921. source
  922. ) {
  923. const xfrmList = ['p:spPr', 'a:xfrm']
  924. const slideXfrmNode = getTextByPathList(node, xfrmList)
  925. const slideLayoutXfrmNode = getTextByPathList(slideLayoutSpNode, xfrmList)
  926. const slideMasterXfrmNode = getTextByPathList(slideMasterSpNode, xfrmList)
  927. const shapType = getTextByPathList(node, [
  928. 'p:spPr',
  929. 'a:prstGeom',
  930. 'attrs',
  931. 'prst'
  932. ])
  933. const custShapType = getTextByPathList(node, ['p:spPr', 'a:custGeom'])
  934. const { top, left } = getPosition(
  935. slideXfrmNode,
  936. slideLayoutXfrmNode,
  937. slideMasterXfrmNode
  938. )
  939. const { width, height } = getSize(
  940. slideXfrmNode,
  941. slideLayoutXfrmNode,
  942. slideMasterXfrmNode
  943. )
  944. const isFlipV = getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1'
  945. const isFlipH = getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1'
  946. const rotate = angleToDegrees(
  947. getTextByPathList(slideXfrmNode, ['attrs', 'rot'])
  948. )
  949. const txtXframeNode = getTextByPathList(node, ['p:txXfrm'])
  950. let txtRotate
  951. if (txtXframeNode) {
  952. const txtXframeRot = getTextByPathList(txtXframeNode, ['attrs', 'rot'])
  953. if (txtXframeRot) txtRotate = angleToDegrees(txtXframeRot) + 90
  954. } else txtRotate = rotate
  955. let content = ''
  956. if (node['p:txBody']) {
  957. content = genTextBody(
  958. node['p:txBody'],
  959. node,
  960. slideLayoutSpNode,
  961. type,
  962. warpObj
  963. )
  964. }
  965. const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(
  966. node,
  967. type,
  968. warpObj
  969. )
  970. const fill =
  971. (await getShapeFill(node, pNode, undefined, warpObj, source)) || ''
  972. let shadow
  973. const outerShdwNode = getTextByPathList(node, [
  974. 'p:spPr',
  975. 'a:effectLst',
  976. 'a:outerShdw'
  977. ])
  978. if (outerShdwNode) shadow = getShadow(outerShdwNode, warpObj)
  979. const vAlign = getVerticalAlign(
  980. node,
  981. slideLayoutSpNode,
  982. slideMasterSpNode,
  983. type
  984. )
  985. const isVertical =
  986. getTextByPathList(node, ['p:txBody', 'a:bodyPr', 'attrs', 'vert']) ===
  987. 'eaVert'
  988. const data = {
  989. left,
  990. top,
  991. width,
  992. height,
  993. borderColor,
  994. borderWidth,
  995. borderType,
  996. borderStrokeDasharray: strokeDasharray,
  997. fill,
  998. content,
  999. isFlipV,
  1000. isFlipH,
  1001. rotate,
  1002. vAlign,
  1003. name,
  1004. order
  1005. }
  1006. if (shadow) data.shadow = shadow
  1007. if (custShapType && type !== 'diagram') {
  1008. const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
  1009. const w = parseInt(ext['cx']) * RATIO_EMUs_Points
  1010. const h = parseInt(ext['cy']) * RATIO_EMUs_Points
  1011. const d = getCustomShapePath(custShapType, w, h)
  1012. if (data.content && !hasValidText(data.content)) data.content = ''
  1013. return {
  1014. ...data,
  1015. type: 'shape',
  1016. shapType: 'custom',
  1017. path: d
  1018. }
  1019. }
  1020. if (shapType && (type === 'obj' || !type)) {
  1021. if (data.content && !hasValidText(data.content)) data.content = ''
  1022. return {
  1023. ...data,
  1024. type: 'shape',
  1025. shapType
  1026. }
  1027. }
  1028. return {
  1029. ...data,
  1030. type: 'text',
  1031. isVertical,
  1032. rotate: txtRotate
  1033. }
  1034. }
  1035. async function processPicNode (node, warpObj, source) {
  1036. let resObj
  1037. if (source === 'slideMasterBg') resObj = warpObj['masterResObj']
  1038. else if (source === 'slideLayoutBg') resObj = warpObj['layoutResObj']
  1039. else resObj = warpObj['slideResObj']
  1040. const order = node['attrs']['order']
  1041. const rid = node['p:blipFill']['a:blip']['attrs']['r:embed']
  1042. const imgName = resObj[rid]['target']
  1043. const imgFileExt = extractFileExtension(imgName).toLowerCase()
  1044. const zip = warpObj['zip']
  1045. const imgArrayBuffer = await zip.file(imgName).async('arraybuffer')
  1046. const xfrmNode = node['p:spPr']['a:xfrm']
  1047. const mimeType = getMimeType(imgFileExt)
  1048. const { top, left } = getPosition(xfrmNode, undefined, undefined)
  1049. const { width, height } = getSize(xfrmNode, undefined, undefined)
  1050. const src = `data:${mimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
  1051. const isFlipV = getTextByPathList(xfrmNode, ['attrs', 'flipV']) === '1'
  1052. const isFlipH = getTextByPathList(xfrmNode, ['attrs', 'flipH']) === '1'
  1053. let rotate = 0
  1054. const rotateNode = getTextByPathList(node, [
  1055. 'p:spPr',
  1056. 'a:xfrm',
  1057. 'attrs',
  1058. 'rot'
  1059. ])
  1060. if (rotateNode) rotate = angleToDegrees(rotateNode)
  1061. const videoNode = getTextByPathList(node, [
  1062. 'p:nvPicPr',
  1063. 'p:nvPr',
  1064. 'a:videoFile'
  1065. ])
  1066. let videoRid,
  1067. videoFile,
  1068. videoFileExt,
  1069. videoMimeType,
  1070. uInt8ArrayVideo,
  1071. videoBlob
  1072. let isVdeoLink = false
  1073. if (videoNode) {
  1074. videoRid = videoNode['attrs']['r:link']
  1075. videoFile = resObj[videoRid]['target']
  1076. if (isVideoLink(videoFile)) {
  1077. videoFile = escapeHtml(videoFile)
  1078. isVdeoLink = true
  1079. } else {
  1080. videoFileExt = extractFileExtension(videoFile).toLowerCase()
  1081. if (
  1082. videoFileExt === 'mp4' ||
  1083. videoFileExt === 'webm' ||
  1084. videoFileExt === 'ogg'
  1085. ) {
  1086. uInt8ArrayVideo = await zip.file(videoFile).async('arraybuffer')
  1087. videoMimeType = getMimeType(videoFileExt)
  1088. videoBlob = URL.createObjectURL(
  1089. new Blob([uInt8ArrayVideo], {
  1090. type: videoMimeType
  1091. })
  1092. )
  1093. }
  1094. }
  1095. }
  1096. const audioNode = getTextByPathList(node, [
  1097. 'p:nvPicPr',
  1098. 'p:nvPr',
  1099. 'a:audioFile'
  1100. ])
  1101. let audioRid, audioFile, audioFileExt, uInt8ArrayAudio, audioBlob
  1102. if (audioNode) {
  1103. audioRid = audioNode['attrs']['r:link']
  1104. audioFile = resObj[audioRid]['target']
  1105. audioFileExt = extractFileExtension(audioFile).toLowerCase()
  1106. if (
  1107. audioFileExt === 'mp3' ||
  1108. audioFileExt === 'wav' ||
  1109. audioFileExt === 'ogg'
  1110. ) {
  1111. uInt8ArrayAudio = await zip.file(audioFile).async('arraybuffer')
  1112. audioBlob = URL.createObjectURL(new Blob([uInt8ArrayAudio]))
  1113. }
  1114. }
  1115. if (videoNode && !isVdeoLink) {
  1116. return {
  1117. type: 'video',
  1118. top,
  1119. left,
  1120. width,
  1121. height,
  1122. rotate,
  1123. blob: videoBlob,
  1124. order
  1125. }
  1126. }
  1127. if (videoNode && isVdeoLink) {
  1128. return {
  1129. type: 'video',
  1130. top,
  1131. left,
  1132. width,
  1133. height,
  1134. rotate,
  1135. src: videoFile,
  1136. order
  1137. }
  1138. }
  1139. if (audioNode) {
  1140. return {
  1141. type: 'audio',
  1142. top,
  1143. left,
  1144. width,
  1145. height,
  1146. rotate,
  1147. blob: audioBlob,
  1148. order
  1149. }
  1150. }
  1151. let rect
  1152. const srcRectAttrs = getTextByPathList(node, [
  1153. 'p:blipFill',
  1154. 'a:srcRect',
  1155. 'attrs'
  1156. ])
  1157. if (
  1158. srcRectAttrs &&
  1159. (srcRectAttrs.t || srcRectAttrs.b || srcRectAttrs.l || srcRectAttrs.r)
  1160. ) {
  1161. rect = {}
  1162. if (srcRectAttrs.t) rect.t = srcRectAttrs.t / 1000
  1163. if (srcRectAttrs.b) rect.b = srcRectAttrs.b / 1000
  1164. if (srcRectAttrs.l) rect.l = srcRectAttrs.l / 1000
  1165. if (srcRectAttrs.r) rect.r = srcRectAttrs.r / 1000
  1166. }
  1167. const geom =
  1168. getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst']) ||
  1169. 'rect'
  1170. const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(
  1171. node,
  1172. undefined,
  1173. warpObj
  1174. )
  1175. return {
  1176. type: 'image',
  1177. top,
  1178. left,
  1179. width,
  1180. height,
  1181. rotate,
  1182. src,
  1183. isFlipV,
  1184. isFlipH,
  1185. order,
  1186. rect,
  1187. geom,
  1188. borderColor,
  1189. borderWidth,
  1190. borderType,
  1191. borderStrokeDasharray: strokeDasharray
  1192. }
  1193. }
  1194. async function processGraphicFrameNode (node, warpObj, source) {
  1195. const graphicTypeUri = getTextByPathList(node, [
  1196. 'a:graphic',
  1197. 'a:graphicData',
  1198. 'attrs',
  1199. 'uri'
  1200. ])
  1201. let result
  1202. switch (graphicTypeUri) {
  1203. case 'http://schemas.openxmlformats.org/drawingml/2006/table':
  1204. result = await genTable(node, warpObj)
  1205. break
  1206. case 'http://schemas.openxmlformats.org/drawingml/2006/chart':
  1207. result = await genChart(node, warpObj)
  1208. break
  1209. case 'http://schemas.openxmlformats.org/drawingml/2006/diagram':
  1210. result = await genDiagram(node, warpObj)
  1211. break
  1212. case 'http://schemas.openxmlformats.org/presentationml/2006/ole':
  1213. let oleObjNode = getTextByPathList(node, [
  1214. 'a:graphic',
  1215. 'a:graphicData',
  1216. 'mc:AlternateContent',
  1217. 'mc:Fallback',
  1218. 'p:oleObj'
  1219. ])
  1220. if (!oleObjNode) {
  1221. oleObjNode = getTextByPathList(node, [
  1222. 'a:graphic',
  1223. 'a:graphicData',
  1224. 'p:oleObj'
  1225. ])
  1226. }
  1227. if (oleObjNode) { result = await processGroupSpNode(oleObjNode, warpObj, source) }
  1228. break
  1229. default:
  1230. }
  1231. return result
  1232. }
  1233. async function genTable (node, warpObj) {
  1234. const order = node['attrs']['order']
  1235. const tableNode = getTextByPathList(node, [
  1236. 'a:graphic',
  1237. 'a:graphicData',
  1238. 'a:tbl'
  1239. ])
  1240. const xfrmNode = getTextByPathList(node, ['p:xfrm'])
  1241. const { top, left } = getPosition(xfrmNode, undefined, undefined)
  1242. const { width, height } = getSize(xfrmNode, undefined, undefined)
  1243. const getTblPr = getTextByPathList(node, [
  1244. 'a:graphic',
  1245. 'a:graphicData',
  1246. 'a:tbl',
  1247. 'a:tblPr'
  1248. ])
  1249. let getColsGrid = getTextByPathList(node, [
  1250. 'a:graphic',
  1251. 'a:graphicData',
  1252. 'a:tbl',
  1253. 'a:tblGrid',
  1254. 'a:gridCol'
  1255. ])
  1256. if (getColsGrid.constructor !== Array) getColsGrid = [getColsGrid]
  1257. const colWidths = []
  1258. if (getColsGrid) {
  1259. for (const item of getColsGrid) {
  1260. const colWidthParam = getTextByPathList(item, ['attrs', 'w']) || 0
  1261. const colWidth = parseInt(colWidthParam) * RATIO_EMUs_Points
  1262. colWidths.push(colWidth)
  1263. }
  1264. }
  1265. const firstRowAttr = getTblPr['attrs']
  1266. ? getTblPr['attrs']['firstRow']
  1267. : undefined
  1268. const firstColAttr = getTblPr['attrs']
  1269. ? getTblPr['attrs']['firstCol']
  1270. : undefined
  1271. const lastRowAttr = getTblPr['attrs']
  1272. ? getTblPr['attrs']['lastRow']
  1273. : undefined
  1274. const lastColAttr = getTblPr['attrs']
  1275. ? getTblPr['attrs']['lastCol']
  1276. : undefined
  1277. const bandRowAttr = getTblPr['attrs']
  1278. ? getTblPr['attrs']['bandRow']
  1279. : undefined
  1280. const bandColAttr = getTblPr['attrs']
  1281. ? getTblPr['attrs']['bandCol']
  1282. : undefined
  1283. const tblStylAttrObj = {
  1284. isFrstRowAttr: firstRowAttr && firstRowAttr === '1' ? 1 : 0,
  1285. isFrstColAttr: firstColAttr && firstColAttr === '1' ? 1 : 0,
  1286. isLstRowAttr: lastRowAttr && lastRowAttr === '1' ? 1 : 0,
  1287. isLstColAttr: lastColAttr && lastColAttr === '1' ? 1 : 0,
  1288. isBandRowAttr: bandRowAttr && bandRowAttr === '1' ? 1 : 0,
  1289. isBandColAttr: bandColAttr && bandColAttr === '1' ? 1 : 0
  1290. }
  1291. let thisTblStyle
  1292. const tbleStyleId = getTblPr['a:tableStyleId']
  1293. if (tbleStyleId) {
  1294. const tbleStylList = warpObj['tableStyles']['a:tblStyleLst']['a:tblStyle']
  1295. if (tbleStylList) {
  1296. if (tbleStylList.constructor === Array) {
  1297. for (let k = 0; k < tbleStylList.length; k++) {
  1298. if (tbleStylList[k]['attrs']['styleId'] === tbleStyleId) {
  1299. thisTblStyle = tbleStylList[k]
  1300. }
  1301. }
  1302. } else {
  1303. if (tbleStylList['attrs']['styleId'] === tbleStyleId) {
  1304. thisTblStyle = tbleStylList
  1305. }
  1306. }
  1307. }
  1308. }
  1309. if (thisTblStyle) thisTblStyle['tblStylAttrObj'] = tblStylAttrObj
  1310. let borders = {}
  1311. const tblStyl = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle'])
  1312. const tblBorderStyl = getTextByPathList(tblStyl, ['a:tcBdr'])
  1313. if (tblBorderStyl) borders = getTableBorders(tblBorderStyl, warpObj)
  1314. let tbl_bgcolor = ''
  1315. let tbl_bgFillschemeClr = getTextByPathList(thisTblStyle, [
  1316. 'a:tblBg',
  1317. 'a:fillRef'
  1318. ])
  1319. if (tbl_bgFillschemeClr) {
  1320. tbl_bgcolor = getSolidFill(
  1321. tbl_bgFillschemeClr,
  1322. undefined,
  1323. undefined,
  1324. warpObj
  1325. )
  1326. }
  1327. if (tbl_bgFillschemeClr === undefined) {
  1328. tbl_bgFillschemeClr = getTextByPathList(thisTblStyle, [
  1329. 'a:wholeTbl',
  1330. 'a:tcStyle',
  1331. 'a:fill',
  1332. 'a:solidFill'
  1333. ])
  1334. tbl_bgcolor = getSolidFill(
  1335. tbl_bgFillschemeClr,
  1336. undefined,
  1337. undefined,
  1338. warpObj
  1339. )
  1340. }
  1341. let trNodes = tableNode['a:tr']
  1342. if (trNodes.constructor !== Array) trNodes = [trNodes]
  1343. const data = []
  1344. const rowHeights = []
  1345. for (let i = 0; i < trNodes.length; i++) {
  1346. const trNode = trNodes[i]
  1347. const rowHeightParam = getTextByPathList(trNodes[i], ['attrs', 'h']) || 0
  1348. const rowHeight = parseInt(rowHeightParam) * RATIO_EMUs_Points
  1349. rowHeights.push(rowHeight)
  1350. const { fillColor, fontColor, fontBold } = getTableRowParams(
  1351. trNodes,
  1352. i,
  1353. tblStylAttrObj,
  1354. thisTblStyle,
  1355. warpObj
  1356. )
  1357. const tcNodes = trNode['a:tc']
  1358. const tr = []
  1359. if (tcNodes.constructor === Array) {
  1360. for (let j = 0; j < tcNodes.length; j++) {
  1361. const tcNode = tcNodes[j]
  1362. let a_sorce
  1363. if (j === 0 && tblStylAttrObj['isFrstColAttr'] === 1) {
  1364. a_sorce = 'a:firstCol'
  1365. if (
  1366. tblStylAttrObj['isLstRowAttr'] === 1 &&
  1367. i === trNodes.length - 1 &&
  1368. getTextByPathList(thisTblStyle, ['a:seCell'])
  1369. ) {
  1370. a_sorce = 'a:seCell'
  1371. } else if (
  1372. tblStylAttrObj['isFrstRowAttr'] === 1 &&
  1373. i === 0 &&
  1374. getTextByPathList(thisTblStyle, ['a:neCell'])
  1375. ) {
  1376. a_sorce = 'a:neCell'
  1377. }
  1378. } else if (
  1379. j > 0 &&
  1380. tblStylAttrObj['isBandColAttr'] === 1 &&
  1381. !(tblStylAttrObj['isFrstColAttr'] === 1 && i === 0) &&
  1382. !(tblStylAttrObj['isLstRowAttr'] === 1 && i === trNodes.length - 1) &&
  1383. j !== tcNodes.length - 1
  1384. ) {
  1385. if (j % 2 !== 0) {
  1386. let aBandNode = getTextByPathList(thisTblStyle, ['a:band2V'])
  1387. if (aBandNode === undefined) {
  1388. aBandNode = getTextByPathList(thisTblStyle, ['a:band1V'])
  1389. if (aBandNode) a_sorce = 'a:band2V'
  1390. } else a_sorce = 'a:band2V'
  1391. }
  1392. }
  1393. if (j === tcNodes.length - 1 && tblStylAttrObj['isLstColAttr'] === 1) {
  1394. a_sorce = 'a:lastCol'
  1395. if (
  1396. tblStylAttrObj['isLstRowAttr'] === 1 &&
  1397. i === trNodes.length - 1 &&
  1398. getTextByPathList(thisTblStyle, ['a:swCell'])
  1399. ) {
  1400. a_sorce = 'a:swCell'
  1401. } else if (
  1402. tblStylAttrObj['isFrstRowAttr'] === 1 &&
  1403. i === 0 &&
  1404. getTextByPathList(thisTblStyle, ['a:nwCell'])
  1405. ) {
  1406. a_sorce = 'a:nwCell'
  1407. }
  1408. }
  1409. const text = genTextBody(
  1410. tcNode['a:txBody'],
  1411. tcNode,
  1412. undefined,
  1413. undefined,
  1414. warpObj
  1415. )
  1416. const cell = await getTableCellParams(
  1417. tcNode,
  1418. thisTblStyle,
  1419. a_sorce,
  1420. warpObj
  1421. )
  1422. const td = { text }
  1423. if (cell.rowSpan) td.rowSpan = cell.rowSpan
  1424. if (cell.colSpan) td.colSpan = cell.colSpan
  1425. if (cell.vMerge) td.vMerge = cell.vMerge
  1426. if (cell.hMerge) td.hMerge = cell.hMerge
  1427. if (cell.fontBold || fontBold) td.fontBold = cell.fontBold || fontBold
  1428. if (cell.fontColor || fontColor) { td.fontColor = cell.fontColor || fontColor }
  1429. if (cell.fillColor || fillColor || tbl_bgcolor) { td.fillColor = cell.fillColor || fillColor || tbl_bgcolor }
  1430. if (cell.borders) td.borders = cell.borders
  1431. tr.push(td)
  1432. }
  1433. } else {
  1434. let a_sorce
  1435. if (
  1436. tblStylAttrObj['isFrstColAttr'] === 1 &&
  1437. tblStylAttrObj['isLstRowAttr'] !== 1
  1438. ) {
  1439. a_sorce = 'a:firstCol'
  1440. } else if (
  1441. tblStylAttrObj['isBandColAttr'] === 1 &&
  1442. tblStylAttrObj['isLstRowAttr'] !== 1
  1443. ) {
  1444. let aBandNode = getTextByPathList(thisTblStyle, ['a:band2V'])
  1445. if (!aBandNode) {
  1446. aBandNode = getTextByPathList(thisTblStyle, ['a:band1V'])
  1447. if (aBandNode) a_sorce = 'a:band2V'
  1448. } else a_sorce = 'a:band2V'
  1449. }
  1450. if (
  1451. tblStylAttrObj['isLstColAttr'] === 1 &&
  1452. tblStylAttrObj['isLstRowAttr'] !== 1
  1453. ) {
  1454. a_sorce = 'a:lastCol'
  1455. }
  1456. const text = genTextBody(
  1457. tcNodes['a:txBody'],
  1458. tcNodes,
  1459. undefined,
  1460. undefined,
  1461. warpObj
  1462. )
  1463. const cell = await getTableCellParams(
  1464. tcNodes,
  1465. thisTblStyle,
  1466. a_sorce,
  1467. warpObj
  1468. )
  1469. const td = { text }
  1470. if (cell.rowSpan) td.rowSpan = cell.rowSpan
  1471. if (cell.colSpan) td.colSpan = cell.colSpan
  1472. if (cell.vMerge) td.vMerge = cell.vMerge
  1473. if (cell.hMerge) td.hMerge = cell.hMerge
  1474. if (cell.fontBold || fontBold) td.fontBold = cell.fontBold || fontBold
  1475. if (cell.fontColor || fontColor) { td.fontColor = cell.fontColor || fontColor }
  1476. if (cell.fillColor || fillColor || tbl_bgcolor) { td.fillColor = cell.fillColor || fillColor || tbl_bgcolor }
  1477. if (cell.borders) td.borders = cell.borders
  1478. tr.push(td)
  1479. }
  1480. data.push(tr)
  1481. }
  1482. return {
  1483. type: 'table',
  1484. top,
  1485. left,
  1486. width,
  1487. height,
  1488. data,
  1489. order,
  1490. borders,
  1491. rowHeights,
  1492. colWidths
  1493. }
  1494. }
  1495. async function genChart (node, warpObj) {
  1496. const order = node['attrs']['order']
  1497. const xfrmNode = getTextByPathList(node, ['p:xfrm'])
  1498. const { top, left } = getPosition(xfrmNode, undefined, undefined)
  1499. const { width, height } = getSize(xfrmNode, undefined, undefined)
  1500. const rid = node['a:graphic']['a:graphicData']['c:chart']['attrs']['r:id']
  1501. let refName = getTextByPathList(warpObj['slideResObj'], [rid, 'target'])
  1502. if (!refName) { refName = getTextByPathList(warpObj['layoutResObj'], [rid, 'target']) }
  1503. if (!refName) { refName = getTextByPathList(warpObj['masterResObj'], [rid, 'target']) }
  1504. if (!refName) return {}
  1505. const content = await readXmlFile(warpObj['zip'], refName)
  1506. const plotArea = getTextByPathList(content, [
  1507. 'c:chartSpace',
  1508. 'c:chart',
  1509. 'c:plotArea'
  1510. ])
  1511. const chart = getChartInfo(plotArea, warpObj)
  1512. if (!chart) return {}
  1513. const data = {
  1514. type: 'chart',
  1515. top,
  1516. left,
  1517. width,
  1518. height,
  1519. data: chart.data,
  1520. colors: chart.colors,
  1521. chartType: chart.type,
  1522. order
  1523. }
  1524. if (chart.marker !== undefined) data.marker = chart.marker
  1525. if (chart.barDir !== undefined) data.barDir = chart.barDir
  1526. if (chart.holeSize !== undefined) data.holeSize = chart.holeSize
  1527. if (chart.grouping !== undefined) data.grouping = chart.grouping
  1528. if (chart.style !== undefined) data.style = chart.style
  1529. return data
  1530. }
  1531. async function genDiagram (node, warpObj) {
  1532. const order = node['attrs']['order']
  1533. const xfrmNode = getTextByPathList(node, ['p:xfrm'])
  1534. const { left, top } = getPosition(xfrmNode, undefined, undefined)
  1535. const { width, height } = getSize(xfrmNode, undefined, undefined)
  1536. const dgmDrwSpArray = getTextByPathList(warpObj['digramFileContent'], [
  1537. 'p:drawing',
  1538. 'p:spTree',
  1539. 'p:sp'
  1540. ])
  1541. const elements = []
  1542. if (dgmDrwSpArray) {
  1543. for (const item of dgmDrwSpArray) {
  1544. const el = await processSpNode(item, node, warpObj, 'diagramBg')
  1545. if (el) elements.push(el)
  1546. }
  1547. }
  1548. return {
  1549. type: 'diagram',
  1550. left,
  1551. top,
  1552. width,
  1553. height,
  1554. elements,
  1555. order
  1556. }
  1557. }