I autogenerate some SVG from existing SVGs by copying the existing ones into the generated one.
What happens is that the style of one of the nested SVGs is affecting the other one. From my understanding, this should not happen. Here's an example showing the effect (the blue rectangle shouldn't be filled but is filled because apparently the second SVG's c
class overrides the one of the first SVG):
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30" width="60" height="30">
<svg viewBox="0 0 60 60" width="30" height="30">
<defs>
<style>
.c { fill: none; }
.c1 { color: blue; }
</style>
</defs>
<rect class="c c1" x="10" y="10" width="40" height="40" stroke="currentColor" />
</svg>
<g transform="translate(30 0)">
<svg viewBox="0 0 60 60" width="30" height="30">
<defs>
<style>
.c { fill: currentColor; }
.c2 { color: red; }
</style>
</defs>
<rect class="c c2" x="10" y="10" width="40" height="40" stroke="currentColor" />
</svg>
</g>
</svg>
How can I avoid this? The brute-force solution would be to parse all style classes of the included SVGs and rewrite their names into unique ones, but that seems to be a lot of work. Is there an easier way?
Edit: Further testing has shown that my initial assessment was not quite right. In fact, SVG will ignore all but the last <style>
tag. So renaming classes won't be enough, I would need to collect all styles and put them together in a single <style>
while renaming the classes to something unique. Wow, just wow.
That's basically how the cascade works. Nested svg elements won't create a new CSS scope.
In fact, SVG will ignore all but the last tag. So renaming classes won't be enough
Not really, the last rules just has a higher specificity. See example (it overrides the second stylesheet adding !important
:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30" width="60" height="30">
<svg viewBox="0 0 60 60" width="30" height="30">
<defs>
<style>
.c { fill: none!important; }
.c1, .c2 { color: blue!important; }
</style>
</defs>
<rect class="c c1" x="10" y="10" width="40" height="40" stroke="currentColor" />
</svg>
<g transform="translate(30 0)">
<svg viewBox="0 0 60 60" width="30" height="30">
<defs>
<style>
.c { fill: currentColor; }
.c2 { color: red; }
</style>
</defs>
<rect class="c c2" x="10" y="10" width="40" height="40" stroke="currentColor" />
</svg>
</g>
</svg>
You may convert the nested svgs to <image>
elements with a dataURL.
Ideally you may integrate this conversion with the creation of your combined svg container (e.g as a node script).
let parentSvg = document.querySelector("svg");
function nestedSvgToImage(svg) {
let childSvgs = svg.querySelectorAll("svg");
childSvgs.forEach((child) => {
// convert svg markupt to dataURL
let markup = new XMLSerializer().serializeToString(child);
let dataUrl = svgToDataUrl(markup);
// replace with image
let img = document.createElementNS("http://www.w3.org/2000/svg", "image");
let width = child.width.baseVal.value;
let height = child.height.baseVal.value;
img.setAttribute("href", dataUrl);
img.setAttribute("width", width);
img.setAttribute("height", height);
child.replaceWith(img);
});
}
function svgToDataUrl(markup) {
let prologuePattern = /<\?xml\s+.*?\?>|<!--.*?-->|<!DOCTYPE[^>]*>/gs;
return (
`data:image/svg+xml,` +
markup
//delete xml prologue
.replace(prologuePattern, "")
// remove new lines and tabs
.replace(/[\n\r\t]/g, "")
// remove duplicate whitespace
.replace(/\ {2,}/g, " ")
.replaceAll('"', "'")
.replaceAll("' >", "'>")
.replaceAll(" />", "/>")
.replaceAll("> <", "><")
//encode # for color codes
.replaceAll("#", "%23")
);
}
<h1 class="c2">
Inherits last css rule
</h1>
<style>
.c2 {
color: blue;
}
</style>
<h1 class="c2">
Inherits last css rule
</h1>
<style>
.c2 {
color: orange;
}
</style>
<p><button onclick="nestedSvgToImage(parentSvg)" type="button">Convert</button></p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30">
<svg viewBox="0 0 60 60" width="30" height="30">
<defs>
<clipPath id="clip">
<ellipse cx="50%" cy="50%" rx="20" ry="20" />
</clipPath>
<style>
.c {
fill: none;
}
.c1 {
color: blue;
}
</style>
</defs>
<rect clip-path="url(#clip)" class="c c1" x="10" y="10" width="40" height="40" stroke-width="10" stroke="currentColor" />
</svg>
<g transform="translate(30 0)">
<svg viewBox="0 0 60 60" width="30" height="30">
<defs>
<clipPath id="clip">
<ellipse cx="50%" cy="50%" rx="15" ry="20" />
</clipPath>
<style>
.c {
fill: currentColor;
}
.c2 {
color: red;
}
</style>
</defs>
<rect class="c c2" x="10" y="10" clip-path="url(#clip)" width="40" height="40" stroke="currentColor" />
</svg>
</g>
</svg>
The above example also applies clip-paths
which also introduce issues due to duplicate ids.
Admittedly this may not work very well in a PDF creation process since many generator libraries struggle with dataURLs (... the same applies to CSS support and nested SVGs)
In your case it may be a better option to place your svgs in your layout context (ASCIIDOC) as separate images without merging them to a compound svg file.