Inserting HTML/SVG attribute on a string with RegEx [duplicate] - javascript

This question already has answers here:
RegEx match open tags except XHTML self-contained tags
(35 answers)
Closed 29 days ago.
I have hundreds of strings, in which I conditionally need to add or remove something. To add a bit more context, this is in fact stringified SVGs, where I want to add/remove attributes to certain nodes. Since I don't have the luxury of a DOM at any stage of this process, and both the input and desired output is a string, I want to do this with a string replacement using RegEx.
The logic is: any substring which looks like an SVG element (path, circle etc.). which doesn't have a stroke attribute needs to have it added with a value of none.
Example input:
<svg>
<path d="..." fill="black" />
<path d="..." stroke="black" />
<circle cx="50" cy="50" r="50" fill="black" />
</svg>
Desired output:
<svg>
<path d="..." fill="red" stroke="none" />
<path d="..." stroke="black" />
<circle cx="50" cy="50" r="50" fill="black" stroke="none" />
</svg>
Note that the first <path> and the <circle> has a stroke="none" added, while the other path remains unchanged.
What I've tried so far:
const matcher = /(?<=path)((?!stroke=).)*(?=\/>)/g
const newSvg = oldSvg.replace(matcher, ' $1 stroke=\"none\" ')
Problems
I think this is really close , but the output is wrong: <path " stroke="none" /> (all attributes removed and a " character is added).
This only matches path elements. I tried something like (?<=(path|circle)), but it seems not to be valid.
What am I doing wrong?

It is safer to use a DOM parser in Node.js, see How do I parse a HTML page with Node.js
You can however use nested regex replaces. Here is a solution to your question that supports the shortened tag format <tag attrs />, as well as the close tag format <tag attrs></tag>. Tweak regex2 to support additional tags.
const input = `<svg>
<path d="..." fill="black" />
<path d="..." stroke="black" />
<circle cx="50" cy="50" r="50" fill="black" />
</svg>
<div>Other stuff</div>
<svg>
<path d="..." fill="black" stroke="green"></path>
<path d="..."></path>
<circle cx="50" cy="50" r="50" fill="black"></circle>
</svg>`;
const regex1 = /(<svg>)(.*?)(<\/svg>)/gis;
const regex2 = /<(path|circle)( .*?)( *\/>|> *<\/\1>)/gis;
const regex3 = /\bstroke=["']/i;
let result = input.replace(regex1, (svg, g1, g2, g3) => {
return g1 + g2.replace(regex2, (tag, g1, g2, g3) => {
if(!g2.match(regex3)) {
return '<' + g1 + g2 + ' stroke="none"' + g3;
} else {
return tag;
}
}) + g3;
});
console.log(result);
Output:
<svg>
<path d="..." fill="black" stroke="none" />
<path d="..." stroke="black" />
<circle cx="50" cy="50" r="50" fill="black" stroke="none" />
</svg>
<div>Other stuff</div>
<svg>
<path d="..." fill="black" stroke="green"></path>
<path d="..." stroke="none"></path>
<circle cx="50" cy="50" r="50" fill="black" stroke="none"></circle>
</svg>
Explanation of regex1:
(<svg>) -- capture group 1 with opening tag
(.*?) -- capture group 2: non-greedy scan until:
(<\/svg>) -- capture group 3 with closing tag
Explanation of regex2:
< -- literal <
(path|circle) -- capture group 1 with tag name
( .*?) -- capture group 2: tag attributes, e.g. non-greedy scan until:
( *\/>|> *<\/\1>) -- capture group 3: either self closing tag /> or > and closing tag </tag>
Explanation of regex3 test:
\b -- word boundary
stroke= -- literal text
["'] -- quote or single quote char

Related

Why are svg tagNames lowercase and non-svg uppercase?

Why is one lowercase and the other uppercase ?
console.log("standard :", document.getElementById('link').tagName) // A
console.log("svg : ", document.getElementById('SvgLink').tagName) // a
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" style="background: peachpuff;">
<a id="SvgLink" href="http://perdu.com">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"></circle>
</a>
</svg>
<a id="link" href="http://perdu.com">Link</a>
SVG's original format was XML only. XML is case sensitive. Some of that case sensitivity remains for compatibility reasons when SVG is embedded within HTML documents.
See DOM level 2 core description of tagName
...Note that this is case-preserving in XML, as are all of the operations of the DOM...

CSS style addition has stopped javascript function from working

I created this javascript function to change the colour of guitar string in an SVG. Originally, the SVG had 'stroke' colours defined in the markup and at that point the function worked, so that when I pressed the button, the colour of the string 'e-low' changed.
However, I decided I wanted to add CSS default style to the stroke colour (which you can see at the stop of the code) because I intend to have functionality so that when the button is pressed a second time, the colour returns to the default colour defined in the style section. Since I've added this, and changed the colour in the SVG to 'None', the javascript function has stopped working and the colour doesn't change whatsoever, and I don't know why.
Before I added the css style element
function svgMod() {
var s = document.getElementById("e-low");
s.setAttribute("stroke", "#000000");
}
#e-string,
#b-string,
#g-string,
#d-string,
#a-string,
#e-low {
stroke: #adad8b;
}
<svg xmlns="http://www.w3.org/2000/svg" ......... <path id="e-string" stroke="none" fill="none" stroke-width="2" d="M502.583,13.046v411.966" />
<path id="b-string" stroke="none" fill="none" stroke-width="2.5" d="M472.366,13.046v411.966" />
<path id="g-string" stroke="none" fill="none" stroke-width="3" d="M440.134,13.046v411.966" />
<path id="d-string" stroke="none" fill="none" stroke-width="3.3" d="M405.887,13.046v411.966" />
<path id="a-string" stroke="none" fill="none" stroke-width="3.5" d="M373.655,13.042v411.965" />
<path id="e-low" stroke="none" fill="none" stroke-width="4" d="M341.423,13.046v411.966" />
</svg>
<button class="btn" onclick="svgMod(); return false;">Test 1</button>
Make sure you have the right viewBox and a proper sizing for the SVG element, I just added a random viewBox to see the guitar strings.
You can read more about viewBox in this link
The viewBox attribute allows you to specify that a given set of
graphics stretch to fit a particular container element.
Also as #CBroe mentioned using s.style.stroke = '#000000' fits better to modify the styles of a element.
function svgMod() {
var s = document.getElementById("e-low");
s.style.stroke = "#000000";
}
#e-string,
#b-string,
#g-string,
#d-string,
#a-string,
#e-low {
stroke: #adad8b;
}
<svg xmlns="http://www.w3.org/2000/svg" width="210" viewBox="0 0 600 600">
<path id="e-string" stroke="none" fill="none" stroke-width="2" d="M502.583,13.046v411.966"/>
<path id="b-string" stroke="none" fill="none" stroke-width="2.5" d="M472.366,13.046v411.966"/>
<path id="g-string" stroke="none" fill="none" stroke-width="3" d="M440.134,13.046v411.966"/>
<path id="d-string" stroke="none" fill="none" stroke-width="3.3" d="M405.887,13.046v411.966"/>
<path id="a-string" stroke="none" fill="none" stroke-width="3.5" d="M373.655,13.042v411.965"/>
<path id="e-low" stroke="none" fill="none" stroke-width="4" d="M341.423,13.046v411.966"/>
</svg>
<button class="btn" onclick="svgMod(); return false;">Test 1</button>

Use tag in svg's not working

I'm pretty new to SVG. I have set of icons created using SVG. I'm trying to use <use> tag to render a particular part of SVG. But everything goes in vain. Not able to figure out what's the mistake i'm doing. Here is the code which i tried and also refer the fiddle. You can see that overall svg is rendered, But trying to render particular part of the SVG failed. Any help will be appreciated
<svg width="303px" height="30px" viewBox="0 0 303 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Pivot" transform="translate(253.232000, 6.247500)">
<path d="M0.833,15.4105 L0.833,11.662 L3.7485,11.662 L3.7485,11.2455 L0.833,11.2455 L0.833,7.9135 L3.7485,7.9135 L3.7485,7.497 L0.833,7.497 L0.833,4.5815 L0,4.5815 L0,16.2435 L0.833,16.2435 L0.833,15.827 L3.7485,15.827 L3.7485,15.4105 L0.833,15.4105 Z M18.8036328,16.2435 L4.5815,16.2435 L4.5815,15.6124537 L8.7465,15.6124537 L8.7465,12.7095463 L4.5815,12.7095463 L4.5815,12.0785 L8.7465,12.0785 L8.7465,8.12804634 L4.5815,8.12804634 L4.5815,7.497 L8.7465,7.497 L8.7465,4.5815 L3.7485,4.5815 L3.7485,4.4408921e-16 L19.5755,4.4408921e-16 L19.5755,4.5815 L17.3516962,4.5815 L14.5775,4.5815 L14.5775,7.497 L18.7425,7.497 L18.7425,4.5815 L19.5755,4.5815 L19.5755,16.2435 L18.8036328,16.2435 Z M18.7425,15.6124537 L14.5775,15.6124537 L14.5775,12.7095463 L18.7425,12.7095463 L18.7425,15.6124537 Z M18.7425,12.0785 L14.5775,12.0785 L14.5775,8.12804634 L18.7425,8.12804634 L18.7425,12.0785 Z M13.7445,4.5815 L9.5795,4.5815 L9.5795,7.497 L13.7445,7.497 L13.7445,4.5815 Z M13.7445,8.12804634 L9.5795,8.12804634 L9.5795,12.0785 L13.7445,12.0785 L13.7445,8.12804634 Z M13.7445,12.7095463 L9.5795,12.7095463 L9.5795,15.6124537 L13.7445,15.6124537 L13.7445,12.7095463 Z M3.7485,4.5815 L4.5815,4.5815 L4.5815,16.2435 L3.7485,16.2435 L3.7485,4.5815 Z M0,3.7485 L3.7485,3.7485 L3.7485,4.165 L0,4.165 L0,3.7485 Z" id="Combined-Shape" fill="#AAAAAA"></path>
<rect id="Rectangle-324" fill="#FAC10C" x="0" y="3.7485" width="4.5815" height="12.495"></rect>
<rect id="Rectangle-324-Copy" fill="#FAC10C" transform="translate(11.733102, 2.290750) rotate(90.000000) translate(-11.733102, -2.290750) " x="9.44235203" y="-5.73533321" width="4.5815" height="16.0521664"></rect>
</g>
<g id="Filter" transform="translate(232.407000, 6.247500)">
<rect id="Rectangle-108" fill="#3BA3F8" x="4.73505679e-14" y="0" width="12.9115" height="1.2495"></rect>
<rect id="Rectangle-109" fill="#3BA3F8" x="4.73505679e-14" y="2.499" width="12.9115" height="1.2495"></rect>
<path d="M6.33160096,10.829 L6.45575,10.9678333 L6.57989904,10.829 L6.33160096,10.829 Z M4.8418125,9.163 L4.73505679e-14,3.7485 L12.9115,3.7485 L8.0696875,9.163 L4.8418125,9.163 Z" id="Combined-Shape" fill="#8EC9FB"></path>
<path d="M4.5815,9.163 L7.9135,9.163 L7.9135,18.326 L4.74601819,14.5516554 L4.5815,9.163 Z" id="Rectangle-111" fill="#8EC9FB"></path>
</g>
<g id="Sort" transform="translate(206.167500, 1.666000)">
<rect id="Rectangle-120" fill="#E2A364" x="10.829" y="4.165" width="2.0825" height="17.9095"></rect>
<path d="M15.5828281,14.5775 L17.1391718,15.7502891 L12.3853437,22.0588321 L10.829,20.886043 L15.5828281,14.5775 Z" id="Rectangle-121" fill="#E2A364"></path>
<text id="A" font-family="SFUIDisplay-Semibold, SF UI Display" font-size="11.902719" font-weight="500" letter-spacing="-0.107586779" fill="#19AF5C">
<tspan x="0.4165" y="11">A</tspan>
</text>
<text id="Z" font-family="SFUIDisplay-Semibold, SF UI Display" font-size="11.902719" font-weight="500" letter-spacing="-0.107586779" fill="#19AF5C">
<tspan x="0.4165" y="22.662">Z</tspan>
</text>
</g>
</svg>
<svg>
<use xlink:href="Pivot"></use>
</svg>
You want to know why the "Pivot" element is not showing up in your second SVG? Is that right?
The reason is because you are not referencing it correctly. You should have written:
<svg>
<use xlink:href="#Pivot"></use>
</svg>
Note the missing hash ("#") symbol.

Changing colour using Javascript [duplicate]

I created a SVG file contains 5 polygons, then I need to embed Javascript so 4 of the polygons' color changes to Red when mouseover, and when mouseout, the color changes to Green. I tried to write the code but it didn't work, what could be the problem? Thanks for help and tips!
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="26cm" height="24cm" viewBox="0 0 2600 2400" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink= "http://www.w3.org/1999/xlink">
<script type="text/javascript">
<![CDATA[
document.getElementById("test").onmouseover = function(){changeColor()};
function changeColor() {
document.getElementById("test").style.color = "red";
}
document.getElementById("test").onmouseout = function(){changeColor()};
function changeColor() {
document.getElementById("test").style.color = "green";
}
]]>
</script>
<circle cx="1600" cy="700" r="600" fill="yellow" stroke="black" stroke-width="3"/>
<ellipse id="test" cx="1300" cy="500" rx="74" ry="120" fill="blue" stroke="black" stroke-width="3" onmouseover="javascript:red();" onmouseout="javascript:green();"/>
<ellipse id="test" cx="1850" cy="500" rx="74" ry="120" fill="blue" stroke="black" stroke-width="3" onmouseover="javascript:red();" onmouseout="javascript:green();"/>
<rect id="test" x="1510" y="650" width="160" height="160" fill="blue" stroke="black" stroke-width="3" onmouseover="javascript:red();" onmouseout="javascript:green();"/>
<polygon id="test" points="1320,800 1370,1080 1820,1080 1870,800 1820,1000 1370,1000" name="mouth" fill="blue" stroke="black" stroke-width="3" onmouseover="javascript:red();" onmouseout="javascript:green();"/>
</svg>
For what you are doing I would recommend using pure CSS.
Here is some working code.
svg:hover .recolor {
fill: red;
}
As you see, you can just use the :hover event in CSS to recolor the necessary elements. And set them to your default color (green), which will take effect when the user is not hovered.
You have various errors
you've two functions called changeColor, functions must have unique names
SVG does not use color to colour elements, instead it uses fill (and stroke).
id values must be unique, you probably want to replace id by class and then use getElementsByClassName instead of getElementById. If you do that you'll need to cope with more than one element though. I've not completed that part, you should try it yourself so you understand what's going on.
I've removed all but one id from my version so you can see it working on the left eye.
document.getElementById("test").onmouseover = function(){changeColor()};
function changeColor() {
document.getElementById("test").style.fill = "red";
}
document.getElementById("test").onmouseout = function(){changeColor2()};
function changeColor2() {
document.getElementById("test").style.fill = "green";
}
<svg width="26cm" height="24cm" viewBox="0 0 2600 2400" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink= "http://www.w3.org/1999/xlink">
<circle cx="1600" cy="700" r="600" fill="yellow" stroke="black" stroke-width="3"/>
<ellipse id="test" cx="1300" cy="500" rx="74" ry="120" fill="blue" stroke="black" stroke-width="3"/>
<ellipse cx="1850" cy="500" rx="74" ry="120" fill="blue" stroke="black" stroke-width="3" />
<rect x="1510" y="650" width="160" height="160" fill="blue" stroke="black" stroke-width="3" />
<polygon points="1320,800 1370,1080 1820,1080 1870,800 1820,1000 1370,1000" name="mouth" fill="blue" stroke="black" stroke-width="3"/>
</svg>

SVG donut-shape with access of both circles

I want to create an SVG donut shape (circle with another empty circle inside). I want to be able to access & resize both circles, eg via their id attributes. This will allow for animation.
I have considered three approaches but none are that great:
complex path: does not allow for access of the inner circle via #id
outline stroke: possible but complicated for my purpose (would have to reposition as I increase stroke)
clippath/mask: Doesn't work like a compound path, only an outer box
Is there a way of doing this?
Probably the easiest way would be with masks.
If you are working with a set of discrete donut sizes, you could use CSS and a mask for each size:
<svg width="500" height="500">
<defs>
<mask id="bigmask">
<rect width="100%" height="100%" fill="white"/>
<circle cx="250" cy="250" r="50"/>
</mask>
<mask id="smallmask">
<circle cx="250" cy="250" r="150" fill="white"/>
<circle cx="250" cy="250" r="100"/>
</mask>
</defs>
<circle id="donut" cx="250" cy="250" r="200" mask="url(#bigmask)"/>
</svg>
CSS:
#donut:hover
{
mask: url(#smallmask);
}
Demo here
Unfortunately you can't modify the size of circles with CSS. "r" is not (yet) a property that can be manipulated with CSS. So you will need to either use SMIL (SVG) animation, or manipulate your mask circles with javascript:
<svg width="500" height="500">
<defs>
<mask id="donutmask">
<circle id="outer" cx="250" cy="250" r="200" fill="white"/>
<circle id="inner" cx="250" cy="250" r="50"/>
</mask>
</defs>
<circle id="donut" cx="250" cy="250" r="200" mask="url(#donutmask)"/>
</svg>
JS
$("#donut").mouseenter(function(evt) {
$("#outer").attr("r", 100 + Math.random() * 100);
$("#inner").attr("r", 100 - Math.random() * 50);
});
Demo here
Although BigBadaboom's answer is the best way IMO, if you want to use a compound path, it's possible to animate by rewriting the path's d attribute each frame like this:
// get svg path coordinates for a ring
ring:function(x, y, ir, or) {
var path =
'M'+x+' '+(y+or)+'A'+or+' '+or+' 0 1 1 '+(x+0.001)+' '+(y+or) // outer
+ 'M'+x+' '+(y+ir)+'A'+ir+' '+ir+' 0 1 0 '+(x-0.001)+' '+(y+ir) // inner
;
return path;
}

Categories