Skip to content

Instantly share code, notes, and snippets.

@grugnog
Last active September 8, 2017 20:37
Show Gist options
  • Save grugnog/35874bf250edc6bd531edae152b95e43 to your computer and use it in GitHub Desktop.
Save grugnog/35874bf250edc6bd531edae152b95e43 to your computer and use it in GitHub Desktop.
Stylesheet to convert a JIRA report list exported as XML into basic markdown, including all non-empty fields.
<?xml version="1.0" encoding="UTF-8"?>
<!--
Stylesheet to convert a JIRA report list exported as XML into
basic markdown, including all non-empty fields.
To run: saxon-xslt -xsl:jira2md.xsl jira-export.xml
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:functx="http://www.functx.com" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0">
<xsl:output omit-xml-declaration="yes" method="text" indent="no"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="rss/channel"/>
</xsl:template>
<xsl:variable name="vAllowedSymbols" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789- '"/>
<xsl:template match="rss/channel">
<xsl:for-each select="item">
<!-- Sanitized file name -->
<xsl:result-document method="text" indent="no" href="{substring(normalize-space(translate(title, translate(title, $vAllowedSymbols, ''), '')),1,250)}.md">
<xsl:apply-templates select="."/>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template match="item">
<xsl:text># </xsl:text>
<xsl:value-of select="title"/>
<xsl:for-each select="*">
<xsl:choose>
<!-- Only add sections with actual content -->
<xsl:when test="string-length(.) &gt; 0">
<xsl:text>&#xa;</xsl:text>
<xsl:text>## </xsl:text>
<xsl:value-of select="concat(upper-case(substring(name(.), 1, 1)), lower-case(substring(name(.), 2)))"/>
<xsl:text>&#xa;</xsl:text>
<xsl:choose>
<!-- Loop over comments adding author and date -->
<xsl:when test="name(.) = 'comments'">
<xsl:for-each select="comment">
<xsl:text>### Comment by </xsl:text>
<xsl:value-of select="./@author" disable-output-escaping="yes"/>
<xsl:text>&#xa;</xsl:text>
<xsl:text>*</xsl:text>
<xsl:value-of select="./@created" disable-output-escaping="yes"/>
<xsl:text>*</xsl:text>
<xsl:text>&#xa;&#xa;</xsl:text>
<!-- Process comment HTML to markdown -->
<xsl:for-each select="parse-xml-fragment(.)">
<xsl:apply-templates select="* | node()"/>
</xsl:for-each>
</xsl:for-each>
</xsl:when>
<xsl:when test="name(.) = 'description'">
<!-- Process description HTML to markdown -->
<xsl:for-each select="parse-xml-fragment(.)">
<xsl:apply-templates select="* | node()"/>
</xsl:for-each>
</xsl:when>
<xsl:when test="count(*) &gt; 1">
<xsl:for-each select="*">
<xsl:text>* </xsl:text>
<xsl:apply-templates select="* | node()"/>
<xsl:text>&#xa;</xsl:text>
</xsl:for-each>
<xsl:text>&#xa;</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="* | node()"/>
<xsl:text>&#xa;</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template match="title">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="customfieldname|name">
<xsl:text>**</xsl:text>
<xsl:value-of select="."/>
<xsl:text>:</xsl:text>
<xsl:text>** </xsl:text>
</xsl:template>
<!-- HTML to markdown rules courtesy of https://gist.github.com/gabetax/1702774 -->
<!-- Required for li indenting -->
<xsl:function name="functx:repeat-string" as="xs:string">
<xsl:param name="stringToRepeat" as="xs:string?"/>
<xsl:param name="count" as="xs:integer"/>
<xsl:sequence select="string-join((for $i in 1 to $count return $stringToRepeat), '')"/>
</xsl:function>
<xsl:template match="li">
<xsl:value-of select="functx:repeat-string(' ', count(ancestor::li))"/>
<xsl:choose>
<xsl:when test="name(..) = 'ol'">
<xsl:value-of select="position()"/>
<xsl:text>. </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>* </xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="normalize-space(concat(., ' '))"/>
<xsl:apply-templates select="* except (ul|ol)"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="ul|ol"/>
</xsl:template>
<!-- Don't process text() nodes for these - prevents unnecessary whitespace -->
<xsl:template match="ul|ol">
<xsl:apply-templates select="* except text()"/>
</xsl:template>
<xsl:template match="a">
<xsl:text>[</xsl:text>
<xsl:apply-templates select="node()|text()"/>
<xsl:text>](</xsl:text>
<xsl:value-of select="@href"/>
<xsl:text>)</xsl:text>
</xsl:template>
<xsl:template match="img">
<xsl:text>![</xsl:text>
<xsl:value-of select="@alt"/>
<xsl:text>](</xsl:text>
<xsl:value-of select="@src"/>
<xsl:text>)</xsl:text>
</xsl:template>
<xsl:template match="strong|b">
<xsl:text>**</xsl:text>
<xsl:value-of select="."/>
<xsl:text>**</xsl:text>
</xsl:template>
<xsl:template match="em|i">
<xsl:text>*</xsl:text>
<xsl:value-of select="."/>
<xsl:text>*</xsl:text>
</xsl:template>
<xsl:template match="code">
<!-- todo: skip the ` if inside a pre -->
<xsl:text>`</xsl:text>
<xsl:value-of select="."/>
<xsl:text>`</xsl:text>
</xsl:template>
<xsl:template match="br">
<xsl:text>
</xsl:text>
</xsl:template>
<!-- Block elements -->
<xsl:template match="hr">
<xsl:text>----
</xsl:text>
</xsl:template>
<xsl:template match="p|div">
<xsl:apply-templates select="*|text()"/>
<xsl:text>
</xsl:text>
<!-- Block element -->
</xsl:template>
<xsl:template match="*[matches(name(), 'h[1-6]')]">
<xsl:value-of select="functx:repeat-string('#', xs:integer(substring(name(), 2)))"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="*|text()"/>
<xsl:text>
</xsl:text>
<!-- Block element -->
</xsl:template>
<xsl:template match="pre">
<xsl:text>
</xsl:text>
<xsl:value-of select="replace(concat(., ' '), '&#10;', '&#10; ')"/>
<xsl:text>
</xsl:text>
<!-- Block element -->
</xsl:template>
<xsl:template match="blockquote">
<xsl:text>&gt; </xsl:text>
<xsl:value-of select="replace(concat(., ' '), '&#10;', '&#10;&gt; ')"/>
<xsl:text>
</xsl:text>
<!-- Block element -->
</xsl:template>
</xsl:stylesheet>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment