Skip to content

Commit 1b6b7bc

Browse files
fix(svg): replace fragile string-split block SVG parsing with DOM parsing (#5605)
1 parent 4e2993f commit 1b6b7bc

File tree

1 file changed

+40
-25
lines changed

1 file changed

+40
-25
lines changed

js/activity.js

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,32 +1331,47 @@ class Activity {
13311331
if (!SPECIALINPUTS.includes(this.blocks.blockList[i].name)) {
13321332
svg += extractSVGInner(rawSVG);
13331333
} else {
1334-
// Keep existing fragile logic for now
1335-
parts = rawSVG.split("><");
1336-
1337-
for (let p = 1; p < parts.length; p++) {
1338-
// FIXME: This is fragile.
1339-
if (p === 1) {
1340-
svg += "<" + parts[p] + "><";
1341-
} else if (p === 2) {
1342-
// skip filter
1343-
} else if (p === 3) {
1344-
svg += parts[p].replace("filter:url(#dropshadow);", "") + "><";
1345-
} else if (p === 5) {
1346-
// Add block value to SVG between tspans
1347-
if (typeof this.blocks.blockList[i].value === "string") {
1348-
svg += parts[p] + ">" + _(this.blocks.blockList[i].value) + "<";
1349-
} else {
1350-
svg += parts[p] + ">" + this.blocks.blockList[i].value + "<";
1351-
}
1352-
} else if (p === parts.length - 2) {
1353-
svg += parts[p] + ">";
1354-
} else if (p === parts.length - 1) {
1355-
// skip final </svg>
1356-
} else {
1357-
svg += parts[p] + "><";
1358-
}
1334+
// Safer SVG manipulation using DOM instead of string splitting
1335+
const parser = new DOMParser();
1336+
const doc = parser.parseFromString(rawSVG, "image/svg+xml");
1337+
1338+
// remove dropshadow filter if present
1339+
const filtered = doc.querySelector('[style*="filter:url(#dropshadow)"]');
1340+
if (filtered) {
1341+
filtered.style.filter = "";
1342+
}
1343+
1344+
// Find correct tspan to inject value (matches previous behaviour)
1345+
let target = null;
1346+
1347+
// 1) Prefer empty tspan (most block SVGs reserve this for value)
1348+
target = Array.from(doc.querySelectorAll("text tspan")).find(
1349+
t => !t.textContent || t.textContent.trim() === ""
1350+
);
1351+
1352+
// 2) Otherwise fallback to last tspan
1353+
if (!target) {
1354+
const tspans = doc.querySelectorAll("text tspan");
1355+
if (tspans.length) target = tspans[tspans.length - 1];
1356+
}
1357+
1358+
// 3) Final fallback to text node
1359+
if (!target) {
1360+
target = doc.querySelector("text");
1361+
}
1362+
1363+
if (target) {
1364+
const val = this.blocks.blockList[i].value;
1365+
target.textContent = typeof val === "string" ? _(val) : val;
13591366
}
1367+
1368+
// serialize without outer <svg> wrapper (matches previous behavior)
1369+
let serialized = new XMLSerializer().serializeToString(doc.documentElement);
1370+
1371+
// remove outer svg tags because original code skipped them
1372+
serialized = serialized.replace(/^<svg[^>]*>/, "").replace(/<\/svg>$/, "");
1373+
1374+
svg += serialized;
13601375
}
13611376

13621377
svg += "</g>";

0 commit comments

Comments
 (0)