viewing svg sprites with xslt - xslt

My team's designer has us using SVG "sprites" in our application. I'm want to be able to see all the available images. I was going to parse the XML and build something on the back end but then I thought about XSLT. I'd like to have an XSLT file that parses the SVG and creates a list of images. I'm close to getting it... here's what I have.
Sample SVG (although I also tried the sample file in the post):
<?xml-stylesheet type="text/xsl" href="/pages/sprites.xslt" ?>
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="fitness" viewBox="0 0 64 64">
<g fill="none" fill-rule="evenodd" stroke-width="2" transform="translate(5 3)" stroke-linecap="square">
<path d="M5.8,27.0664159 L5.8,10.6333223 C5.8,4.7607008 10.5607008,0 16.4333223,0 L16.4333445,0 C22.3059668,0 27.0666667,4.7607008 27.0666667,10.6333223 L27.0666667,47.3666778 C27.0666667,53.2393002 31.8273665,58 37.699989,58 L37.7000111,58 C43.5726335,58 48.3333333,53.2393002 48.3333333,47.3666777 L48.3333333,30.9333333"/>
<polygon points="11.6 50.267 11.6 47.367 9.667 44.467 9.667 32.867 11.6 29.967 11.6 27.067 0 27.067 0 29.967 1.933 32.867 1.933 44.467 0 47.367 0 50.267"/>
<polygon points="54.133 30.933 54.133 28.033 52.2 25.134 52.2 13.533 54.133 10.633 54.133 7.733 42.533 7.733 42.533 10.633 44.467 13.533 44.467 25.134 42.533 28.033 42.533 30.933"/>
</g>
</symbol>
</defs>
</svg>
and the XSLT file found here:
How can I show all symbols in an SVG file?
It mostly works... it creates all the dom objects I'm expecting out of the box, which is pretty amazing. But inside use I get:
#shadow-root (closed)
which is what we see in our actual app... but in our app, the image is nested inside the shadow-root. But in this version, it's empty. Seems basically the same as how we're doing it on the app side. What's the issue?

Instead of loading the symbols from a file, write them into the output and reference locally.
Aditionally, you need to define a size for the used elements (the default would be 100%). As position() is one-based, subtract one to start top-left with the display.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/2000/svg"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<xsl:template match="/">
<svg>
<xsl:copy>
<xsl:copy-of select="//svg:defs"/>
</xsl:copy>
<g stroke="black" fill="black">
<xsl:for-each select="//svg:symbol">
<use width="32" height="32">
<xsl:attribute name="x"><xsl:value-of select="(position()-1) mod 10 * 32"/></xsl:attribute>
<xsl:attribute name="y"><xsl:value-of select="floor((position()-1) div 10) * 32"/></xsl:attribute>
<xsl:attribute name="xlink:href">#<xsl:value-of select="#id"/></xsl:attribute>
</use>
</xsl:for-each>
</g>
</svg>
</xsl:template>
</xsl:stylesheet>

Related

Why doesn't my SVG display when I try to use the XSLT output method "html"?

Apologies if this is a simple question as I'm fairly novice at XSLT. I'm having an issue where the SVG image I'm generating in XSLT won't display if my output method is set to html. If I open the desired xml file, transform it, and view it in browser (IE v11), the whole document loads with exception to the SVG image itself. In IE, if I right click the document and select "view source" I can see the SVG information sitting there, right where its expected to be.
If I set the output method to xml and open in IE, the SVG images do appear but the document structure isn't there (but to reiterate: the goal is to output in html as it's used in other processes further down the line)
Below is a snippet of a small, simple testing XSLT and the post transformation xml seen from "view source" (leaving out unrelated stuff). I'm trying to get this working on a small scale before I tackle the larger picture.
Also to note, the svg:text tag does display the text mentioned, although it seems more like a simple text entry than part of an image.
For those familiar with it, the majority of functionality comes from RenderX's XSLT barcode generator.
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
<xsl:import href="code128.xsl"/>
<xsl:output method='html' media-type="image/svg" encoding='UTF-8' indent='yes'/>
<xsl:template match="/">
<html lang="en">
<head>
<title>SVG bar code examples</title>
</head>
<body>
<h1>SVG bar code examples</h1>
<ul>
<xsl:apply-templates select="//barcode"/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="barcode">
<li>
<xsl:call-template name="barcode-code128">
.
<!--Parameters here-->
.
</xsl:call-template>
</li>
</xsl:template>
</xsl:stylesheet>
Post Transformation XML:
<?xml version="1.0" encoding="utf-8"?>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
<head>
<title>SVG bar code examples</title>
</head>
<body>
<h1>SVG bar code examples</h1>
<ul>
<li>
<svg:svg width="29.494444444444443mm" height="12.7mm" viewBox="0 0 10618 4572">
<svg:path d="M 686 686 l 0 2286 138 0 0 -2286 z m ..."(truncated due to length)>
<svg:text x="6068" y="4420" text-anchor="middle" font-family=...
</svg:svg>
</li>
.
<!--More SVG images here-->
.
</ul>
</body>
</html>
If you want to use the output method html then you should use HTML syntax and neither HTML 4 nor HTML5 or what is simply called HTML now supports namespaces, in particular not prefixed element names. So for your SVG elements to be recognized in text/html you need simply svg, path and text as the element names without any prefix, the only allowed "namespace" declaration would be xmlns="http://www.w3.org/2000/svg" on the svg element.
I am also not sure about IE's SVG support, perhaps it is only enabled in standards compliant rendering mode, meaning, for method="html" it is recommended to have <xsl:output method="html" indent="yes" version="5" doctype-system="about:legacy-doctype"/>, as that doctype-system is supposed to set older browsers distinguishing between quirks mode and standard compliant mode to the latter mode.
If your used library creates prefixed elements then you would need to run them through an additional transformation step to strip the namespace prefix which, in a single transformation, is in XSLT 1 only possible using a proprietary extension function to convert a result tree fragment into a node-set:
<xsl:template match="barcode">
<li>
<xsl:variable name="barcode-rtf">
<xsl:call-template name="barcode-code128">
.
<!--Parameters here-->
.
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="msxml:node-set($barcode-rtf)/node()" xmlns:msxml="urn:schemas-microsoft-com:xslt" mode="strip-svg-prefix"/>
</li>
</xsl:template>
<xsl:template match="#* | node()" mode="strip-svg-prefix">
<xsl:copy>
<xsl:apply-templates select="#* | node()" mode="strip-svg-prefix"/>
</xsl:copy>
</xsl:template>
<xsl:template match="svg:*" mode="strip-svg-prefix" xmlns:svg="http://www.w3.org/2000/svg">
<xsl:element name="{local-name()}" namespace="http://www.w3.org/2000/svg">
<xsl:apply-templates select="#* | node()" mode="strip-svg-prefix"/>
</xsl:copy>
</xsl:template>
Note that, for text/html and HTML 4 or HTML5 as the transformation target with method="html", I would recommend to use no namespace at all for the HTML elements, i.e. to remove the XHTML namespace declaration you have. Otherwise serialization of empty elements might not give the proper HTML syntax.

XSLT: Add attribute to last given element of SVG image

How can I add the attribute stroke="red" to the last occurence of the circle tag ?
My SVG example is like this:
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-900 -900 1800 1800" width="1800" height="1800">
<circle r="20" stroke="#343434" stroke-width="10"/>
<g stroke="#77868b" stroke-width="8" fill="none">
<circle id="o4" r="600"/>
<circle id="o5" r="700"/>
<circle id="o6" r="800" />
<g stroke-width="60" stroke-linecap="round" transform="rotate(90)">
<circle id="e4" r="600" stroke-dasharray="1,116.75"/>
<circle id="e5" r="700" stroke-dasharray="1,365.5"/>
<circle id="e6" r="800" stroke-dasharray="1,2512.25"/>
</g>
</g>
</svg>
When I use the following XSLT template, I get three circles with stroke="red" instead of the last one only.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:svg="http://www.w3.org/2000/svg" version='1.0'>
<!-- this template is applied by default to all nodes and attributes -->
<xsl:template match="#*|node()">
<!-- just copy all my attributes and child nodes, except if there's a better template for some of them -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- this template is applied to a path node that doesn't have a fill attribute -->
<xsl:template match="svg:circle[not(#stroke)][last()]">
<!-- copy me and my attributes and my subnodes, applying templates as necessary, and add a fill attribute set to red -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:attribute name="stroke">red</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Adding g/ to my template like this <xsl:template match="svg:g/circle[not(#stroke)][last()]"> fixed my issue.

FOP XSL-FO Internal page anchor link URI problems

Morning folks!
So I am currently working on an FOP solution for a project, the end goal of which is basically to allow me to print onto a medium with the second iteration of the same information upside down, a feature which is not possible in Access 2007.
Everything is working swimmingly, and I can get the FOP to parse when I duplicate the code for the front side and use it for the back side.
However, when I try to use the parameter, I am getting an error I simply don't understand from the FOP terminal, having spent the better part of 4-5 hours trying to get my head around it. Code and error to follow.
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING - THIS TEMPLATE IS FOR Z FOLD CANON STOCK - DOUBLE SIDED -->
<!-- In order to covert CM to pixels, multiply the CM by 37.7952755905511, and round to two decimal places. It may be easier to use a spreadsheet to do this if you are editing multiple values -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<!-- EDIT THIS ROW TO CHANGE THE SIZE OF THE PAGE - AKA SET THIS TO THE SIZE OF THE MEDIUM YOU ARE PRINTING TO - CONVERTED TO PIXELS-->
<fo:simple-page-master master-name="simple" page-height="740.787px" page-width="317.480px">
<!-- IGNORE EVERYTHING BETWEEN HERE-->
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<fo:flow flow-name="xsl-region-body">
<xsl:for-each select="//Person">
<fo:block>
<fo:instream-foreign-object>
<!--<svg xmlns="http://www.w3.org/2000/svg">-->
<!-- AND HERE-->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="370.4" height="634.96">
<g class="mirrorpage">
<rect style= "fill:none; stroke: black" width="370.394" height="317.480"/>
<text x="37.8" y="122.83" style="text-anchor:start;" font-family="'ChevinLight'">
<tspan font-size="20">
Firstname
</tspan>
<tspan font-size="20">
Lastname
</tspan>
</text>
<text style="text-anchor:start;" x="37.8" y="150" font-family="'ChevinLight'">
<tspan font-size="20">
Company Name
</tspan>
</text>
<xsl:variable name="code" select="Code"/>
<image xlink:href="LINK REDACTED" x="37.8" y="190" height="50" width="50"/>
<image xlink:href="LINK REDACTED" x="250" y="200" height="100"width="100"/>
</g>
<use href= "#mirrorpage" transform="translate (370.394 634.96) scale (-1 -1)"/>
</svg>
</fo:instream-foreign-object>
</fo:block>
</xsl:for-each>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
The error I am then receiving when I try to parse this is as follows:
[ERROR] svg graphic could not be built: file:/C:/Users/Events/Desktop/Angledtext/BadgePrinting/PDF/:-1
The URI '' specified on the element is invalid.
From my (limited) understanding, there is an issue with the parsing of the "#" in the use line. I have tried to research URI properties but I have to be honest, I am completely lost.
Any help, or even a pointer to some relevant material I can read would be greatly appreciated.
Thanks

XSLT 2.0: Transform notation in plain text to svg

I am new to transformations between different formats. My goal is to transfer a notation from a toolkit which is in a plain text format to svg. An easy example would be that I have an orange ellipse and the notation would be like this (x and y is the coordinate system so 0 and 0 means the ellipse is in the middle):
GRAPHREP
PEN color:$000000 w:2pt
FILL color:$ff7f00
ELLIPSE x:0pt y:0pt rx:114pt ry:70pt
and my desired output would be an svg code something like this(the cx and cy coordinate are randomly selected for the example):
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g>
<ellipse fill="#ff7f00" stroke="#000000" stroke-width="2" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" cx="250" cy="250" id="svg_1" rx="114" ry="70"/>
</g>
</svg>
I found these two threads Parse text file with XSLT and XSL transform on text to XML with unparsed-text: need more depth
where they transform plain text to xml with XSLT 2.0 and the unparsed-text() function and regex. In my example how would it be possible to get the commands like ELLIPSE(is a
regex which recognizes the all uppercase words possible?) and the parameters(is it possible to get with Xpath from plain text anyhow?)? Is a good implementation doable in XSLT 2.0 or should I
look for another method? Any help would be appreciated!
Below is an example of how you can load the text file using unparsed-text(), and parse the content using xsl:analyze-text to produce an intermediate XML document, and then transform that XML using a "push"-style stylesheet.
It shows an example of how to support ELLIPSE, CIRCLE and RECTANGLE text conversion. You may need to customize it a bit, but should give you an idea of what is possible. With the addition of regex and unparsed-text(), XSLT 2.0 and 3.0 makes all sorts of text transformations possible that would have been extremely cumbersome or difficult in XSLT 1.0.
With a file called "drawing.txt" with the following content:
GRAPHREP
PEN color:$000000 w:2pt
FILL color:$ff7f00
ELLIPSE x:0pt y:0pt rx:114pt ry:70pt
GRAPHREP
PEN color:$000000 w:2pt
FILL color:$ff7f00
CIRCLE x:0pt y:0pt rx:114pt ry:70pt
GRAPHREP
PEN color:$000000 w:2pt
FILL color:$ff7f00
RECTANGLE x:0pt y:0pt width:114pt height:70pt
Executing the following XSLT in the same directory:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:local="local"
exclude-result-prefixes="xs"
version="2.0"
xmlns:svg="http://www.w3.org/2000/svg">
<xsl:output indent="yes"/>
<!--matches sequences of UPPER-CASE letters -->
<xsl:variable name="label-pattern" select="'[A-Z]+'"/>
<!--matches the "attributes" in the line i.e. w:2pt,
has two capture groups (1) => attribute name, (2) => attribute value -->
<xsl:variable name="attribute-pattern" select="'\s?(\S+):(\S+)'"/>
<!--matches a line of data for the drawing text,
has two capture groups (1) => label, (2) attribute data-->
<xsl:variable name="line-pattern" select="concat('(', $label-pattern, ')\s(.*)\n?')"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<svg width="400" height="400">
<g>
<!-- Find the text patterns indicating the shape -->
<xsl:analyze-string select="unparsed-text('drawing.txt')"
regex="{concat('(', $label-pattern, ')\n((', $line-pattern, ')+)\n?')}">
<xsl:matching-substring>
<!--Convert text to XML -->
<xsl:variable name="drawing-markup" as="element()">
<!--Create an element for this group, using first matched pattern as the element name
(i.e. GRAPHREP => <GRAPHREP>) -->
<xsl:element name="{regex-group(1)}">
<!--split the second matched group for this shape into lines by breaking on newline-->
<xsl:variable name="lines" select="tokenize(regex-group(2), '\n')"/>
<xsl:for-each select="$lines">
<!--for each line, run through this process to create an element with attributes
(e.g. FILL color:$frf7f00 => <FILL color=""/>
-->
<xsl:analyze-string select="." regex="{$line-pattern}">
<xsl:matching-substring>
<!--create an element using the UPPER-CASE label starting the line -->
<xsl:element name="{regex-group(1)}">
<!-- capture each of the attributes -->
<xsl:analyze-string select="regex-group(2)" regex="\s?(\S+):(\S+)">
<xsl:matching-substring>
<!--convert foo:bar into attribute foo="bar",
translate $ => #
and remove the letters 'p' and 't' by translating into nothing"-->
<xsl:attribute name="{regex-group(1)}" select="translate(regex-group(2), '$pt', '#')"/>
</xsl:matching-substring>
<xsl:non-matching-substring/>
</xsl:analyze-string>
</xsl:element>
</xsl:matching-substring>
<xsl:non-matching-substring/>
</xsl:analyze-string>
</xsl:for-each>
</xsl:element>
</xsl:variable>
<!--Uncomment the copy-of below if you want to see the intermediate XML $drawing-markup-->
<!--<xsl:copy-of select="$drawing-markup"/>-->
<!-- Transform XML into SVG -->
<xsl:apply-templates select="$drawing-markup"/>
</xsl:matching-substring>
<xsl:non-matching-substring/>
</xsl:analyze-string>
</g>
</svg>
</xsl:template>
<!--==========================================-->
<!-- Templates to convert the $drawing-markup -->
<!--==========================================-->
<!--for supported shapes, create the element using
lower-case value, and change rectangle to rect
for the svg element name-->
<xsl:template match="GRAPHREP[ELLIPSE | CIRCLE | RECTANGLE]">
<xsl:element name="{replace(lower-case(local-name(ELLIPSE | CIRCLE | RECTANGLE)), 'rectangle', 'rect', 'i')}">
<xsl:attribute name="id" select="concat('id_', generate-id())"/>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="ELLIPSE | CIRCLE | RECTANGLE"/>
<!-- Just process the content of GRAPHREP.
If there are multiple shapes and you want a new
<svg><g></g></svg> for each shape,
then move it from the template for "/" into this template-->
<xsl:template match="GRAPHREP/*">
<xsl:apply-templates select="#*"/>
</xsl:template>
<xsl:template match="PEN" priority="1">
<!--TODO: test if these attributes exist, if they do, do not create these defaults.
Hard-coding for now, to match desired output, since I don't know what the text
attributes would be, but could wrap each with <xsl:if test="not(#dasharray)">-->
<xsl:attribute name="stroke-dasharray" select="'null'"/>
<xsl:attribute name="stroke-linjoin" select="'null'"/>
<xsl:attribute name="stroke-linecap" select="'null'"/>
<xsl:apply-templates select="#*"/>
</xsl:template>
<!-- conterts #color => #stroke -->
<xsl:template match="PEN/#color">
<xsl:attribute name="stroke" select="."/>
</xsl:template>
<!--converts #w => #stroke-width -->
<xsl:template match="PEN/#w">
<xsl:attribute name="stroke-width" select="."/>
</xsl:template>
<!--converts #color => #fill and replaces $ with # -->
<xsl:template match="FILL/#color">
<xsl:attribute name="fill" select="translate(., '$', '#')"/>
</xsl:template>
<!-- converts #x => #cx with hard-coded values.
May want to use value from text, but matching your example-->
<xsl:template match="ELLIPSE/#x | ELLIPSE/#y">
<!--not sure if there was a relationship between ELLIPSE x:0pt y:0pt, and why 0pt would be 250,
but just an example...-->
<xsl:attribute name="c{name()}" select="250"/>
</xsl:template>
</xsl:stylesheet>
Produces the following SVG output:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns:local="local"
xmlns:svg="http://www.w3.org/2000/svg"
width="400"
height="400">
<g>
<ellipse id="id_d2e0"
stroke-dasharray="null"
stroke-linjoin="null"
stroke-linecap="null"
stroke="#000000"
stroke-width="2"
fill="#ff7f00"
cx="250"
cy="250"
rx="114"
ry="70"/>
<circle id="id_d3e0"
stroke-dasharray="null"
stroke-linjoin="null"
stroke-linecap="null"
stroke="#000000"
stroke-width="2"
fill="#ff7f00"
x="0"
y="0"
rx="114"
ry="70"/>
<rect id="id_d4e0"
stroke-dasharray="null"
stroke-linjoin="null"
stroke-linecap="null"
stroke="#000000"
stroke-width="2"
fill="#ff7f00"
x="0"
y="0"
width="114"
height="70"/>
</g>
</svg>

XSLT rounding attributes for SVG optimization

I need to reduce the numeric precision, and minimum exponent of SVG attributes to save space.
Input:
<svg xmlns="http://www.w3.org/2000/svg" width="250" height="250">
<circle cx="125.1111" cy="125.2222" r="124.9999" fill="red"/>
</svg>
Output:
<svg xmlns="http://www.w3.org/2000/svg" width="250" height="250">
<circle cx="125.1" cy="125.2" r="125.0" fill="red"/>
</svg>
What stylesheet will achieve the result?
Any thoughts on the safety of this?
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*[.=number()]">
<xsl:attribute name="{name()}">
<xsl:value-of select="format-number(.,'#.#')"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Output:
<svg width="250" height="250" xmlns="http://www.w3.org/2000/svg">
<circle cx="125.1" cy="125.2" r="125" fill="red"></circle>
</svg>
On the applicability:
Without proper analysis it's not going to work for all cases, but it usually works fine to chop off some decimals and hoping for the best. Truncating to one decimal in gradients (usually defined in objectBoundingBox space) can give poor results, same with other places using objectBoundingBox units (such attribute values are usually in the range [0..1]). Anything else mostly depends on the current transform (CTM) and what 'viewBox' is used on the root svg element.