Skip to content

Commit 38a8bf6

Browse files
Merge commit from fork
* fix: handle empty quoted strings in CSS selector splitting * add unit tests and special case for empty selectors * remove unterminated quotes special case --------- Co-authored-by: Lukas Holländer <lukas.hollaender@yworks.com>
1 parent 666038d commit 38a8bf6

2 files changed

Lines changed: 52 additions & 6 deletions

File tree

‎src/context/stylesheets.ts‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,14 @@ export class StyleSheets {
8888
}
8989
}
9090

91-
private static splitSelectorAtCommas(selectorText: string): string[] {
91+
public static splitSelectorAtCommas(selectorText: string): string[] {
92+
if (selectorText.trim().length === 0) {
93+
return []
94+
}
95+
9296
const initialRegex = /,|["']/g
93-
const closingDoubleQuotesRegex = /[^\\]["]/g
94-
const closingSingleQuotesRegex = /[^\\][']/g
97+
const closingDoubleQuotesRegex = /\\[\s\S]|"/g
98+
const closingSingleQuotesRegex = /\\[\s\S]|'/g
9599
const parts = []
96100

97101
let state: 'initial' | 'withinQuotes' = 'initial'
@@ -124,11 +128,14 @@ export class StyleSheets {
124128
case 'withinQuotes':
125129
closingQuotesRegex.lastIndex = i
126130
match = closingQuotesRegex.exec(selectorText)
127-
if (match) {
131+
if (!match) {
132+
i = selectorText.length
133+
} else {
128134
i = closingQuotesRegex.lastIndex
129-
state = 'initial'
135+
if (match[0].length === 1) {
136+
state = 'initial'
137+
}
130138
}
131-
// else this is a syntax error - omit the last part...
132139
break
133140
}
134141
}

‎test/style-sheets/style-sheets.spec.ts‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
import { describe, it, expect } from 'vitest'
22
import { svg2pdf } from '../../src/svg2pdf'
3+
import { StyleSheets } from '../../src/context/stylesheets'
34
import jsPDF from 'jspdf'
45
import { loadSvg } from '../utils/loadSvg'
56

67
describe('style-sheets', () => {
8+
it('splits selectors at commas outside quoted attribute values', () => {
9+
expect(
10+
StyleSheets.splitSelectorAtCommas(
11+
'rect, [data-value="one,two"], [data-value=\'three,four\'], circle'
12+
)
13+
).toEqual(['rect', '[data-value="one,two"]', "[data-value='three,four']", 'circle'])
14+
})
15+
16+
it('trims selectors and preserves commas in escaped quoted values', () => {
17+
expect(StyleSheets.splitSelectorAtCommas(' .first , [title="a\\",b"], .last ')).toEqual([
18+
'.first',
19+
'[title="a\\",b"]',
20+
'.last'
21+
])
22+
})
23+
24+
it('returns an empty selector for an empty string', () => {
25+
expect(StyleSheets.splitSelectorAtCommas('')).toEqual([])
26+
expect(StyleSheets.splitSelectorAtCommas(' ')).toEqual([])
27+
})
28+
29+
it('preserves empty strings inside selectors', () => {
30+
expect(StyleSheets.splitSelectorAtCommas('[href=""], [target="_blank"]')).toEqual([
31+
'[href=""]',
32+
'[target="_blank"]'
33+
])
34+
})
35+
36+
it('keeps invalid selector fragments when splitting', () => {
37+
expect(StyleSheets.splitSelectorAtCommas('rect,,[invalid, .circle')).toEqual([
38+
'rect',
39+
'',
40+
'[invalid',
41+
'.circle'
42+
])
43+
expect(StyleSheets.splitSelectorAtCommas('[title="unterminated, .last')).toEqual([])
44+
})
45+
746
it('style-sheets', async () => {
847
const { svgElement, width, height } = await loadSvg('/test/style-sheets/style-sheets.svg')
948
const pdf = new jsPDF(width > height ? 'l' : 'p', 'pt', [width, height])

0 commit comments

Comments
 (0)