1 <?xml version='1.0' encoding='UTF-8' ?>
3 Nick's web site: SVG embedding.
5 Copyright © 2021-2022 Nick Bowler
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <https://www.gnu.org/licenses/>
20 <xsl:stylesheet version='1.0'
21 xmlns='http://www.w3.org/1999/xhtml'
22 xmlns:cc='http://creativecommons.org/ns#'
23 xmlns:dc='http://purl.org/dc/elements/1.1/'
24 xmlns:f='http://draconx.ca/my-functions'
25 xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
26 xmlns:svg="http://www.w3.org/2000/svg"
27 xmlns:xhtml='http://www.w3.org/1999/xhtml'
28 xmlns:xlink='http://www.w3.org/1999/xlink'
29 xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
30 extension-element-prefixes="f"
31 exclude-result-prefixes="cc dc rdf xhtml">
33 <xsl:import href='layouts/functions.xsl' />
34 <xsl:output cdata-section-elements='style script' />
35 <xsl:strip-space elements='xhtml:html xhtml:body' />
37 <xsl:key name='embed-svg' match='//xhtml:img[f:ends-with(@src, ".svg")]'
40 <xsl:key name='id' match='//*[@id]' use='@id' />
42 <xsl:template match='node()|@*'>
44 <xsl:apply-templates select='node()|@*' />
48 <!-- force a literal result to ensure html gets all namespace nodes -->
49 <xsl:template name='rewrite-html'>
51 <xsl:apply-templates select='node()|@*' />
55 <xsl:template match='/xhtml:html'>
57 <xsl:when test='//xhtml:img[key("embed-svg", @src)]'>
58 <xsl:call-template name='rewrite-html' />
61 <xsl:element name='{name()}' namespace='{namespace-uri()}'>
62 <xsl:apply-templates select='node()|@*' />
68 <xsl:template match='xhtml:img[key("embed-svg", @src)]'>
70 <xsl:apply-templates select='@style' />
72 <xsl:apply-templates select='@width|@height' />
74 <svg:use xlink:href='#es-{generate-id(key("embed-svg", @src))}' />
75 <svg:foreignObject width='0' height='0'>
76 <!-- TODO: this hardcoded fallback method needs to be more flexible -->
78 <xsl:attribute name='src'>
79 <xsl:value-of select='substring-before(@src, ".svg")' />
80 <xsl:text>-32.png</xsl:text>
82 <xsl:attribute name='class'>svgfallback</xsl:attribute>
83 <xsl:apply-templates select='node()|
84 @*[not(contains("src style class", local-name()))]' />
92 <!-- SVG embedding -->
93 <xsl:template mode='embed-svg' match='node()|@*'>
95 <xsl:apply-templates select='node()|@*' />
99 <!-- Remove all whitespace-only text nodes from embedded SVG -->
100 <xsl:template mode='embed-svg' match='text()[normalize-space(.) = ""]' />
102 <xsl:template name='svgnode' mode='embed-svg' match='svg:*'>
103 <xsl:param name='idnode' select='.' />
105 <!-- doctype demands svg: prefix -->
106 <xsl:element name='svg:{local-name()}' namespace='{namespace-uri()}'>
107 <!-- rewrite all id nodes to avoid conflicts with other embeddings -->
108 <xsl:if test='@id or not(parent::*)'>
109 <xsl:attribute name='id'>
110 <xsl:value-of select='concat("es-", generate-id($idnode))' />
114 <xsl:when test='$idnode/@src and not(parent::*)'>
115 <!-- remove .svg suffix -->
116 <xsl:variable name='raw'
117 select='substring($idnode/@src, 1, string-length($idnode/@src)-4)' />
119 <xsl:attribute name='class'>
120 <xsl:value-of select='normalize-space(concat(@class, " embed ",
121 translate($raw, "/", " ")))' />
125 <xsl:apply-templates mode='embed-svg' select='@class' />
128 <xsl:apply-templates mode='embed-svg'
129 select='@*[local-name()!="id" and local-name()!="class"]' />
130 <xsl:apply-templates mode='embed-svg' select='node()'>
131 <xsl:sort select='-count(self::svg:metadata)' data-type='number' />
132 </xsl:apply-templates>
136 <xsl:template name='depx'>
137 <xsl:param name='node' select='.' />
139 <xsl:when test='contains($node, "px")'>
140 <xsl:value-of select='substring-before($node, "px")' />
143 <xsl:value-of select='node' />
148 <xsl:template mode='embed-svg' match='/svg:svg/@width[../@height]'>
149 <xsl:attribute name='viewBox'>
150 <xsl:text>0 0 </xsl:text>
151 <xsl:call-template name='depx' />
152 <xsl:text> </xsl:text>
153 <xsl:call-template name='depx'>
154 <xsl:with-param name='node' select='../@height' />
158 <xsl:template mode='embed-svg' match='/svg:svg/@height[../@width]' />
161 the RDF stuff is disallowed by doctype, try and transform to an
164 <xsl:template mode='embed-svg' match='svg:metadata'>
165 <xsl:if test='descendant::cc:Work'>
167 <xsl:apply-templates select='descendant::cc:Work' />
172 <!-- hackjob to stringify the work info -->
173 <xsl:template match='cc:Work'>
176 <xsl:if test='dc:title'>
177 <xsl:value-of select='concat("“", dc:title, "”")' />
179 <xsl:if test='dc:creator/cc:Agent'>
180 <xsl:text> by </xsl:text>
181 <xsl:for-each select='dc:creator/cc:Agent'>
182 <xsl:value-of select='dc:title' />
184 <xsl:when test='position()=last()'>
185 <xsl:text>.</xsl:text>
188 <xsl:text>, </xsl:text>
193 <xsl:if test='dc:source'>
196 <xsl:value-of select='dc:source' />
198 <xsl:if test='cc:license/@rdf:resource'>
201 <xsl:value-of select='cc:license/@rdf:resource' />
207 <!-- match all xlink:href attributes to generated IDs for this document -->
208 <xsl:template mode='embed-svg' match='@xlink:href[starts-with(.,"#")]'>
209 <xsl:variable name='ref' select='substring-after(., "#")' />
211 <xsl:attribute name='xlink:href'>
212 <xsl:value-of select='concat("#es-", generate-id(key("id", $ref)))' />
216 <!-- rewrite all attributes containing url(#id) to generated IDs -->
217 <xsl:template name='rewrite-urls'>
218 <xsl:param name='val' select='.' />
221 <xsl:when test='contains($val, "url(#")'>
222 <xsl:variable name='tail' select='substring-after($val, "url(#")' />
223 <xsl:variable name='ref' select='substring-before($tail, ")")' />
225 <xsl:value-of select='substring-before($val, "url(#")' />
226 <xsl:text>url(</xsl:text>
227 <xsl:value-of select='concat("#es-", generate-id(key("id", $ref)))' />
228 <xsl:text>)</xsl:text>
229 <xsl:call-template name='rewrite-urls'>
230 <xsl:with-param name='val' select='substring-after($tail, ")")' />
234 <xsl:value-of select='$val' />
239 <xsl:template mode='embed-svg' match='@*[contains(., "url(#")]'>
240 <xsl:attribute name='{name()}'>
241 <xsl:call-template name='rewrite-urls' />
245 <xsl:template match='xhtml:body'>
247 <xsl:apply-templates select='node()|@*' />
248 <xsl:if test='key("embed-svg", //xhtml:img/@src)'>
249 <script type='x'><![CDATA[]]x><!--]]></script>
250 <svg:svg width='0' height='0'>
252 <xsl:for-each select='//xhtml:img'>
253 <xsl:if test='generate-id(.)=generate-id(key("embed-svg", @src))'>
254 <xsl:apply-templates mode='embed-svg'
255 select='document(concat("output", @src))'>
256 <xsl:with-param name='idnode' select='.' />
257 </xsl:apply-templates>
262 <script type='x'>--></script>