]> git.draconx.ca Git - homepage.git/blobdiff - layouts/embed-svg.xsl
Embed SVG icons directly into output.
[homepage.git] / layouts / embed-svg.xsl
diff --git a/layouts/embed-svg.xsl b/layouts/embed-svg.xsl
new file mode 100644 (file)
index 0000000..2b3f8e2
--- /dev/null
@@ -0,0 +1,251 @@
+<?xml version='1.0' encoding='UTF-8' ?>
+<!--
+  Nick's web site: SVG embedding.
+
+  Copyright © 2021 Nick Bowler
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <https://www.gnu.org/licenses/>
+-->
+<xsl:stylesheet version='1.0'
+  xmlns='http://www.w3.org/1999/xhtml'
+  xmlns:cc='http://creativecommons.org/ns#'
+  xmlns:dc='http://purl.org/dc/elements/1.1/'
+  xmlns:f='http://draconx.ca/my-functions'
+  xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
+  xmlns:svg="http://www.w3.org/2000/svg"
+  xmlns:xhtml='http://www.w3.org/1999/xhtml'
+  xmlns:xlink='http://www.w3.org/1999/xlink'
+  xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+  extension-element-prefixes="f"
+  exclude-result-prefixes="cc dc rdf xhtml">
+
+<xsl:import href='layouts/functions.xsl' />
+<xsl:output cdata-section-elements='style script' />
+<xsl:strip-space elements='xhtml:html xhtml:body' />
+
+<xsl:key name='embed-svg' match='//xhtml:img[f:ends-with(@src, ".svg")]'
+  use='@src' />
+
+<xsl:key name='id' match='//*[@id]' use='@id' />
+
+<xsl:template match='node()|@*'>
+  <xsl:copy>
+    <xsl:apply-templates select='node()|@*' />
+  </xsl:copy>
+</xsl:template>
+
+<!-- force a literal result to ensure html gets all namespace nodes -->
+<xsl:template name='rewrite-html'>
+  <html>
+    <xsl:apply-templates select='node()|@*' />
+  </html>
+</xsl:template>
+
+<xsl:template match='/xhtml:html'>
+  <xsl:choose>
+    <xsl:when test='//xhtml:img[key("embed-svg", @src)]'>
+      <xsl:call-template name='rewrite-html' />
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:element name='{name()}' namespace='{namespace-uri()}'>
+        <xsl:apply-templates select='node()|@*' />
+      </xsl:element>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match='xhtml:img[key("embed-svg", @src)]'>
+  <span class='svg'>
+    <xsl:apply-templates select='@style' />
+    <svg:svg>
+      <xsl:apply-templates select='@width|@height' />
+      <svg:switch>
+        <svg:use xlink:href='#es-{generate-id(key("embed-svg", @src))}' />
+        <svg:foreignObject width='0' height='0'>
+          <!-- TODO: this hardcoded fallback method needs to be more flexible -->
+          <xsl:copy>
+            <xsl:attribute name='src'>
+              <xsl:value-of select='substring-before(@src, ".svg")' />
+              <xsl:text>-32.png</xsl:text>
+            </xsl:attribute>
+            <xsl:attribute name='class'>svgfallback</xsl:attribute>
+            <xsl:apply-templates select='node()|
+                  @*[not(contains("src style class", local-name()))]' />
+          </xsl:copy>
+        </svg:foreignObject>
+      </svg:switch>
+    </svg:svg>
+  </span>
+</xsl:template>
+
+<!-- SVG embedding -->
+<xsl:template mode='embed-svg' match='node()|@*'>
+  <xsl:copy>
+    <xsl:apply-templates select='node()|@*' />
+  </xsl:copy>
+</xsl:template>
+
+<!-- Remove all whitespace-only text nodes from embedded SVG -->
+<xsl:template mode='embed-svg' match='text()[normalize-space(.) = ""]' />
+
+<xsl:template name='svgnode' mode='embed-svg' match='svg:*'>
+  <xsl:param name='idnode' select='.' />
+
+  <!-- doctype demands svg: prefix -->
+  <xsl:element name='svg:{local-name()}' namespace='{namespace-uri()}'>
+    <!-- rewrite all id nodes to avoid conflicts with other embeddings -->
+    <xsl:if test='@id or not(parent::*)'>
+      <xsl:attribute name='id'>
+        <xsl:value-of select='concat("es-", generate-id($idnode))' />
+      </xsl:attribute>
+    </xsl:if>
+    <xsl:apply-templates mode='embed-svg' select='@*[local-name()!="id"]' />
+    <xsl:apply-templates mode='embed-svg' select='node()'>
+      <xsl:sort select='-count(self::svg:metadata)' data-type='number' />
+    </xsl:apply-templates>
+  </xsl:element>
+</xsl:template>
+
+<xsl:template name='depx'>
+  <xsl:param name='node' select='.' />
+  <xsl:choose>
+    <xsl:when test='contains($node, "px")'>
+      <xsl:value-of select='substring-before($node, "px")' />
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:value-of select='node' />
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template mode='embed-svg' match='/svg:svg/@width[../@height]'>
+  <xsl:attribute name='viewBox'>
+    <xsl:text>0 0 </xsl:text>
+    <xsl:call-template name='depx' />
+    <xsl:text> </xsl:text>
+    <xsl:call-template name='depx'>
+      <xsl:with-param name='node' select='../@height' />
+    </xsl:call-template>
+  </xsl:attribute>
+</xsl:template>
+<xsl:template mode='embed-svg' match='/svg:svg/@height[../@width]' />
+
+<!--
+  the RDF stuff is disallowed by doctype, try and transform to an
+  attribution comment.
+-->
+<xsl:template mode='embed-svg' match='svg:metadata'>
+  <xsl:if test='descendant::cc:Work'>
+    <xsl:comment>
+      <xsl:apply-templates select='descendant::cc:Work' />
+    </xsl:comment>
+  </xsl:if>
+</xsl:template>
+
+<!-- hackjob to stringify the work info -->
+<xsl:template match='cc:Work'>
+  <xsl:text>
+  </xsl:text>
+  <xsl:if test='dc:title'>
+    <xsl:value-of select='concat("“", dc:title, "”")' />
+  </xsl:if>
+  <xsl:if test='dc:creator/cc:Agent'>
+    <xsl:text> by </xsl:text>
+    <xsl:for-each select='dc:creator/cc:Agent'>
+      <xsl:value-of select='dc:title' />
+      <xsl:choose>
+        <xsl:when test='position()=last()'>
+          <xsl:text>.</xsl:text>
+        </xsl:when>
+        <xsl:otherwise>
+          <xsl:text>, </xsl:text>
+        </xsl:otherwise>
+      </xsl:choose>
+    </xsl:for-each>
+  </xsl:if>
+  <xsl:if test='dc:source'>
+    <xsl:text>
+  </xsl:text>
+    <xsl:value-of select='dc:source' />
+  </xsl:if>
+  <xsl:if test='cc:license/@rdf:resource'>
+    <xsl:text>
+  </xsl:text>
+    <xsl:value-of select='cc:license/@rdf:resource' />
+  </xsl:if>
+  <xsl:text>
+</xsl:text>
+</xsl:template>
+
+<!-- match all xlink:href attributes to generated IDs for this document -->
+<xsl:template mode='embed-svg' match='@xlink:href[starts-with(.,"#")]'>
+  <xsl:variable name='ref' select='substring-after(., "#")' />
+
+  <xsl:attribute name='xlink:href'>
+    <xsl:value-of select='concat("#es-", generate-id(key("id", $ref)))' />
+  </xsl:attribute>
+</xsl:template>
+
+<!-- rewrite all attributes containing url(#id) to generated IDs -->
+<xsl:template name='rewrite-urls'>
+  <xsl:param name='val' select='.' />
+
+  <xsl:choose>
+    <xsl:when test='contains($val, "url(#")'>
+      <xsl:variable name='tail' select='substring-after($val, "url(#")' />
+      <xsl:variable name='ref' select='substring-before($tail, ")")' />
+
+      <xsl:value-of select='substring-before($val, "url(#")' />
+      <xsl:text>url(</xsl:text>
+      <xsl:value-of select='concat("#es-", generate-id(key("id", $ref)))' />
+      <xsl:text>)</xsl:text>
+      <xsl:call-template name='rewrite-urls'>
+        <xsl:with-param name='val' select='substring-after($tail, ")")' />
+      </xsl:call-template>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:value-of select='$val' />
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template mode='embed-svg' match='@*[contains(., "url(#")]'>
+  <xsl:attribute name='{name()}'>
+    <xsl:call-template name='rewrite-urls' />
+  </xsl:attribute>
+</xsl:template>
+
+<xsl:template match='xhtml:body'>
+  <xsl:copy>
+    <xsl:apply-templates select='node()|@*' />
+    <xsl:if test='key("embed-svg", //xhtml:img/@src)'>
+      <script type='x'><![CDATA[]]x><!--]]></script>
+      <svg:svg width='0' height='0'>
+        <svg:defs>
+          <xsl:for-each select='//xhtml:img'>
+            <xsl:if test='generate-id(.)=generate-id(key("embed-svg", @src))'>
+              <xsl:apply-templates mode='embed-svg'
+                select='document(concat("output", @src))'>
+                <xsl:with-param name='idnode' select='.' />
+              </xsl:apply-templates>
+            </xsl:if>
+          </xsl:for-each>
+        </svg:defs>
+      </svg:svg>
+      <script type='x'>--></script>
+    </xsl:if>
+  </xsl:copy>
+</xsl:template>
+
+</xsl:stylesheet>