]> git.draconx.ca Git - homepage.git/blobdiff - layouts/default.xsl
cdecl99-1.3 bash-5 hotfix
[homepage.git] / layouts / default.xsl
index f41467bd87e01d6de9a7aa0d594ab074f16ac494..aa22e8f23460062220cba346f2b7468de96bf1ef 100644 (file)
@@ -2,7 +2,7 @@
 <!--
   Nick's web site: XHTML output stage
 
-  Copyright © 2018-2020 Nick Bowler
+  Copyright © 2018-2022 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
   xmlns:xhtml='http://www.w3.org/1999/xhtml'
   xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
   xmlns:func='http://exslt.org/functions'
+  xmlns:exslt='http://exslt.org/common'
   xmlns:f='http://draconx.ca/my-functions'
-  extension-element-prefixes='func f'
+  extension-element-prefixes='exslt func f'
   exclude-result-prefixes='xhtml'>
 
 <xsl:import href='layouts/functions.xsl' />
-
-<xsl:output method='xml' encoding='UTF-8' indent='yes'
-  doctype-public='-//W3C//DTD XHTML 1.1//EN'
-  doctype-system='http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
-  cdata-section-elements='style' />
+<xsl:output cdata-section-elements='style script' />
 
 <xsl:param name='source-uri'
-  select='"//git.draconx.ca/gitweb/homepage.git/blob/"' />
+  select='"//git.draconx.ca/gitweb/homepage.git/"' />
 <xsl:param name='site-title' select='"The Citrine Citadel"' />
 <xsl:param name='section-links' select='//document/section-links' />
 
-<func:function name='f:ends-with'>
-  <xsl:param name='a' />
-  <xsl:param name='b' />
-  <func:result
-    select='substring($a, string-length($a)-string-length($b)+1)=$b' />
-</func:function>
-
 <xsl:template match='node()|@*'>
   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
 </xsl:template>
 
-<!--
-  Nokogiri's pretty-printer is a bit weird.  Regardless of the indentation
-  setting, if an element has no child text nodes then it will be pretty-
-  printed.  This works by adding arbitrary whitespace to that element, and
-  then all of its children are eligible to be pretty-printed.
-
-  If an element has any text nodes at all, then it is not pretty-printed and
-  neither are any of its descendents.
-
-  Adding arbitrary whitespace to <pre> is bad, so we inject zero-width non-
-  breaking spaces to prevent this.  This will render fine but the spaces
-  should be removed before final output to avoid problems with copy+paste.
--->
-<xsl:template match='xhtml:pre'>
+<xsl:template name='notransform' mode='notransform' match='node()|@*'>
   <xsl:copy>
-    <xsl:apply-templates select='node()|@*' />
-    <xsl:text>&#x2060;</xsl:text>
+    <xsl:apply-templates mode='notransform' select='node()|@*' />
   </xsl:copy>
 </xsl:template>
 
-<!--
-  Likewise, adding spaces between consecutive span-level elements where
-  none existed before won't go over well.
--->
-<xsl:template name='glue-preceding-span'>
-  <xsl:if test='f:element-is-span(preceding-sibling::node()[1])'>
-    <xsl:text>&#x2060;</xsl:text>
-  </xsl:if>
-</xsl:template>
-
-<xsl:template match='*[f:element-is-span()]'>
-  <xsl:call-template name='glue-preceding-span' />
-  <xsl:copy>
-    <xsl:apply-templates select='node()|@*' />
-    <xsl:if test='*'>
-      <!-- avoid breaking within a span element -->
-      <xsl:text>&#x2060;</xsl:text>
-    </xsl:if>
-  </xsl:copy>
-</xsl:template>
-
-<!--
-  Manually strip whitespace-only text nodes so the pretty printer can do its
-  thing on remaining elements.
--->
-<xsl:template match='text()[normalize-space(.) = ""]'>
-  <xsl:choose>
-    <!-- preserve anything according to xml:space -->
-    <xsl:when test='ancestor::*[@xml:space][1][@xml:space="preserve"]'>
-      <xsl:copy />
-    </xsl:when>
-    <!-- preserve anything under <pre> -->
-    <xsl:when test='ancestor::xhtml:pre'><xsl:copy /></xsl:when>
-    <!-- preserve whitespace which is the only child node of an element -->
-    <xsl:when test='count(../node()) = 1'><xsl:copy /></xsl:when>
-    <!-- preserve whitespace between consecutive span-level elements
-         which have at least one non-whitespace sibling text element -->
-    <xsl:when test='f:element-is-span(preceding-sibling::node()[1])
-                    and f:element-is-span(following-sibling::node()[1])
-                    and ../text()[normalize-space(.) != ""]'>
-      <xsl:copy />
-    </xsl:when>
-  </xsl:choose>
-</xsl:template>
-
-<!-- Clean up whitespace where harmless to do so -->
-<xsl:template match='xhtml:p/node()[1][self::text()]'>
-  <xsl:value-of select='f:strip-leading()' />
-</xsl:template>
-<xsl:template match='xhtml:p/node()[position()=last()][self::text()]'>
-  <xsl:value-of select='f:strip-trailing()' />
-</xsl:template>
-
 <!-- Add rel attributes to external links -->
 <xsl:template match='xhtml:a[starts-with(@href,"http://")
                           or starts-with(@href,"https://")
   </xsl:copy>
 </xsl:template>
 
+<!--
+  Allow abbr to apply to document titles too, since these are generated
+  and kramdown's abbr support won't influence them. We do this by just
+  checking each word of the heading to see if is identical to an existing
+  abbr tag, and just substituting that in its place.
+  -->
+<xsl:key name='abbr' match='xhtml:abbr' use='string(.)' />
+<xsl:template name='insert-abbr' match='xhtml:h1/text()'>
+  <xsl:param name='string' select='normalize-space(.)' />
+
+  <xsl:variable name='head'
+    select='substring-before(concat($string, " "), " ")' />
+  <xsl:variable name='tail' select='substring-after($string, " ")' />
+  <xsl:variable name='match' select='key("abbr", $head)[1]' />
+
+  <xsl:choose>
+    <xsl:when test='$match'><xsl:apply-templates select='$match' /></xsl:when>
+    <xsl:otherwise><xsl:value-of select='$head' /></xsl:otherwise>
+  </xsl:choose>
+  <xsl:if test='$tail'>
+    <xsl:text> </xsl:text>
+    <xsl:call-template name='insert-abbr'>
+      <xsl:with-param name='string' select='$tail' />
+    </xsl:call-template>
+  </xsl:if>
+</xsl:template>
+
 <!--
   Convert caption attribute on tables into proper caption elements, to allow
   a simple way to add captions to kramdown tables.
   <pre>&#x2060;<code><xsl:value-of select='f:strip-leading(.)' /></code></pre>
 </xsl:template>
 
+<!--
+  Attempt to wrap the first bit of linked email addresses to allow
+  linewrapping to occur after the '@'.
+-->
+<xsl:template match='xhtml:a[starts-with(@href,"mailto:")]/text()'>
+  <xsl:variable name='addr' select='substring-after(../@href, "mailto:")' />
+
+  <xsl:variable name='wrap'
+    select='concat(substring-before($addr, "@"), "@")' />
+
+  <xsl:choose>
+    <xsl:when test='contains(., $wrap)'>
+      <xsl:value-of select='substring-before(., $wrap)' />
+      <span class='wbr'>
+        <xsl:value-of select='$wrap' />
+      </span>
+      <xsl:value-of select='substring-after(., $wrap)' />
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy />
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
 <!--
   Add a simple way to reference a document node by ID and include the XHTML
   code listing directly in the document.
   </code></pre>
 </xsl:template>
 
+<!-- For paragraphs containing only kbd elements, wrap in blockquote. -->
+<xsl:template match='xhtml:p[*[last()=count(../xhtml:kbd|../xhtml:br)]]'>
+  <blockquote>
+    <xsl:copy>
+      <xsl:apply-templates select='node()|@*' />
+    </xsl:copy>
+  </blockquote>
+</xsl:template>
+
+<!--
+  Wrap each word of text in kbd elements in spans, so they can be styled
+  to avoid linebreaks in the middle of option names and other bad places.
+-->
+<xsl:template name='spanify-text' match='xhtml:kbd/text()'>
+  <xsl:param name='text' select='normalize-space(.)' />
+  <xsl:variable name='firstword' select='substring-before($text, " ")' />
+  <xsl:choose>
+    <xsl:when test='$firstword'>
+      <span><xsl:value-of select='$firstword' /></span>
+      <xsl:text> </xsl:text>
+    </xsl:when>
+    <xsl:when test='$text'>
+      <span><xsl:value-of select='$text' /></span>
+    </xsl:when>
+  </xsl:choose>
+  <xsl:if test='$firstword'>
+    <xsl:call-template name='spanify-text'>
+      <xsl:with-param name='text' select='substring-after($text, " ")' />
+    </xsl:call-template>
+  </xsl:if>
+</xsl:template>
+
 <xsl:template match='copyright'>
   <p>
     <xsl:text>Copyright © </xsl:text>
 </xsl:template>
 
 <xsl:template match='license'>
+  <xsl:variable name='node' select='.' />
   <p>
-    <xsl:text>Copying and distribution of this material</xsl:text>
+    <xsl:choose>
+      <xsl:when test='/document/image[license/identifier != $node/identifier]'>
+        <xsl:text>Except as otherwise noted, copying</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>Copying</xsl:otherwise>
+    </xsl:choose>
+    <xsl:text> and distribution of this material</xsl:text>
     <xsl:if test='normalize-space(modification-allowed)="yes"'>
       <xsl:text>, with or without modification,</xsl:text>
     </xsl:if>
   </p>
 </xsl:template>
 
+<func:function name='f:matching-child'>
+  <xsl:param name='child' select='./copyright-holder' />
+  <xsl:param name='node' select='.' />
+  <xsl:param name='nodeset' select='$node/../*[name()=name($node)]' />
+
+  <func:result select='$nodeset[*[name()=name($child)]=$child]' />
+</func:function>
+
+<func:function name='f:attribution-order'>
+  <xsl:param name='a' />
+  <xsl:param name='b' />
+
+  <xsl:variable name='docmatch'
+    select='number($a/copyright-holder = /document/copyright-holder)
+            - number($b/copyright-holder = /document/copyright-holder)' />
+
+  <xsl:variable name='authmatch'
+    select='count(f:matching-child($a/copyright-holder, $a))
+            - count(f:matching-child($b/copyright-holder, $b))' />
+
+  <xsl:variable name='licmatch'
+    select='count(f:matching-child($a/license, $a))
+            - count(f:matching-child($b/license, $b))' />
+
+  <xsl:choose>
+    <xsl:when test='$docmatch'><func:result select='$docmatch' /></xsl:when>
+    <xsl:when test='$authmatch'><func:result select='$authmatch' /></xsl:when>
+    <xsl:when test='$licmatch'><func:result select='$licmatch' /></xsl:when>
+    <xsl:otherwise><func:result select='"nope"' /></xsl:otherwise>
+  </xsl:choose>
+</func:function>
+
+<xsl:template match='image/license'>
+  <xsl:text>, </xsl:text>
+  <a href='{uri}' rel='license'><xsl:value-of select='shortname' /></a>
+</xsl:template>
+
+<xsl:template match='image'>
+  <xsl:choose>
+    <xsl:when test='position() = 1'>, except </xsl:when>
+    <xsl:when test='position() = last()'> and </xsl:when>
+    <xsl:otherwise>, </xsl:otherwise>
+  </xsl:choose>
+  <a href='{uri}'><xsl:value-of select='title' /></a>
+  <xsl:text> © </xsl:text>
+  <xsl:value-of select='copyright' />
+  <xsl:apply-templates select='license' />
+</xsl:template>
+
+<xsl:template name='image-attribution'>
+  <xsl:variable name='images-fragment'>
+    <xsl:for-each select='/document/image'>
+      <xsl:sort select='number(copyright-holder = /document/copyright-holder)'
+                data-type='number' order='descending' />
+      <xsl:sort select='count(f:matching-child(copyright-holder))'
+                data-type='number' order='descending' />
+      <xsl:sort select='copyright-holder' order='descending' />
+      <xsl:sort select='count(f:matching-child(license))'
+                data-type='number' order='descending' />
+      <xsl:sort select='license/identifier' order='descending' />
+
+      <xsl:call-template name='notransform' />
+    </xsl:for-each>
+  </xsl:variable>
+  <xsl:variable name='images' select='exslt:node-set($images-fragment)/*' />
+
+  <xsl:variable name='abbrev-split'
+      select='count($images[copyright-holder = $images[1]/copyright-holder
+                 and license/identifier = $images[1]/license/identifier])' />
+
+  <xsl:variable name='abbrev-years-fragment'>
+    <xsl:for-each select='$images[$abbrev-split >= position()]/copyright-year'>
+      <xsl:sort data-type='number' />
+      <copyright-year><xsl:value-of select='.' /></copyright-year>
+    </xsl:for-each>
+  </xsl:variable>
+  <xsl:variable name='abbrev-years'
+    select='exslt:node-set($abbrev-years-fragment)/*' />
+
+  <p>
+    <xsl:text>Images © </xsl:text>
+    <xsl:value-of select='$abbrev-years[1]' />
+    <xsl:if test='$abbrev-years[last()] != $abbrev-years[1]'>
+      <xsl:value-of select='concat("–", $abbrev-years[last()])' />
+    </xsl:if>
+    <xsl:value-of select='concat(" ", $images[1]/copyright-holder)' />
+    <xsl:apply-templates select='$images[1]/license' />
+    <xsl:apply-templates select='$images[position() > $abbrev-split]' />
+    <xsl:text>.</xsl:text>
+  </p>
+</xsl:template>
+
 <xsl:template match='source'>
   <p>
-    <xsl:text>This document was compiled from </xsl:text>
-    <a href='{concat($source-uri, revision, ":", file)}'>
-      <xsl:value-of select='file' />
-    </a>
+    <xsl:text>This document was compiled</xsl:text>
+    <xsl:choose>
+      <xsl:when test='file'>
+        <xsl:text> from </xsl:text>
+        <a href='{concat($source-uri, "blob/", revision, ":", file)}'>
+          <xsl:value-of select='file' />
+        </a>
+      </xsl:when>
+      <xsl:when test='dir'>
+        <xsl:text> from </xsl:text>
+        <a href='{concat($source-uri, "tree/", revision, ":", dir)}'>
+          <xsl:value-of select='dir' />
+        </a>
+      </xsl:when>
+    </xsl:choose>
     <xsl:text> on </xsl:text>
     <xsl:value-of select='compiletime' />
     <xsl:text>.</xsl:text>
   </p>
 </xsl:template>
 
+<!-- Article info block inserted between heading and main contents -->
 <xsl:template match='xhtml:h1[not(preceding::xhtml:h1)]'>
+  <xsl:variable name='nodes'
+    select='/document/article/published|//xhtml:*[@article-info]' />
+
   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
-  <xsl:if test='/document/article/published'>
+
+  <xsl:if test='$nodes'>
     <div id='article-info'>
-      <p>
-        <xsl:text>Posted </xsl:text>
-        <xsl:value-of select='/document/article/published' />
-      </p>
+      <xsl:apply-templates mode='article-info' select='$nodes'>
+        <xsl:sort data-type='number'
+          select='number(generate-id(..)=generate-id(/document/article))' />
+      </xsl:apply-templates>
     </div>
   </xsl:if>
 </xsl:template>
 
+<xsl:template mode='article-info' match='/document/article/published'>
+  <p>
+    <xsl:text>Posted </xsl:text>
+    <xsl:value-of select='/document/article/published' />
+    <xsl:if test='/document/article/updated'>
+      <xsl:if test='/document/article/updated != /document/article/published'>
+        <xsl:text>, last updated </xsl:text>
+        <xsl:value-of select='/document/article/updated' />
+      </xsl:if>
+    </xsl:if>
+  </p>
+</xsl:template>
+
+<xsl:template match='*[@article-info]' />
+<xsl:template mode='article-info' match='*[@article-info]'>
+  <xsl:copy>
+    <xsl:apply-templates select='@*[local-name() != "article-info"]' />
+    <xsl:apply-templates select='node()' />
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template name='imgpara' match='xhtml:p[count(*)=1]
+                                           [normalize-space(text())=""]
+                                           [descendant::xhtml:img]'>
+  <xsl:copy>
+    <xsl:apply-templates select='@*[local-name() != "class"]' />
+    <xsl:attribute name='class'>
+      <xsl:if test='@class'>
+        <xsl:value-of select='concat(@class, " ")' />
+      </xsl:if>
+      <xsl:text>img</xsl:text>
+    </xsl:attribute>
+    <xsl:apply-templates select='node()' />
+  </xsl:copy>
+</xsl:template>
+
+<xsl:key name='gallery'
+  match='xhtml:html/xhtml:p[descendant::*[@generate-gallery]]'
+  use='generate-id(
+        ( ( preceding-sibling::*[not(descendant::*[@generate-gallery])][1]
+          /following-sibling::* ) | self::* ) [1])' />
+
+<xsl:template match='@generate-gallery' />
+<xsl:template match='xhtml:html/xhtml:p[descendant::*[@generate-gallery]]' />
+<xsl:template match='xhtml:html/xhtml:p[key("gallery", generate-id(.))]'>
+  <xsl:variable name='images' select='key("gallery", generate-id(.))' />
+  <xsl:choose>
+    <xsl:when test='count($images) > 1'>
+      <div>
+        <xsl:attribute name='class'>
+          <xsl:text>gallery</xsl:text>
+          <xsl:if test='//xhtml:a[f:contains-token(@class, "left")
+                                  or f:contains-token(@class, "right")]'>
+            <xsl:text> inline</xsl:text>
+          </xsl:if>
+        </xsl:attribute>
+        <xsl:for-each select='$images'>
+          <xsl:call-template name='imgpara' />
+        </xsl:for-each>
+      </div>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:call-template name='imgpara' />
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
 <xsl:template match='/'>
   <html>
     <head>
       <meta name='viewport' content='width=device-width, initial-scale=1' />
       <link rel='stylesheet' type='text/css' href='/style.css' />
+      <link rel='alternate stylesheet' type='text/css' href='/dark.css'
+        title='Dark Style' />
       <link rel="icon" href="data:," />
       <title>
         <xsl:variable name='page-title' select='string(/document/title)' />
       <div id='footer'>
         <xsl:apply-templates select='/document/copyright' />
         <xsl:apply-templates select='/document/license' />
+        <xsl:if test='/document/image[copyright != /document/copyright
+                      or license/identifier != /document/license/identifier]'>
+          <xsl:call-template name='image-attribution' />
+        </xsl:if>
         <xsl:apply-templates select='/document/source' />
       </div>
     </body>
   </html>
 </xsl:template>
 
+<xsl:include href='layouts/whitespace.xsl' />
+<xsl:include href='layouts/clickytable.xsl' />
+
 </xsl:stylesheet>