]> git.draconx.ca Git - homepage.git/blobdiff - layouts/whitespace.xsl
Move whitespace handling templates into a separate file.
[homepage.git] / layouts / whitespace.xsl
diff --git a/layouts/whitespace.xsl b/layouts/whitespace.xsl
new file mode 100644 (file)
index 0000000..c15639a
--- /dev/null
@@ -0,0 +1,116 @@
+<!--
+  Nick's web site: white-space normalization for Nokogiri.
+
+  Copyright © 2019, 2022 Nick Bowler
+
+  Nokogiri's pretty-printer seems 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.
+
+  In general, adding or removing whitespace from an XHTML document is unsafe
+  (changes the meaning) around span-level elements, but it is OK around other
+  kinds of elements.
+
+  These templates exploit the Nokogiri behaviour in two ways:
+
+  - by explicitly stripping whitespace wherever it is safe to do so, attempting
+    to allow pretty printing as much as possible without changing the meaning
+    of the document.
+
+  - by explicitly adding text nodes to suppress pretty printing in
+    situations where it would otherwise change the meaning of the document.
+
+  The text nodes which are added consist of U+2060 word joiner characters.
+  These should be removed from the final document in a separate pass.
+
+  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:xhtml='http://www.w3.org/1999/xhtml'
+  xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+  xmlns:f='http://draconx.ca/my-functions'>
+
+<xsl:import href='functions.xsl' />
+
+<!--
+  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:copy>
+    <xsl:apply-templates select='node()|@*' />
+    <xsl:text>&#x2060;</xsl:text>
+  </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>
+
+</xsl:stylesheet>