layout '/listing.erb'
layout '/default.xml'
layout '/default.xsl'
+ layout '/embed-svg.xsl'
filter :relativize_paths, type: :xml
- filter :xhtml_compat
+ filter :xhtml_compat, fix_doctype: true
write item.identifier.without_ext + ".xhtml"
end
write @item.identifier.to_s
end
+compile '/icons/**/*.svg', rep: :icon32 do
+ filter :svg2png, width: 32, height: 32
+ write @item.identifier.without_ext + "-32.png"
+end
+
compile '/**/*' do
filter :copybin if @item.binary?
write @item.identifier.to_s
}
}
- &:focus ~ table th.clicky-#{$col}>label~label>span {
+ &:focus ~ table th.clicky-#{$col}>label~label>span:first-child {
border-color: $foregroundcolour;
}
&:checked ~ table {
// Update table header state
& th.clicky-#{$col} {
- img+img {
+ .svg+.svg {
display: -moz-inline-box !important;
display: inline-block !important;
}
- img { display: none; }
+ .svg { display: none; }
}
}
- &:focus ~ table th.clicky-#{$col}>label~label>img {
+ &:focus ~ table th.clicky-#{$col}>label~label .svg {
border-color: $foregroundcolour;
}
}
th.clicky-#{$col}>label {
- &, &>* {
- white-space: nowrap;
- vertical-align: middle;
+ white-space: nowrap;
+ cursor: pointer;
+ line-height: 1.5em;
+
+ &>* {
display: -moz-inline-box;
display: inline-block;
- cursor: pointer;
+ border: 1px dotted transparent;
+ vertical-align: middle;
}
- &>* { border: 1px dotted transparent; }
-
// Expand the first label a bit so the table (hopefully)
// does not reshape as columns are selected.
&:first-child {
}
&:active { color: $linkactivecolour; }
- &:first-child:active>span, &~label:active>img {
+ &:first-child:active>span, &~label:active>.svg {
border-color: $linkactivecolour;
}
- img {
+ .svg {
margin-left: 0.25em;
- width: 1.5em;
- height: auto;
}
+
+ .svg, svg, img.svgfallback {
+ height: 1.5em;
+ width: auto;
+ }
+ .svg svg { width: 1.5em; }
}
}
table.filelist {
- &>tr>*:first-child, &>*>tr>*:first-child {
- &+td { min-width: 50%; }
- width: 0;
+ &>*>tr>*:first-child {
+ &+* { width: 50%; }
+ // chrome doesn't like width: 0 for some reason
+ width: 0.1px;
}
- tbody img {
- display: block;
- height: 1.5em;
- width: auto;
+ tbody {
+ .svg, svg, img.svgfallback {
+ height: 1.5em;
+ width: auto;
+ }
+
+ .svg {
+ svg { width: 1.5em; }
+ display: inline-block;
+ }
}
}
<label for='rev-{generate-id(@clicky)}' style='display: none'>
<xsl:text>⁠</xsl:text>
<span><xsl:apply-templates select='node()' /></span>
- <img alt='FWD' width='16' height='16' src='/images/down.svg' />
- <img alt='REV' width='16' height='16' src='/images/up.svg' style='display: none' />
+ <img alt='FWD' width='16' height='16' src='/icons/down.svg' />
+ <img alt='REV' width='16' height='16' src='/icons/up.svg' style='display: none' />
</label>
<script type='x'>--></script>
</xsl:copy>
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 script' />
+<xsl:output cdata-section-elements='style script' />
<xsl:param name='source-uri'
select='"//git.draconx.ca/gitweb/homepage.git/"' />
--- /dev/null
+<?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>
<!--
Nick's web site: XSLT helper functions.
- Copyright © 2019-2020 Nick Bowler
+ Copyright © 2019-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
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:func='http://exslt.org/functions'
xmlns:f='http://draconx.ca/my-functions'
extension-element-prefixes='func f'>
+<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' />
+
<!-- Returns true iff the given element is an XHTML span-level element -->
<func:function name='f:element-is-span'>
<xsl:param name='node' select='.' />
or f:element-is-span($node)' />
</func:function>
+<!-- f:ends-with(a, b) returns true iff a ends with b -->
+<func:function name='f:ends-with'>
+ <xsl:param name='str' />
+ <xsl:param name='suffix' />
+ <func:result select='$suffix=substring($str,
+ string-length($str)-string-length($suffix)+1)' />
+</func:function>
+
<!-- Remove leading whitespace from a string -->
<func:function name='f:strip-leading'>
<xsl:param name='str' select='.' />
files[f] = {
sorttime: if t then t.to_f else 0.0 end,
- displaytime: if t then t.getutc.strftime "%Y-%m-%d %H:%M UTC" end,
+ displaytime: if t then t.getutc.strftime "%Y-%m-%d %H:%M\u00a0UTC" end,
displaysize: displaysize,
size: size,
type: type,
return <<~EOF
<td>#{if f[:type]
"<img alt='#{f[:type]}' width='16' height='16' src='#{case f[:type]
- when :DIR; "/images/folder.svg"
- when :UP; "/images/return.svg"
+ when :DIR; "/icons/folder.svg"
+ when :UP; "/icons/return.svg"
else raise "no icon for filetype #{f[:type]}"
end}' />"
end}</td>
next if ts[i].nil? or ts[i][:node] != :delim
next if ts[i][:value] == '*'
+ if ts[i-1]
+ # keep whitespace before class selectors
+ next if ts[i][:value] == '.' and ts[i-1][:node] == :whitespace
+ end
+
ts[i-1] = nil if ts[i-1] and ts[i-1][:node] == :whitespace
ts[i+1] = nil if ts[i+1] and ts[i+1][:node] == :whitespace
end
--- /dev/null
+# Nick's web site: svg2png filter: convert SVG to PNG using rsvg-convert.
+#
+# 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/>.
+
+class SVG2PNG < Nanoc::Filter
+ identifier :svg2png
+ type :text => :binary
+
+ def run(content, params = {})
+ args = ["-o", output_filename]
+ params.each do |key, val|
+ next if !val
+
+ args << "--#{key.to_s.gsub("_", "-")}"
+ if val != true then args << val.to_s end
+ end
+
+ dummy, status = Open3.capture2("rsvg-convert", *args,
+ stdin_data: content)
+ raise "rsvg-convert failed" if status != 0
+ end
+end
class XhtmlCompatFilter < Nanoc::Filter
identifier :xhtml_compat
+ requires 'nokogiri'
+
+ Xmlns = {
+ math: 'http://www.w3.org/1998/Math/MathML',
+ svg: 'http://www.w3.org/2000/svg',
+ }.freeze
+
+ XHTMLPublic = '-//W3C//DTD XHTML 1.1//EN'
+ MathPublic = '-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN'
+ MathSystem = 'http://www.w3.org/Math/DTD/mathml2/xhtml-math11-f.dtd'
+ SVGPublic = '-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN'
+ SVGSystem = 'http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd'
+
+ # XSLT 1.0 as implemented in Nokogiri canot construct doctypes based
+ # on content. When using MathML or SVG elements in XHTML a different
+ # doctype is needed: select one based on which elements are present.
+ def fix_doctype(content, params = {})
+ return "#{content}" if not params[:fix_doctype]
+
+ doc = Nokogiri::XML(content)
+ doctype = doc.internal_subset
+
+ return "#{content}" if doctype.external_id != XHTMLPublic
+
+ if not doc.xpath("//svg:svg", Xmlns).empty?
+ doctype.remove
+ doc.create_internal_subset("html", SVGPublic, SVGSystem)
+ elsif not doc.xpath("//math:math", Xmlns).empty?
+ doctype.remove
+ doc.create_internal_subset("html", MathPublic, MathSystem)
+ end
+
+ return doc.to_xml
+ end
def run(content, params = {})
+ text = fix_doctype(content, params)
+
# Old versions of Netscape get confused by <hr/> but have no problem
# with <hr />, so avoid that by adding spaces to such elements.
- text = content.gsub(/([^[:space:]])\/>/m, '\1 />');
+ text.gsub!(/([^[:space:]])\/>/m, '\1 />');
# Even older versions of Netscape interpret any script as Javascript,
# which causes major problems with the CDATA hack; solve that by making
text.gsub!("<![CDATA[-->]]>", '\&*/')
# Delete any zero-width word joiners added for XSLT processing.
- return text.delete "\u2060"
+ text.delete! "\u2060"
+
+ return text
end
end