Skip to content

Commit f289bab

Browse files
committed
address bugbot
1 parent fe5b0cf commit f289bab

3 files changed

Lines changed: 83 additions & 42 deletions

File tree

apps/sim/lib/pptx-renderer/model/nodes/shape-node.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -70,28 +70,6 @@ function parseParagraph(pNode: SafeXmlNode): TextParagraph {
7070
const pPr = pNode.child('pPr')
7171
const level = pPr.numAttr('lvl') ?? 0
7272

73-
const runs: TextRun[] = []
74-
75-
// Regular runs (a:r)
76-
for (const rNode of pNode.children('r')) {
77-
const rPr = rNode.child('rPr')
78-
const tNode = rNode.child('t')
79-
runs.push({
80-
text: tNode.text(),
81-
properties: rPr.exists() ? rPr : undefined,
82-
})
83-
}
84-
85-
// Line breaks (a:br) — treated as runs with newline text
86-
// Field codes (a:fld) — treated as runs with their display text
87-
for (const child of pNode.allChildren()) {
88-
if (child.localName === 'br') {
89-
// a:br nodes are interspersed with a:r nodes, but since we iterate
90-
// children separately, we need a combined approach. We handle this
91-
// by re-scanning all children in order below.
92-
}
93-
}
94-
9573
// Re-scan in document order to get correct interleaving of r, br, fld
9674
const orderedRuns: TextRun[] = []
9775
for (const child of pNode.allChildren()) {
@@ -122,7 +100,7 @@ function parseParagraph(pNode: SafeXmlNode): TextParagraph {
122100
const endParaRPrNode = pNode.child('endParaRPr')
123101
return {
124102
properties: pPr.exists() ? pPr : undefined,
125-
runs: orderedRuns.length > 0 ? orderedRuns : runs,
103+
runs: orderedRuns,
126104
level,
127105
endParaRPr: endParaRPrNode.exists() ? endParaRPrNode : undefined,
128106
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { buildPresentation } from '@/lib/pptx-renderer/model/presentation'
6+
import type { PptxFiles } from '@/lib/pptx-renderer/parser/zip-parser'
7+
8+
function createFiles(presentation: string): PptxFiles {
9+
return {
10+
contentTypes: '<Types />',
11+
presentation,
12+
presentationRels: `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
13+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml" />
14+
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide2.xml" />
15+
</Relationships>`,
16+
slides: new Map([
17+
['ppt/slides/slide1.xml', createSlideXml()],
18+
['ppt/slides/slide2.xml', createSlideXml()],
19+
]),
20+
slideRels: new Map([
21+
['ppt/slides/_rels/slide1.xml.rels', '<Relationships />'],
22+
['ppt/slides/_rels/slide2.xml.rels', '<Relationships />'],
23+
]),
24+
slideLayouts: new Map(),
25+
slideLayoutRels: new Map(),
26+
slideMasters: new Map(),
27+
slideMasterRels: new Map(),
28+
themes: new Map(),
29+
media: new Map(),
30+
charts: new Map(),
31+
chartStyles: new Map(),
32+
chartColors: new Map(),
33+
diagramDrawings: new Map(),
34+
}
35+
}
36+
37+
function createPresentationXml(markers = ''): string {
38+
return `<p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" ${markers}>
39+
<p:sldSz cx="9144000" cy="5143500" />
40+
<p:sldIdLst>
41+
<p:sldId id="256" r:id="rId2" />
42+
<p:sldId id="257" r:id="rId1" />
43+
</p:sldIdLst>
44+
</p:presentation>`
45+
}
46+
47+
function createSlideXml(): string {
48+
return `<p:sld xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
49+
<p:cSld>
50+
<p:spTree>
51+
<p:nvGrpSpPr><p:cNvPr id="1" name="" /><p:cNvGrpSpPr /><p:nvPr /></p:nvGrpSpPr>
52+
<p:grpSpPr />
53+
</p:spTree>
54+
</p:cSld>
55+
</p:sld>`
56+
}
57+
58+
describe('buildPresentation', () => {
59+
it('does not treat the standard wps namespace prefix as WPS Office', () => {
60+
const presentation = buildPresentation(
61+
createFiles(
62+
createPresentationXml(
63+
'xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"'
64+
)
65+
)
66+
)
67+
68+
expect(presentation.isWps).toBe(false)
69+
})
70+
71+
it('orders slides by relationship id instead of numeric slide id', () => {
72+
const presentation = buildPresentation(createFiles(createPresentationXml()))
73+
74+
expect(presentation.slides.map((slide) => slide.slidePath)).toEqual([
75+
'ppt/slides/slide2.xml',
76+
'ppt/slides/slide1.xml',
77+
])
78+
})
79+
})

apps/sim/lib/pptx-renderer/model/presentation.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,31 +48,15 @@ function relsPathFor(filePath: string): string {
4848
return `${dir}/_rels/${fileName}.rels`
4949
}
5050

51-
/**
52-
* Extract ordered slide rId list from presentation.xml.
53-
* Reads `p:sldIdLst > p:sldId` elements and returns their r:id attributes in order.
54-
*/
55-
function _getSlideOrder(presRoot: SafeXmlNode): string[] {
56-
const sldIdLst = presRoot.child('sldIdLst')
57-
const rIds: string[] = []
58-
for (const sldId of sldIdLst.children('sldId')) {
59-
const rId = sldId.attr('id') ?? sldId.attr('r:id')
60-
if (rId) rIds.push(rId)
61-
}
62-
return rIds
63-
}
64-
6551
/**
6652
* Detect WPS (Kingsoft Office / WPS Office) by checking for known markers
6753
* in the presentation XML string.
6854
*/
6955
function detectWps(presentationXml: string): boolean {
70-
// WPS adds its own namespace or processing instructions
7156
return (
72-
presentationXml.includes('wps') ||
73-
presentationXml.includes('kso') ||
74-
presentationXml.includes('Kingsoft') ||
75-
presentationXml.includes('WPS')
57+
/\bKingsoft\b/i.test(presentationXml) ||
58+
/\bWPS Office\b/i.test(presentationXml) ||
59+
/xmlns:[\w.-]*kso[\w.-]*=/i.test(presentationXml)
7660
)
7761
}
7862

0 commit comments

Comments
 (0)