Home > Software design >  Set fill-opacity of various paths to 0 on an SVG file through XSLT
Set fill-opacity of various paths to 0 on an SVG file through XSLT

Time:10-20

I would like to set the fill opacity of all paths whose id starts with Invisible to 0.0, below the SVG.

Original SVG (extract)

<g id="Section-svg">

<path d="M150 0 L75 200 L225 200 Z" stroke="rgb(100, 100, 100)" style="fill: rgb(100, 100, 100); stroke-width: 0.3; stroke-linejoin: round; stroke-linecap: round; stroke rgb(100, 100, 100);"/>

<g id="symbols-svg">
<g id="Invisible2-svg" transform="translate(250, 90)" >
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Invisible2" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.9; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);" />
</g>

<g id="Invisible3-svg" transform="translate(250, 90)" >
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Invisible3" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.9; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);" />
</g>

</g>
</g>

Here is the XSL I used , I tried editing what I could find at How to modify a SVG attribute using XSLT

XSL for the relative task

 <xsl:template match="svg:*[@id[starts-with(., 'Invisible')]]">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:attribute name="fill-opacity">0.0</xsl:attribute> 
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:copy>
  </xsl:template> 

Yet I only get the wanted paths duplicated, and with fill-opacity not as a style but as something that does not have real effect on the path.

Resulting (wrong) SVG

<g id="Section-svg">

<path d="M150 0 L75 200 L225 200 Z" stroke="rgb(100, 100, 100)" style="fill: rgb(100, 100, 100); stroke-width: 0.3; stroke-linejoin: round; stroke-linecap: round; stroke rgb(100, 100, 100);"/>

<g id="symbols-svg">
<g id="Invisible2-svg" transform="translate(250, 90)" >
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Invisible2" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.9; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);" fill-opacity="0.0;" />
</g>

<g id="Invisible3-svg" transform="translate(250, 90)" >
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Invisible3" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.9; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);" fill-opacity="0.0;" />
</g>
</g>
</g>

Wanted result SVG

<g id="Section-svg">

<path d="M150 0 L75 200 L225 200 Z" stroke="rgb(100, 100, 100)" style="fill: rgb(100, 100, 100); stroke-width: 0.3; stroke-linejoin: round; stroke-linecap: round; stroke rgb(100, 100, 100);"/>
<g id="symbols-svg">

<g id="Invisible2-svg" transform="translate(250, 90)" >
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Invisible2" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.0; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);" />
</g>

<g id="Invisible3-svg" transform="translate(250, 90)" >
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Invisible3" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.0; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);" />
</g>
</g>
</g>

I also tried matching the path and giving it the style I would like it to have

XSL

 <xsl:template match="svg:path[@id[starts-with(., 'Invisible')]]">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
<path style="fill: rgb(0, 15, 60); stroke-width: 0.1; fill-opacity: 0.0; stroke-opacity: 0.0; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);">
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:copy>
  </xsl:template> 

but both NotePad and VBA said the resulting XSL is not well formed.

Nor did this work

XSL

 <xsl:template match="svg:*[@id[starts-with(., 'Invisible')]]">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:attribute style="fill-opacity">0.0</xsl:attribute> 
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:copy>
  </xsl:template> 
  

Could someone please advise?

CodePudding user response:

If I am guessing correctly, you want to change the existing fill-opacity value inside the existing style attribute. If so, try something like:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:svg="http://www.w3.org/2000/svg">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="svg:*[starts-with(@id, 'Invisible')]/@style">
    <xsl:attribute name="style">
        <xsl:value-of select="substring-before(., 'fill-opacity:')" />
        <xsl:text>fill-opacity: 0.0;</xsl:text>
        <xsl:value-of select="substring-after(substring-after(., 'fill-opacity:'), ';')" />
    </xsl:attribute> 
</xsl:template> 
  
</xsl:stylesheet>

CodePudding user response:

You've got two xsl:copy elements inside the template that matches "Invisible" elements; hence the duplication. You want to copy the element once, copy its attributes, then create the new fill-opacity attribute, then apply templates to copy child nodes (and descendants, recursively).

Try this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:svg="http://www.w3.org/2000/svg"
    exclude-result-prefixes="svg"
    version="1.0">
  <xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes"/>

  <xsl:template match="svg:*[@id[starts-with(., 'Invisible')]]">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:attribute name="fill-opacity">0.0</xsl:attribute> 
      <xsl:apply-templates select="node()"/>
    </xsl:copy>
  </xsl:template> 
  
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

BTW your well-formedness error is because your SVG path element is not closed.

CodePudding user response:

Here's a refinement of Michael's answer, which will work correctly even if the element has no existing style attribute, or has a style attribute which doesn't already contain a fill-opacity property.

It works slightly differently in that it has a template that matches an element whose id attribute starts with Invisible, rather than directly matching the style attribute of such an element.

The template copies the "invisible" element, and its attributes, and then constructs a new style attribute that starts with fill-opacity:0.0;, and then finishes in one of two different ways: if there was an existing style containing a fill-opacity property, then it copies the parts before and after that fill-opacity property, but otherwise it copies the entire value of the style attribute.

The reason for doing it in two different ways is that the functions substring-before and substring-after will return empty strings if the substring being sought (i.e. fill-opacity: is not in the string, and that would have the effect of erasing all the other CSS properties in the style attribute.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*[starts-with(@id, 'Invisible')]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:attribute name="style">
                <xsl:text>fill-opacity: 0.0;</xsl:text>
                <xsl:choose>
                    <xsl:when test="contains(@style, 'fill-opacity')">
                        <!-- style already contains a fill-opacity property, so
                        it's safe to use substring-before and substring-after to 
                        extract the preceding and following CSS properties -->
                        <xsl:value-of select="
                            concat(
                                substring-before(
                                    @style, 
                                    'fill-opacity:'
                                ),
                                substring-after(
                                    substring-after(
                                        @style, 
                                        'fill-opacity:'
                                    ), 
                                    ';'
                                )
                            )
                        "/>
                    </xsl:when>
                    <xsl:otherwise>
                        <!-- style had no existing fill-opacity property
                        so just copy the whole text including any other CSS properties -->
                        <xsl:value-of select="@style"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

  • Related