diff --git a/packages/shared/src/build/buildLucideSvg.ts b/packages/shared/src/build/buildLucideSvg.ts index f2bc8b8fc..fa19192ca 100644 --- a/packages/shared/src/build/buildLucideSvg.ts +++ b/packages/shared/src/build/buildLucideSvg.ts @@ -1,10 +1,21 @@ import buildLucideIconNode from './buildLucideIconNode'; import type { LucideBuildParams, LucideIconData, LucideIconNode } from './types'; +const entities = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', +} as const; + +const escapeAttribute = (value: string) => + value.replace(/[&<>"']/g, (char) => entities[char as keyof typeof entities]); + const buildDomNode = ([tagName, attributes, children = []]: LucideIconNode): string => `<${tagName} ${Object.entries(attributes) .filter(([, value]) => value !== undefined && value !== null) - .map(([attrName, value]) => `${attrName}="${String(value)}"`) + .map(([attrName, value]) => `${attrName}="${escapeAttribute(String(value))}"`) .join(' ')}>${children?.map((child) => buildDomNode(child)).join('')}`; /** diff --git a/packages/shared/tests/buildLucideSvg.spec.ts b/packages/shared/tests/buildLucideSvg.spec.ts new file mode 100644 index 000000000..896a7464e --- /dev/null +++ b/packages/shared/tests/buildLucideSvg.spec.ts @@ -0,0 +1,14 @@ +import { describe, it, expect } from 'vitest'; +import buildLucideSvg from '../src/build/buildLucideSvg'; + +const icon = { name: 'dot', size: 24, node: [['circle', { cx: 12, cy: 12, r: 1 }]] } as const; + +describe('buildLucideSvg', () => { + it('should escape special characters in attribute values', () => { + const svg = buildLucideSvg(icon as any, { + attributes: { 'data-label': 'a\'s "quoted" & c' }, + }); + + expect(svg).toContain('data-label="a's "quoted" <b> & c"'); + }); +});