Skip to content

Instantly share code, notes, and snippets.

@ernstki
Last active November 23, 2024 18:19
Show Gist options
  • Save ernstki/c3f484737a293f76abfcd75e1a173a85 to your computer and use it in GitHub Desktop.
Save ernstki/c3f484737a293f76abfcd75e1a173a85 to your computer and use it in GitHub Desktop.
Pretty-printing stylesheet for KeePass XML exports

Pretty-printing KeePass XML export files

First, create an XML export using KeePass for Windowsnot KeePassX, the export format is completely different.

Then add a line like:

<?xml-stylesheet href="keepass_xml.xsl" type="text/xsl"?>

to the exported KeePass .xml file, right below the line that looks like:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>

In other words, make <?xml-stylesheet ... ?> the second line in the file.

As for which stylesheet you can choose, there are two options:

  1. keepass_xml.xsl (recommended)

    • prints password entries in roughly the same order as they appear in the graphical "tree" control in KeePass
  2. keepass_xml-alpha_sort.xsl (work-in-progress)

    • sorts entries alphabetically; this one may lag behind the non-alpha version in terms of appearance and completeness

Save the exported .xml file, then open that file in a web browser.

WARNING! WARNING! DANGER WILL ROBINSON!!!

You must "securely erase" the .xml file when you're done!

If you save your KeePass database in a cloud storage service, disable the sync client first, or work on a copy in some other (offline) directory, because versions of deleted files are stored on the provider's servers. Safest bet is to work out of a temporary encrypted volume that you delete afterwards (see below).

If you have no idea what any of this means, then ask someone who does!

Any copies of the PLAIN TEXT, UNENCRYPTED .xml file left lying around will compromise ALL of the accounts that were contained in the original database.

PLEASE, PLEASE, take this into consideration before exporting and printing the .xml file. EVEN THE OFFICE PRINTER will be keeping copies of this unencrypted file on its internal hard drive!

For Linux
Use the shred utility with the -u (unlink) option.
For macOS
Use rm -P (overwrites three times with rotating bit patterns)
For Windows
Use Eraser - http://eraser.heidi.ie

The default settings are sufficient to scrub all traces from magnetic media, at least sufficiently that they cannot be recovered except with crazy-expensive equipment that petty thieves (personal enemies, etc.) are not likely to have access to. Even state actors like three-letter security agencies will have a hard time of it.

Using a "scratchpad" volume

As a precaution against creating insecure temp files elsewhere on the filesystem, you can create a temporary encrypted container volume (using something like VeraCrypt, Cryptomator, or macOS's Disk Utility), then mount that volume and work on the XML files there.

Pro Tip: You can also use this volume to export and diff XML dumps of the KeePass database when cloud storage conflicts occur (as they often do when one sync client is detached from the network for a period of time, and meanwhile makes changes to the KeePass database.

Improvements for the future

  • Figure out how to indent the resulting HTML file so that the subgroups' left margin reflects the appropriate nesting.

References

  1. http://www.joelsklar.com/xslt.htm#example1

    • which is not well-formed XML until you change <? xml ... to <?xml ... (i.e., remove the extra space)!
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="no" version="1.0" />
<xsl:template match="/">
<xsl:processing-instruction name="xml-stylesheet">href="keepass_xml.css" type="text/css"</xsl:processing-instruction>
<xsl:text>&#10;</xsl:text>
<pwlist><xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="//pwentry">
<xsl:sort select="group"/> <!-- sort by group -->
<xsl:sort select="title"/> <!-- then sort by title -->
</xsl:apply-templates>
</pwlist>
</xsl:template>
<xsl:template match="pwentry">
<xsl:copy-of select="." />
<xsl:text>&#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>
pwlist {
/* display: table; */
}
pwentry {
border: 1px dotted grey;
width: 800px;
display: block;
margin-bottom: 0.5em;
padding: 10px;
position: relative;
font-family: sans-serif;
page-break-inside: avoid; /* only works in Opera */
/* display: table-row; */
}
image,uuid,creationtime,lastmodtime,lastaccesstime,expiretime,attachment {
display: none;
}
group {
background: grey;
position: absolute;
margin-left: -10px;
margin-top: -10px;
color: white;
font-weight: bold;
padding: 0.4em;
width: 175px;
overflow: hidden;
font-variant: small-caps;
font-size: 70%;
/* text-shadow: black 1px 1px 1px; */
border-radius: 0 0 5px 0;
}
title,username,notes,attachdesc {
margin-left: 185px;
}
title {
font-size: 110%;
display: block;
margin-right: 255px;
line-height: 1em;
margin-bottom: 5px;
clear: both;
font-weight: bold;
/* text-shadow: grey 1px 1px 5px; */
}
username:after {
content: ' / ';
margin-right: 10px;
}
username, password {
font-family: monospace;
font-size: 130%;
margin-bottom: 0.5em;
display: inline-block;
}
url {
position: absolute;
top: 0;
right: 0;
display: inline-block;
font-family: sans-serif;
font-size: 80%;
margin: 0.1em 0.5em;
color: blue;
text-decoration: underline;
width: 250px;
overflow: hidden;
height: 1em;
text-align: right;
padding: 0.3em;
}
notes {
display: block;
margin-top: 0.5em;
font-size: 75%;
background-color: #ffee99;
padding: 1em;
border: 2px solid #a89d65;
border-radius: 5px;
}
br {
display: block;
}
attachdesc {
margin-top: 1em;
display: inline-block;
border: 1px solid darkgray;
background: lightgray;
padding: 0.2em 1em;
font-size: 75%;
}
attachdesc:before {
content: 'Attachment: ';
font-weight: bold;
}
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no" version="1.0" />
<!--xsl:preserve-space elements="notes"/-->
<!--xsl:strip-space elements="p"/-->
<!--xsl:param name="LineEndChar" select="'&#13;'"/-->
<xsl:param name="LineEndChar" select="'&#xA;'"/>
<xsl:template match="/">
<xsl:processing-instruction name="xml-stylesheet">href="keepass_xml.css" type="text/css"</xsl:processing-instruction>
<xsl:text>&#10;</xsl:text>
<pwlist>
<xsl:apply-templates select="//pwentry"/>
</pwlist>
</xsl:template>
<xsl:template match="pwentry">
<pwentry>
<!--xsl:apply-templates select="group"/-->
<xsl:copy-of select="group|title|username|password" />
<xsl:apply-templates select="url"/>
<xsl:apply-templates select="notes"/>
</pwentry>
</xsl:template>
<!-- Add extra level of indent for sub-groups -->
<!--xsl:template match="//group">
</xsl:template-->
<!-- maybe skip empty nodes?
see: http://stackoverflow.com/questions/4404491/xslt-to-remove-empty-nodes-and-nodes-with-1
-->
<xsl:template match="//url[text()]">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="//notes[text()]">
<notes>
<xsl:call-template name="PreserveLineBreaks">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</notes>
</xsl:template>
<!-- Half-hearted attempt to collapse empty <p></p>'s -->
<!--xsl:template match="p[text()]">
<xsl:if test="empty(normalize-space(.))">
</xsl:if>
<xsl:apply-templates />
</xsl:template-->
<!-- See: http://www.danrigsby.com/blog/index.php/2008/01/03/preserving-line-breaks-in-xml-while-transforming-to-html-with-xslt/ -->
<!-- Convert newlines to <br/>'s (or <p></p>'s) -->
<xsl:template name="PreserveLineBreaks">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text,$LineEndChar)">
<xsl:value-of select="substring-before($text,$LineEndChar)"/><br/>
<xsl:call-template name="PreserveLineBreaks">
<xsl:with-param name="text">
<xsl:value-of select="substring-after($text,$LineEndChar)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment