Skip to content

Commit 7435846

Browse files
committed
fix(): use DOMParser with "image/svg+xml" to parse svg
1 parent a7fd3cf commit 7435846

File tree

3 files changed

+79
-19
lines changed

3 files changed

+79
-19
lines changed

bricks/icons/src/antd-icon/index.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ describe("eo-antd-icon", () => {
3535
height="1em"
3636
viewBox="64 64 896 896"
3737
width="1em"
38+
xmlns="http://www.w3.org/2000/svg"
3839
>
3940
<defs>
4041
<style />
@@ -77,6 +78,7 @@ describe("eo-antd-icon", () => {
7778
height="1em"
7879
viewBox="64 64 896 896"
7980
width="1em"
81+
xmlns="http://www.w3.org/2000/svg"
8082
>
8183
<path
8284
d="M148.2 674.6z"
@@ -134,6 +136,7 @@ describe("eo-antd-icon", () => {
134136
height="1em"
135137
viewBox="64 64 896 896"
136138
width="1em"
139+
xmlns="http://www.w3.org/2000/svg"
137140
>
138141
<path
139142
d="M148.2 674.6z"
@@ -221,6 +224,7 @@ describe("eo-antd-icon", () => {
221224
height="1em"
222225
viewBox="64 64 896 896"
223226
width="1em"
227+
xmlns="http://www.w3.org/2000/svg"
224228
>
225229
<defs>
226230
<style />

bricks/icons/src/shared/SvgCache.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,15 @@ export function constructSvgElement(
2929
retryable: boolean,
3030
options?: ResolveIconOptions
3131
): SVGResult | null {
32-
const div = document.createElement("div");
33-
div.innerHTML = content;
34-
35-
const svg = div.firstElementChild;
36-
if (svg?.tagName?.toLowerCase() !== "svg")
37-
return retryable ? CACHEABLE_ERROR : null;
38-
3932
if (!parser) parser = new DOMParser();
40-
const doc = parser.parseFromString(svg.outerHTML, "text/html");
41-
42-
const svgEl = doc.body.querySelector("svg");
43-
if (!svgEl) return retryable ? CACHEABLE_ERROR : null;
33+
// Requires `xmlns`for the SVG to be parsed correctly
34+
const refinedContent = content.includes("xmlns=")
35+
? content
36+
: content.replace(/(<svg)(\s)/i, '$1 xmlns="http://www.w3.org/2000/svg"$2');
37+
const doc = parser.parseFromString(refinedContent, "image/svg+xml");
38+
const svgEl = doc.documentElement;
39+
if (!(svgEl instanceof SVGSVGElement))
40+
return retryable ? CACHEABLE_ERROR : null;
4441

4542
const titles = svgEl.querySelectorAll("title");
4643
for (const title of titles) {

bricks/icons/src/svg-icon/index.spec.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@ import { describe, test, expect } from "@jest/globals";
22
import "./index.js";
33
import type { SvgIcon } from "./index.js";
44

5-
(global as any).fetch = jest.fn(() =>
6-
Promise.resolve({
7-
ok: true,
8-
text: () =>
9-
Promise.resolve(
10-
`<?xml version="1.0" encoding="UTF-8"?>
5+
const svgContent = `<?xml version="1.0" encoding="UTF-8"?>
116
<svg width="15px" height="17px" viewBox="0 0 15 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
127
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
138
<g transform="translate(-1804.000000, -58.000000)" stroke="#595959">
@@ -17,8 +12,12 @@ import type { SvgIcon } from "./index.js";
1712
</g>
1813
</g>
1914
</g>
20-
</svg>`.replace(/>\s+</g, "><")
21-
),
15+
</svg>`.replace(/>\s+</g, "><");
16+
17+
(global as any).fetch = jest.fn(() =>
18+
Promise.resolve({
19+
ok: true,
20+
text: () => Promise.resolve(svgContent),
2221
})
2322
);
2423

@@ -105,4 +104,64 @@ describe("eo-svg-icon", () => {
105104
(element as any)._render();
106105
expect(fetch).not.toBeCalled();
107106
});
107+
108+
test("use svg content", async () => {
109+
const element = document.createElement("eo-svg-icon") as SvgIcon;
110+
element.svgContent = svgContent;
111+
112+
expect(element.shadowRoot).toBeFalsy();
113+
document.body.appendChild(element);
114+
expect(element.shadowRoot).toBeTruthy();
115+
await (global as any).flushPromises();
116+
expect(element.shadowRoot?.childNodes).toMatchInlineSnapshot(`
117+
NodeList [
118+
<style>
119+
icons.shadow.css
120+
</style>,
121+
<svg
122+
height="1em"
123+
version="1.1"
124+
viewBox="0 0 15 17"
125+
width="1em"
126+
xmlns="http://www.w3.org/2000/svg"
127+
xmlns:xlink="http://www.w3.org/1999/xlink"
128+
>
129+
<g
130+
fill="none"
131+
fill-rule="evenodd"
132+
stroke="none"
133+
stroke-width="1"
134+
>
135+
<g
136+
stroke="currentColor"
137+
transform="translate(-1804.000000, -58.000000)"
138+
>
139+
<g
140+
transform="translate(1805.000000, 59.000000)"
141+
>
142+
<circle
143+
cx="6.512"
144+
cy="3.552"
145+
r="3.552"
146+
/>
147+
<path
148+
d="M10.448,8.184 Z"
149+
stroke-linecap="square"
150+
/>
151+
</g>
152+
</g>
153+
</g>
154+
</svg>,
155+
]
156+
`);
157+
document.body.removeChild(element);
158+
expect(element.shadowRoot?.childNodes.length).toBe(0);
159+
160+
// Re-connect
161+
document.body.appendChild(element);
162+
await (global as any).flushPromises();
163+
expect(element.shadowRoot?.childNodes.length).toBe(2);
164+
document.body.removeChild(element);
165+
expect(element.shadowRoot?.childNodes.length).toBe(0);
166+
});
108167
});

0 commit comments

Comments
 (0)