pptxtojson 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.html CHANGED
@@ -4,8 +4,8 @@
4
4
  <meta charset="utf-8">
5
5
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <meta name="description" content="基于JavaScript的 .pptx 文件解析工具丨A javascript tool for parsing .pptx file" />
8
- <meta name="keywords" content="ppt,powerpoint,office powerpoint,json,javascript,PPT解析,PPT转JSON" />
7
+ <meta name="description" content="Office PowerPoint(.pptx) file to JSON | PPTX 文件转为可读的 JSON 数据" />
8
+ <meta name="keywords" content="pptx2json,pptxtojson,ppt,powerpoint,json,javascript,PPT解析,PPT转JSON" />
9
9
  <link rel="icon" href="favicon.ico">
10
10
  <title>pptxtojson - PPTX转JSON</title>
11
11
 
@@ -80,6 +80,13 @@
80
80
  .upload-input {
81
81
  display: none;
82
82
  }
83
+ .link {
84
+ display: flex;
85
+ }
86
+ .link a {
87
+ padding: 5px 10px;
88
+ color: #d14424;
89
+ }
83
90
  </style>
84
91
  </head>
85
92
 
@@ -90,7 +97,10 @@
90
97
  <input class="upload-input" type="file" accept="application/vnd.openxmlformats-officedocument.presentationml.presentation"/>
91
98
  </div>
92
99
 
93
- <a href="https://github.com/pipipi-pikachu/pptx2json">Github</a>
100
+ <div class="link">
101
+ <a target="_blank" href="https://github.com/pipipi-pikachu/pptx2json">Github仓库</a>
102
+ <a target="_blank" href="https://pipipi-pikachu.github.io/PPTist/">可视化测试</a>
103
+ </div>
94
104
  </div>
95
105
  <div id="jsoneditor"></div>
96
106
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptxtojson",
3
- "version": "1.0.3",
3
+ "version": "1.1.1",
4
4
  "description": "A javascript tool for parsing .pptx file",
5
5
  "type": "module",
6
6
  "main": "./dist/index.umd.js",
@@ -24,7 +24,7 @@
24
24
  "homepage": "https://github.com/pipipi-pikachu/pptxtojson",
25
25
  "license": "MIT",
26
26
  "publishConfig": {
27
- "registry": "https://registry.npmjs.org"
27
+ "registry": "https://registry.npmjs.com"
28
28
  },
29
29
  "dependencies": {
30
30
  "jszip": "^3.10.1",
package/src/math.js ADDED
@@ -0,0 +1,184 @@
1
+ import { getTextByPathList } from './utils'
2
+
3
+ export function findOMath(obj) {
4
+ let results = []
5
+ if (typeof obj !== 'object') return results
6
+ if (obj['m:oMath']) results = results.concat(obj['m:oMath'])
7
+
8
+ Object.values(obj).forEach(value => {
9
+ if (Array.isArray(value) || typeof value === 'object') {
10
+ results = results.concat(findOMath(value))
11
+ }
12
+ })
13
+ return results
14
+ }
15
+
16
+ export function parseFraction(fraction) {
17
+ const numerator = parseOMath(fraction['m:num'])
18
+ const denominator = parseOMath(fraction['m:den'])
19
+ return `\\frac{${numerator}}{${denominator}}`
20
+ }
21
+ export function parseSuperscript(superscript) {
22
+ const base = parseOMath(superscript['m:e'])
23
+ const sup = parseOMath(superscript['m:sup'])
24
+ return `${base}^{${sup}}`
25
+ }
26
+ export function parseSubscript(subscript) {
27
+ const base = parseOMath(subscript['m:e'])
28
+ const sub = parseOMath(subscript['m:sub'])
29
+ return `${base}_{${sub}}`
30
+ }
31
+ export function parseRadical(radical) {
32
+ const degree = parseOMath(radical['m:deg'])
33
+ const expression = parseOMath(radical['m:e'])
34
+ return degree ? `\\sqrt[${degree}]{${expression}}` : `\\sqrt{${expression}}`
35
+ }
36
+ export function parseMatrix(matrix) {
37
+ const rows = matrix['m:mr']
38
+ const matrixRows = rows.map((row) => {
39
+ return row['m:e'].map((element) => parseOMath(element)).join(' & ')
40
+ })
41
+ return `\\begin{matrix} ${matrixRows.join(' \\\\ ')} \\end{matrix}`
42
+ }
43
+ export function parseNary(nary) {
44
+ const op = getTextByPathList(nary, ['m:naryPr', 'm:chr', 'attrs', 'm:val']) || '∫'
45
+ const sub = parseOMath(nary['m:sub'])
46
+ const sup = parseOMath(nary['m:sup'])
47
+ const e = parseOMath(nary['m:e'])
48
+ return `${op}_{${sub}}^{${sup}}{${e}}`
49
+ }
50
+ export function parseLimit(limit, type) {
51
+ const base = parseOMath(limit['m:e'])
52
+ const lim = parseOMath(limit['m:lim'])
53
+ return type === 'low' ? `${base}_{${lim}}` : `${base}^{${lim}}`
54
+ }
55
+ export function parseDelimiter(delimiter) {
56
+ let left = getTextByPathList(delimiter, ['m:dPr', 'm:begChr', 'attrs', 'm:val'])
57
+ let right = getTextByPathList(delimiter, ['m:dPr', 'm:endChr', 'attrs', 'm:val'])
58
+ if (!left && !right) {
59
+ left = '('
60
+ right = ')'
61
+ }
62
+ if (left && right) {
63
+ left = `\\left${left}`
64
+ right = `\\right${right}`
65
+ }
66
+ const e = parseOMath(delimiter['m:e'])
67
+ return `${left}${e}${right}`
68
+ }
69
+ export function parseFunction(func) {
70
+ const name = parseOMath(func['m:fName'])
71
+ const arg = parseOMath(func['m:e'])
72
+ return `\\${name}{${arg}}`
73
+ }
74
+ export function parseGroupChr(groupChr) {
75
+ const chr = getTextByPathList(groupChr, ['m:groupChrPr', 'm:chr', 'attrs', 'm:val'])
76
+ const e = parseOMath(groupChr['m:e'])
77
+ return `${chr}${e}${chr}`
78
+ }
79
+ export function parseEqArr(eqArr) {
80
+ const equations = eqArr['m:e'].map((eq) => parseOMath(eq)).join(' \\\\ ')
81
+ return `\\begin{cases} ${equations} \\end{cases}`
82
+ }
83
+ export function parseBar(bar) {
84
+ const e = parseOMath(bar['m:e'])
85
+ const pos = getTextByPathList(bar, ['m:barPr', 'm:pos', 'attrs', 'm:val'])
86
+ return pos === 'top' ? `\\overline{${e}}` : `\\underline{${e}}`
87
+ }
88
+ export function parseAccent(accent) {
89
+ const chr = getTextByPathList(accent, ['m:accPr', 'm:chr', 'attrs', 'm:val']) || '^'
90
+ const e = parseOMath(accent['m:e'])
91
+ switch (chr) {
92
+ case '\u0301':
93
+ return `\\acute{${e}}`
94
+ case '\u0300':
95
+ return `\\grave{${e}}`
96
+ case '\u0302':
97
+ return `\\hat{${e}}`
98
+ case '\u0303':
99
+ return `\\tilde{${e}}`
100
+ case '\u0304':
101
+ return `\\bar{${e}}`
102
+ case '\u0306':
103
+ return `\\breve{${e}}`
104
+ case '\u0307':
105
+ return `\\dot{${e}}`
106
+ case '\u0308':
107
+ return `\\ddot{${e}}`
108
+ case '\u030A':
109
+ return `\\mathring{${e}}`
110
+ case '\u030B':
111
+ return `\\H{${e}}`
112
+ case '\u030C':
113
+ return `\\check{${e}}`
114
+ case '\u0327':
115
+ return `\\c{${e}}`
116
+ default:
117
+ return `\\${chr}{${e}}`
118
+ }
119
+ }
120
+ export function parseBox(box) {
121
+ const e = parseOMath(box['m:e'])
122
+ return `\\boxed{${e}}`
123
+ }
124
+
125
+
126
+ export function parseOMath(oMath) {
127
+ if (!oMath) return ''
128
+
129
+ if (Array.isArray(oMath)) {
130
+ return oMath.map(item => parseOMath(item)).join('')
131
+ }
132
+
133
+ const oMathList = []
134
+ const keys = Object.keys(oMath)
135
+ for (const key of keys) {
136
+ if (Array.isArray(oMath[key])) {
137
+ oMathList.push(...oMath[key].map(item => ({ key, value: item })))
138
+ }
139
+ else oMathList.push({ key, value: oMath[key] })
140
+ }
141
+
142
+ oMathList.sort((a, b) => {
143
+ let oA = 0
144
+ if (a.key === 'm:r' && a.value && a.value['a:rPr']) oA = a.value['a:rPr']['attrs']['order']
145
+ else if (a.value[`${a.key}Pr`] && a.value[`${a.key}Pr`]['m:ctrlPr'] && a.value[`${a.key}Pr`]['m:ctrlPr']['a:rPr']) {
146
+ oA = a.value[`${a.key}Pr`] && a.value[`${a.key}Pr`]['m:ctrlPr'] && a.value[`${a.key}Pr`]['m:ctrlPr']['a:rPr'] && a.value[`${a.key}Pr`]['m:ctrlPr']['a:rPr']['attrs']['order']
147
+ }
148
+ let oB = 0
149
+ if (b.key === 'm:r' && b.value && b.value['a:rPr']) oB = b.value['a:rPr']['attrs']['order']
150
+ else if (b.value[`${b.key}Pr`] && b.value[`${b.key}Pr`]['m:ctrlPr'] && b.value[`${b.key}Pr`]['m:ctrlPr']['a:rPr']) {
151
+ oB = b.value[`${b.key}Pr`] && b.value[`${b.key}Pr`]['m:ctrlPr'] && b.value[`${b.key}Pr`]['m:ctrlPr']['a:rPr'] && b.value[`${b.key}Pr`]['m:ctrlPr']['a:rPr']['attrs']['order']
152
+ }
153
+ return oA - oB
154
+ })
155
+
156
+ return oMathList.map(({ key, value }) => {
157
+ if (key === 'm:f') return parseFraction(value)
158
+ if (key === 'm:sSup') return parseSuperscript(value)
159
+ if (key === 'm:sSub') return parseSubscript(value)
160
+ if (key === 'm:rad') return parseRadical(value)
161
+ if (key === 'm:nary') return parseNary(value)
162
+ if (key === 'm:limLow') return parseLimit(value, 'low')
163
+ if (key === 'm:limUpp') return parseLimit(value, 'upp')
164
+ if (key === 'm:d') return parseDelimiter(value)
165
+ if (key === 'm:func') return parseFunction(value)
166
+ if (key === 'm:groupChr') return parseGroupChr(value)
167
+ if (key === 'm:eqArr') return parseEqArr(value)
168
+ if (key === 'm:bar') return parseBar(value)
169
+ if (key === 'm:acc') return parseAccent(value)
170
+ if (key === 'm:borderBox') return parseBox(value)
171
+ if (key === 'm:m') return parseMatrix(value)
172
+ if (key === 'm:r') return parseOMath(value)
173
+ if (key === 'm:t') return value
174
+ return ''
175
+ }).join('')
176
+ }
177
+
178
+ export function latexFormart(latex) {
179
+ return latex.replaceAll(/&lt;/g, '<')
180
+ .replaceAll(/&gt;/g, '>')
181
+ .replaceAll(/&amp;/g, '&')
182
+ .replaceAll(/&apos;/g, "'")
183
+ .replaceAll(/&quot;/g, '"')
184
+ }
package/src/pptxtojson.js CHANGED
@@ -7,10 +7,11 @@ import { getVerticalAlign } from './align'
7
7
  import { getPosition, getSize } from './position'
8
8
  import { genTextBody } from './text'
9
9
  import { getCustomShapePath } from './shape'
10
- import { extractFileExtension, base64ArrayBuffer, getTextByPathList, angleToDegrees, getMimeType, isVideoLink, escapeHtml } from './utils'
10
+ import { extractFileExtension, base64ArrayBuffer, getTextByPathList, angleToDegrees, getMimeType, isVideoLink, escapeHtml, hasValidText } from './utils'
11
11
  import { getShadow } from './shadow'
12
12
  import { getTableBorders, getTableCellParams, getTableRowParams } from './table'
13
13
  import { RATIO_EMUs_Points } from './constants'
14
+ import { findOMath, latexFormart, parseOMath } from './math'
14
15
 
15
16
  export async function parse(file) {
16
17
  const slides = []
@@ -105,6 +106,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
105
106
  let relationshipArray = resContent['Relationships']['Relationship']
106
107
  let layoutFilename = ''
107
108
  let diagramFilename = ''
109
+ let notesFilename = ''
108
110
  const slideResObj = {}
109
111
 
110
112
  if (relationshipArray.constructor === Array) {
@@ -121,6 +123,8 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
121
123
  }
122
124
  break
123
125
  case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide':
126
+ notesFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
127
+ break
124
128
  case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image':
125
129
  case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart':
126
130
  case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink':
@@ -133,6 +137,9 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
133
137
  }
134
138
  }
135
139
  else layoutFilename = relationshipArray['attrs']['Target'].replace('../', 'ppt/')
140
+
141
+ const slideNotesContent = await readXmlFile(zip, notesFilename)
142
+ const note = getNote(slideNotesContent)
136
143
 
137
144
  const slideLayoutContent = await readXmlFile(zip, layoutFilename)
138
145
  const slideLayoutTables = await indexNodes(slideLayoutContent)
@@ -283,9 +290,29 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
283
290
  return {
284
291
  fill: bgColor,
285
292
  elements,
293
+ note,
286
294
  }
287
295
  }
288
296
 
297
+ function getNote(noteContent) {
298
+ let text = ''
299
+ let spNodes = getTextByPathList(noteContent, ['p:notes', 'p:cSld', 'p:spTree', 'p:sp'])
300
+ if (!spNodes) return ''
301
+
302
+ if (spNodes.constructor !== Array) spNodes = [spNodes]
303
+ for (const spNode of spNodes) {
304
+ let rNodes = getTextByPathList(spNode, ['p:txBody', 'a:p', 'a:r'])
305
+ if (!rNodes) continue
306
+
307
+ if (rNodes.constructor !== Array) rNodes = [rNodes]
308
+ for (const rNode of rNodes) {
309
+ const t = getTextByPathList(rNode, ['a:t'])
310
+ if (t) text += t
311
+ }
312
+ }
313
+ return text
314
+ }
315
+
289
316
  // async function getBackground(warpObj) {
290
317
  // const elements = []
291
318
  // const slideLayoutContent = warpObj['slideLayoutContent']
@@ -390,7 +417,12 @@ async function processNodesInSlide(nodeKey, nodeValue, warpObj, source) {
390
417
  json = await processGroupSpNode(nodeValue, warpObj, source)
391
418
  break
392
419
  case 'mc:AlternateContent':
393
- json = await processGroupSpNode(getTextByPathList(nodeValue, ['mc:Fallback']), warpObj, source)
420
+ if (getTextByPathList(nodeValue, ['mc:Fallback', 'p:grpSpPr', 'a:xfrm'])) {
421
+ json = await processGroupSpNode(getTextByPathList(nodeValue, ['mc:Fallback']), warpObj, source)
422
+ }
423
+ else if (getTextByPathList(nodeValue, ['mc:Choice'])) {
424
+ json = await processMathNode(getTextByPathList(nodeValue, ['mc:Choice']))
425
+ }
394
426
  break
395
427
  default:
396
428
  }
@@ -398,7 +430,28 @@ async function processNodesInSlide(nodeKey, nodeValue, warpObj, source) {
398
430
  return json
399
431
  }
400
432
 
433
+ function processMathNode(node) {
434
+ const order = node['attrs']['order']
435
+ const xfrmNode = getTextByPathList(node, ['p:sp', 'p:spPr', 'a:xfrm'])
436
+ const { top, left } = getPosition(xfrmNode, undefined, undefined)
437
+ const { width, height } = getSize(xfrmNode, undefined, undefined)
438
+
439
+ const oMath = findOMath(node)[0]
440
+ const latex = latexFormart(parseOMath(oMath))
441
+
442
+ return {
443
+ type: 'math',
444
+ top,
445
+ left,
446
+ width,
447
+ height,
448
+ latex,
449
+ order,
450
+ }
451
+ }
452
+
401
453
  async function processGroupSpNode(node, warpObj, source) {
454
+ const order = node['attrs']['order']
402
455
  const xfrmNode = getTextByPathList(node, ['p:grpSpPr', 'a:xfrm'])
403
456
  if (!xfrmNode) return null
404
457
 
@@ -438,6 +491,7 @@ async function processGroupSpNode(node, warpObj, source) {
438
491
  width: cx,
439
492
  height: cy,
440
493
  rotate,
494
+ order,
441
495
  elements: elements.map(element => ({
442
496
  ...element,
443
497
  left: (element.left - chx) * ws,
@@ -452,6 +506,7 @@ function processSpNode(node, warpObj, source) {
452
506
  const name = getTextByPathList(node, ['p:nvSpPr', 'p:cNvPr', 'attrs', 'name'])
453
507
  const idx = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'idx'])
454
508
  let type = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
509
+ const order = getTextByPathList(node, ['attrs', 'order'])
455
510
 
456
511
  let slideLayoutSpNode, slideMasterSpNode
457
512
 
@@ -482,17 +537,18 @@ function processSpNode(node, warpObj, source) {
482
537
  else type = 'obj'
483
538
  }
484
539
 
485
- return genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type, warpObj)
540
+ return genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj)
486
541
  }
487
542
 
488
543
  function processCxnSpNode(node, warpObj) {
489
544
  const name = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['name']
490
545
  const type = (node['p:nvCxnSpPr']['p:nvPr']['p:ph'] === undefined) ? undefined : node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['type']
546
+ const order = node['attrs']['order']
491
547
 
492
- return genShape(node, undefined, undefined, name, type, warpObj)
548
+ return genShape(node, undefined, undefined, name, type, order, warpObj)
493
549
  }
494
550
 
495
- function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type, warpObj) {
551
+ function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj) {
496
552
  const xfrmList = ['p:spPr', 'a:xfrm']
497
553
  const slideXfrmNode = getTextByPathList(node, xfrmList)
498
554
  const slideLayoutXfrmNode = getTextByPathList(slideLayoutSpNode, xfrmList)
@@ -518,7 +574,10 @@ function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type, warpOb
518
574
  else txtRotate = rotate
519
575
 
520
576
  let content = ''
521
- if (node['p:txBody']) content = genTextBody(node['p:txBody'], node, slideLayoutSpNode, type, warpObj)
577
+ if (node['p:txBody']) {
578
+ content = genTextBody(node['p:txBody'], node, slideLayoutSpNode, type, warpObj)
579
+ if (!hasValidText(content)) content = ''
580
+ }
522
581
 
523
582
  const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
524
583
  const fillColor = getShapeFill(node, undefined, warpObj) || ''
@@ -546,6 +605,7 @@ function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type, warpOb
546
605
  rotate,
547
606
  vAlign,
548
607
  name,
608
+ order,
549
609
  }
550
610
 
551
611
  if (shadow) data.shadow = shadow
@@ -583,6 +643,8 @@ async function processPicNode(node, warpObj, source) {
583
643
  if (source === 'slideMasterBg') resObj = warpObj['masterResObj']
584
644
  else if (source === 'slideLayoutBg') resObj = warpObj['layoutResObj']
585
645
  else resObj = warpObj['slideResObj']
646
+
647
+ const order = node['attrs']['order']
586
648
 
587
649
  const rid = node['p:blipFill']['a:blip']['attrs']['r:embed']
588
650
  const imgName = resObj[rid]['target']
@@ -647,6 +709,7 @@ async function processPicNode(node, warpObj, source) {
647
709
  height,
648
710
  rotate,
649
711
  blob: videoBlob,
712
+ order,
650
713
  }
651
714
  }
652
715
  if (videoNode && isVdeoLink) {
@@ -658,6 +721,7 @@ async function processPicNode(node, warpObj, source) {
658
721
  height,
659
722
  rotate,
660
723
  src: videoFile,
724
+ order,
661
725
  }
662
726
  }
663
727
  if (audioNode) {
@@ -669,6 +733,7 @@ async function processPicNode(node, warpObj, source) {
669
733
  height,
670
734
  rotate,
671
735
  blob: audioBlob,
736
+ order,
672
737
  }
673
738
  }
674
739
  return {
@@ -680,7 +745,8 @@ async function processPicNode(node, warpObj, source) {
680
745
  rotate,
681
746
  src,
682
747
  isFlipV,
683
- isFlipH
748
+ isFlipH,
749
+ order,
684
750
  }
685
751
  }
686
752
 
@@ -709,6 +775,7 @@ async function processGraphicFrameNode(node, warpObj, source) {
709
775
  }
710
776
 
711
777
  function genTable(node, warpObj) {
778
+ const order = node['attrs']['order']
712
779
  const tableNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl'])
713
780
  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
714
781
  const { top, left } = getPosition(xfrmNode, undefined, undefined)
@@ -878,11 +945,13 @@ function genTable(node, warpObj) {
878
945
  width,
879
946
  height,
880
947
  data,
948
+ order,
881
949
  ...(tbl_border || {}),
882
950
  }
883
951
  }
884
952
 
885
953
  async function genChart(node, warpObj) {
954
+ const order = node['attrs']['order']
886
955
  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
887
956
  const { top, left } = getPosition(xfrmNode, undefined, undefined)
888
957
  const { width, height } = getSize(xfrmNode, undefined, undefined)
@@ -904,6 +973,7 @@ async function genChart(node, warpObj) {
904
973
  height,
905
974
  data: chart.data,
906
975
  chartType: chart.type,
976
+ order,
907
977
  }
908
978
  if (chart.marker !== undefined) data.marker = chart.marker
909
979
  if (chart.barDir !== undefined) data.barDir = chart.barDir
@@ -915,6 +985,7 @@ async function genChart(node, warpObj) {
915
985
  }
916
986
 
917
987
  function genDiagram(node, warpObj) {
988
+ const order = node['attrs']['order']
918
989
  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
919
990
  const { left, top } = getPosition(xfrmNode, undefined, undefined)
920
991
  const { width, height } = getSize(xfrmNode, undefined, undefined)
@@ -935,5 +1006,6 @@ function genDiagram(node, warpObj) {
935
1006
  width,
936
1007
  height,
937
1008
  elements,
1009
+ order,
938
1010
  }
939
1011
  }
@@ -19,11 +19,15 @@ export function simplifyLostLess(children, parentAttributes = {}) {
19
19
  if (!out[child.tagName]) out[child.tagName] = []
20
20
 
21
21
  const kids = simplifyLostLess(child.children || [], child.attributes)
22
- out[child.tagName].push(kids)
23
-
24
- if (Object.keys(child.attributes).length) {
25
- kids.attrs = { order: cust_attr_order++, ...child.attributes }
22
+
23
+ if (typeof kids === 'object') {
24
+ if (!kids.attrs) kids.attrs = { order: cust_attr_order++ }
25
+ else kids.attrs.order = cust_attr_order++
26
26
  }
27
+ if (Object.keys(child.attributes || {}).length) {
28
+ kids.attrs = { ...kids.attrs, ...child.attributes }
29
+ }
30
+ out[child.tagName].push(kids)
27
31
  }
28
32
  for (const child in out) {
29
33
  if (out[child].length === 1) out[child] = out[child][0]
package/src/utils.js CHANGED
@@ -150,4 +150,13 @@ export function toHex(n) {
150
150
  hex = '0' + hex
151
151
  }
152
152
  return hex
153
+ }
154
+
155
+ export function hasValidText(htmlString) {
156
+ if (!DOMParser) return true
157
+
158
+ const parser = new DOMParser()
159
+ const doc = parser.parseFromString(htmlString, 'text/html')
160
+ const text = doc.body.textContent || doc.body.innerText
161
+ return text.trim() !== ''
153
162
  }