@@ -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 ( / ^ < s v g [ ^ > ] * > / , "" ) . replace ( / < \/ s v g > $ / , "" ) ;
1373+
1374+ svg += serialized ;
13601375 }
13611376
13621377 svg += "</g>" ;
0 commit comments