Configurable XSL stylesheet for general purpose KeePass 2.x to CSV export
- Disclaimer
- Errors and omissions expected.
- Use at your own risk!
June 1st 2024, PC
Main features
- Automatic export of all custom string fields.
- Configurable: Select the fields to include / exclude.
- Three CSV output styles:
- Single header line, one column per field name.
Drawback: Many custom string fields can result in many columns.
- Multiple header lines, one per set of custom string fields.
Useful when only few sets of custom string field names are used.
- Single header line, custom string fields require two columns each.
Drawback: Custom string field names are listed in columns rather than the header.
- Single header line, one column per field name.
CSV header and column structure
General overview, valid for all three output styles
Header Number | Entry status | Group | Fields before auto-added | Remaining string fields: Added automatically | Fields after auto-added |
---|---|---|---|---|---|
single column | single column | single column | multiple columns | multiple columns | multiple columns |
Only used when multiple headers are printed. |
Current Recycle Bin Old version Old & binned |
Group “path” |
Configurable
|
KeePass has two “types” of string fields:
String fields are added automatically,
|
Configurable
|
Header_1 Header_2 … |
Current_Entry Bin_Entry History_Entry Bin_History_Entry |
example: /group/sub/ |
|
Style1: Single header, one column per field name
Header | Group | Title | UserName | Password | URL | Notes | Usr Str Field1 | Usr Str Field2 | Usr Str Field3 | Attachments | UUID | LastModificationTime |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Current_Entry | / | Sample Entry | Sample UName | SamplePass | keepass.info | Sample Notes | WId9GV… | 2024-04-29T08:50:00Z | ||||
Current_Entry | / | Sample Entry #2 | Michael321 | 12345 | keepass.info | Ez3x1M… | 2024-04-19T17:16:08Z | |||||
Current_Entry | / | Two attachments | Mr Two | attached | 0: image1.png 1: image2.png |
3Mg96… | 2024-04-29T08:53:39Z | |||||
Current_Entry | /Group1/ | YTitle | YUsername | XPassword | www.example.com | YNote line 1 YNote line2 |
some value | other value | 2: text_attachment.txt | bXJ+p… | 2024-05-10T10:13:06Z | |
Current_Entry | /Group1/SubGroup/ | XTitle | XUsername | XPassword | www.example.com | XNote line 1 XNote line2 |
a value | Bla bla | dVzdSh… | 2024-05-10T10:30:17Z | ||
Current_Entry | /Group1/SubGroup/ | Attachment Tux | Bearer | Grizzly | tux.example.com | ryLih+… | 2024-05-10T10:31:52Z | |||||
Bin_Entry | /Recycle Bin/ | Empty Coffee | cuppa joe | coffepass | more coffee? | fpb4NA… | 2024-05-10T18:14:08Z | |||||
History_Entry | /Group1/SubGroup/ | Attachment Bear | Bear | Grizzly | www.bear.yogi | 2: text_attachment.txt | bXJ+pv… | 2024-04-29T09:13:17Z | ||||
Bin_History_Entry | /Recycle Bin/ | Empty Coffee | cuppa joe | coffepass | fpb4NA… | 2024-05-10T18:14:00Z |
Style2: Multiple headers
Header_1 | Header | Group | Title | UserName | Password | URL | Notes | Attachments | UUID | LastModificationTime | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Header_2 | Header | Group | Title | UserName | Password | URL | Notes | Usr Str Field1 | Usr Str Field2 | Attachments | UUID | LastModificationTime |
Header_3 | Header | Group | Title | UserName | Password | URL | Notes | Usr Str Field1 | Usr Str Field3 | Attachments | UUID | LastModificationTime |
Header_4 | Header | Group | Title | UserName | Password | URL | Notes | Usr Str Field2 | Attachments | UUID | LastModificationTime | |
Header_1 | Current_Entry | / | Sample Entry | Sample UName | SamplePass | keepass.info | Sample Notes | WId9GV… | 2024-04-29T08:50:00Z | |||
Header_1 | Current_Entry | / | Sample Entry #2 | Michael321 | 12345 | keepass.info | Ez3x1M… | 2024-04-19T17:16:08Z | ||||
Header_1 | Current_Entry | / | Two attachments | Mr Two | attached | 0: image1.png 1: image2.png |
3Mg96… | 2024-04-29T08:53:39Z | ||||
Header_1 | Current_Entry | /Group1/ | YTitle | YUsername | XPassword | www.example.com | YNote line 1 YNote line2 |
2: text_attachment.txt | bXJ+p… | 2024-05-10T10:13:06Z | ||
Header_2 | Current_Entry | /Group1/SubGroup/ | XTitle | XUsername | XPassword | www.example.com | XNote line 1 XNote line2 |
some value | other value | dVzdSh… | 2024-05-10T10:30:17Z | |
Header_3 | Current_Entry | /Group1/SubGroup/ | Attachment Tux | Bearer | Grizzly | tux.example.com | a value | Bla bla | ryLih+… | 2024-05-10T10:31:52Z | ||
Header_4 | Bin_Entry | /Recycle Bin/ | Empty Coffee | cuppa joe | coffepass | more coffee? | fpb4NA… | 2024-05-10T18:14:08Z | ||||
Header_1 | History_Entry | /Group1/SubGroup/ | Attachment Bear | Bear | Grizzly | www.bear.yogi | 2: text_attachment.txt | bXJ+pv… | 2024-04-29T09:13:17Z | |||
Header_1 | Bin_History_Entry | /Recycle Bin/ | Empty Coffee | cuppa joe | coffepass | fpb4NA… | 2024-05-10T18:14:00Z |
Style3: Single header, two columns per custom field
Header | Group | Title | UserName | Password | URL | Notes | Usr1: Name | Usr1: Value | Usr2: Name | Usr2: Value | Attachments | UUID | LastModificationTime |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Current_Entry | / | Sample Entry | Sample UName | SamplePass | keepass.info | Sample Notes | WId9GV… | 2024-04-29T08:50:00Z | |||||
Current_Entry | / | Sample Entry #2 | Michael321 | 12345 | keepass.info | Ez3x1M… | 2024-04-19T17:16:08Z | ||||||
Current_Entry | / | Two attachments | Mr Two | attached | 0: image1.png 1: image2.png |
3Mg96… | 2024-04-29T08:53:39Z | ||||||
Current_Entry | /Group1/ | YTitle | YUsername | XPassword | www.example.com | YNote line 1 YNote line2 |
Usr Str Field1 | some value | Usr Str Field2 | other value | 2: text_attachment.txt | bXJ+p… | 2024-05-10T10:13:06Z |
Current_Entry | /Group1/SubGroup/ | XTitle | XUsername | XPassword | www.example.com | XNote line 1 XNote line2 |
Usr Str Field1 | a value | Usr Str Field3 | Bla bla | dVzdSh… | 2024-05-10T10:30:17Z | |
Current_Entry | /Group1/SubGroup/ | Attachment Tux | Bearer | Grizzly | tux.example.com | ryLih+… | 2024-05-10T10:31:52Z | ||||||
Bin_Entry | /Recycle Bin/ | Empty Coffee | cuppa joe | coffepass | Usr Str Field2 | more coffee? | fpb4NA… | 2024-05-10T18:14:08Z | |||||
History_Entry | /Group1/SubGroup/ | Attachment Bear | Bear | Grizzly | www.bear.yogi | 2: text_attachment.txt | bXJ+pv… | 2024-04-29T09:13:17Z | |||||
Bin_History_Entry | /Recycle Bin/ | Empty Coffee | cuppa joe | coffepass | fpb4NA… | 2024-05-10T18:14:00Z |
CSV output generation: Direct vs indirect
Direct CSV export: KeePass → CSV
In KeePass
- Open the Export dialogue: File → Export
- Select Transform using XSL Stylesheet and set a Destination file path.
- Click OK, enter the database password and pass your XSL file of choice.
Indirect CSV generation: KeePass → XML → web browser → CSV
Benefit
When experimenting with different XSL CSV generation settings, directly exporting the CSV file from KeePass is both tedious and time consuming.
The faster way is viewing the CSV output in a web browser, allowing to simply reload the page.
Caveat
Viewing a local file on your computer requires browser file access (explained below).
Step 1: Export as XML
First export the database as an XML file:
⇅ |
→ |
Step 2: Associate the XML file with the XSL stylesheet
Edit the XML file:
- Insert
<?xml-stylesheet type="text/xsl" href="my_xml_stylesheet.xsl"?>
as line 2. - Adapt my_xml_stylesheet.xsl to your XSL file’s name.
- Store the XML and XSL files in the same folder (or use a relative path).
1 | <?xml version="1.0" encoding="utf-8" standalone="yes"?> |
2 | <?xml-stylesheet type="text/xsl" href="my_xml_stylesheet.xsl"?> |
3 | <KeePassFile> |
4 | <Meta> |
5 | … |
Viewing in your browser – Option 1: http server
Place your XML and XSL files on a http server and open the XML file’s corresponding http link.
For many, this option will be a no-go. → See Option 2.
Viewing in your browser – Option 2: Launch a chromium based browser with file access
Steps
- Pick a chromium based browser.
- Close all of its windows.
- Launch with the --allow-file-access-from-files option.
- e.g. chrome --allow-file-access-from-files
- Open your locally stored XML file.
Details
Safety warning
Browsers default to not allowing file access for security reasons:
- Locally opened files could, in principle, steal data from your file system or run an exploit.
- While theoretically excluded from local file access, it’s still possible, that a malicious website or browser extension gains an additional path for wrongdoing.
Safety precautions
- Do not browse the web or anything other than the local KeePass XML files while file access is active.
- Closing all browser windows and launching the browser normally, i.e. without the file access flag, will terminate file access. → i.e. you can browse the web again.
- If you don’t trust your extensions: Temporarily disable them.
Procedure
- Pick a convenient chromium-based browser: Google Chrome, Opera, Vivaldi, MS-Edge, …
- I usually choose MS-Edge as I otherwise don’t use it, so closing all of its windows doesn’t bother me and no extensions are installed anyway.
- Close all your chosen browser’s windows.
- Launch your browser of choice with the --allow-file-access-from-files option.
Windows
Press ⊞ Win + R to run one of the following commands.
- chrome --allow-file-access-from-files
- opera --allow-file-access-from-files
- vivaldi --allow-file-access-from-files
- msedge --allow-file-access-from-files
In some cases, you may have to enter the full path to chrome / opera / … . Please use a search engine or an AI for information on how to do this.
Linux
Open a terminal and experiment with the following commands.
- google-chrome --allow-file-access-from-files
- opera --allow-file-access-from-files
- vivaldi --allow-file-access-from-files
macOS
Open a terminal and experiment with the following commands.
- open -a "Google Chrome.app" --args --allow-file-access-from-files
- open -a "Opera.app" --args --allow-file-access-from-files
- open -a "Vivaldi.app" --args --allow-file-access-from-files
- In case the above commands fail, try:
open -a "/Applications/Google Chrome.app" --args --allow-file-access-from-files
open -a "/Applications/Opera.app" --args --allow-file-access-from-files
open -a "/Applications/Vivaldi.app" --args --allow-file-access-from-files
- Open the local XML file in your browser:
- Press Control + O and select your XML file.
- Alternatively: Paste the XML file’s full path in the browser’s address bar.
Why not Firefox?
Firefox only allows permanently enabling or disabling file access. Closing and re-opening Firefox alone won’t suffice.
Btw: Apple’s Safari browser seems to have the same issue.
HTML rendered example
- XSL
- XML (pay attention to line 2, which associates the XSL stylesheet)
- Resulting CSV when configured for style 1’s output:
Configuring the XSL
Basics
- Open the XSL in a text editor (example).
- Navigate to destination or variable by searching with the editor’s “Find” command.
→ Always search from the beginning of the file (first occurrence *).
Main anchor points
Use these when you want to have a look at all parameters, rather than specific ones.
-
Configuration parameters & properties
- The configurable parameters are from this point onward.
-
csv export options 1/4: What to include in the output
- These are the main options.
-
csv export options 2/4: Separators, breaks and which parts to print
- Separators used in the CSV output.
Example: Delimiter for nested KeePass groups. - Breaks – Example: Line breaks for multi line fields.
- Parts to print: Currently limited to whether to print attachment reference numbers.
- Separators used in the CSV output.
-
csv export options 3/4: Names
- Names used in the csv output.
For example: “Attachments” for the attachments column header.
- Names used in the csv output.
-
csv export options 4/4: Empty rows
- Options for adding empty rows between headers and classes of entries.
Setting boolean parameters
Example:
<xsl:variable name="example_bool_parameter" select="true()" />
→ Set select to either false() do not export or true() do export.
Include or exclude the recycle bin and history
Recycle bin: export_recycle_bin
<xsl:variable name="export_recycle_bin" select="true()" />
History: export_history
<xsl:variable name="export_history" select="true()" />
Output style selection
Styles 1, 2 & 3
Style 1: use_style_1_unified_header Example: xml + xsl🛈 or txt/csv🛈
<xsl:variable name="use_style_1_unified_header" select="true()" />
Style 2: use_style_2_multi_header Example: xml + xsl🛈 or txt/csv🛈
<xsl:variable name="use_style_2_multi_header" select="true()" />
Style 3: use_style_3_nameValCols Example: xml + xsl🛈 or txt/csv🛈
<xsl:variable name="use_style_3_nameValCols" select="true()" />
The database summary output
Database summary: print_db_stat Example: xml + xsl🛈 or txt🛈
<xsl:variable name="print_db_stat" select="true()" />
When setting multiple outputs to true()
Result: Text output with multiple sections🛈 Example: xml + xsl🛈 or txt/csv🛈
Fields: Include, exclude and order of
Order of the added fields
As visualised in chapter Output Structure, fields are added in the following order:
- As listed in fields_before_autoadd__unfiltered
→ Explicitly included fields 1/2.
- String fields not listed in 1, 3 and excluded_str_field_names_unescaped
→ Implicitly included fields.
- As listed in fields_after_autoadd__unfiltered
→ Explicitly included fields 2/2.
Listing fields
Explicitly included fields are listed and sorted as in the following example:
<xsl:variable
name=
"fields_before_autoadd__unfiltered"
"fields_after_autoadd__unfiltered"
>
<xsl:text>String=UserName</xsl:text>
<xsl:value-of select="$CRLF" />
<xsl:text>Binary</xsl:text>
<xsl:value-of select="$CRLF" />
<xsl:text>UUID</xsl:text>
<xsl:value-of select="$CRLF" />
<xsl:text>Times/LastModificationTime</xsl:text>
<xsl:value-of select="$CRLF" />
</xsl:variable>
Every field identifier, including the very last one, must be followed by an
<xsl:value-of select="$CRLF" />
statement.
In case you got lost and cannot find the field listing
Navigate to the top of the XSL file and search for fields_before_autoadd__unfiltered.
→ It should be the first hit.
How to figure out / determine field identifier names
Export the KeePass database as an xml file and study the tags within xml elements named <Entry>
.
Details
Locating a particular entry
Search 🛈 for its UUID 🛈.
Example KeePass xml export – file entry
<Entry>
<UUID>Ez3x1MRhrkud4gWpdKAojQ==</UUID>
<IconID>0</IconID>
<Tags></Tags>
…
<Times>
<CreationTime>2024-04-19T17:16:08Z</CreationTime>
<LastModificationTime>2024-04-19T17:16:08Z</LastModificationTime>
<LastAccessTime>2024-04-29T10:10:24Z</LastAccessTime>
…
</Times>
<String>
<Key>UserName</Key>
<Value>Michael321</Value>
</String>
…
<Binary>
<Key>super_secret_text.txt</Key>
<Value Ref="4" />
</Binary>
…
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
<Association>
<Window>*Test Form - KeePass*</Window>
<KeystrokeSequence></KeystrokeSequence>
</Association>
…
</AutoType>
<History /> <!-- Do not select History! -->
</Entry>
String field identifiers
Use: <xsl:text>String=…</xsl:text>
Where … is as in
<String><Key>…</Key></String>
<String>
<Key>User One</Key>
<Value>Michael321</Value>
</String>
↳ List as: <xsl:text>String=User One</xsl:text>
Excluding specific string field names
String field exclusion – Basics
Use: <xsl:text>…</xsl:text>
Where … is as in
<String><Key>…</Key></String>
Note: String= is not included.
<String>
<Key>Exclude this</Key>
<Value>Don’t want to export this.</Value>
</String>
↳ List as: <xsl:text>Exclude this</xsl:text>
Example listing
<xsl:variable name="excluded_str_field_names_unescaped">
<xsl:text>Exclude this</xsl:text>
<xsl:value-of select="$CRLF" />
<xsl:text>Exclude too</xsl:text>
<xsl:value-of select="$CRLF" />
</xsl:variable>
String field exclusion – Wildcards
*
?
?+
To match the wildcards literally, escape with ^.
I.e.^*
,^?
and^?+
.
Example listing
<xsl:variable name="excluded_str_field_names_unescaped">
<xsl:text>Unwanted*</xsl:text>
<xsl:value-of select="$CRLF" />
<xsl:text>Question^?</xsl:text>
<xsl:value-of select="$CRLF" />
</xsl:variable>
Include attachment file names in the output
Use: <xsl:text>Binary</xsl:text>
→ This will export all the entry’s attachment file names into a single multiline CSV cell.
Details & example
All or nothing
Unlike string fields, listing attachments in the output is only possible in an all or nothing fashion.
Example: Entry with two attachments
<Binary>
<Key>confidential.txt</Key>
<Value Ref="4" />
</Binary>
<Binary>
<Key>secret.txt</Key>
<Value Ref="7" />
</Binary>
↳ Export the file names and ref numbers with: <xsl:text>Binary</xsl:text>
Attachments |
---|
4: confidential.txt 7: secret.txt |
All other field identifiers
Use: <xsl:text>tag name</xsl:text>
<xsl:text>UUID</xsl:text>
<xsl:text>Times/CreationTime</xsl:text>
<xsl:text>AutoType/Association/KeystrokeSequence</xsl:text>
Exporting AutoType
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
<Association>
<Window>*Test Form - KeePass*</Window>
<KeystrokeSequence></KeystrokeSequence>
</Association>
…
</AutoType>
Nonetheless, a relatively crude “raw” export can be achieved with the following settings:
<xsl:variable name="fields_after_autoadd__unfiltered">
…
<xsl:text>AutoType/Enabled</xsl:text>
<xsl:value-of select="$CRLF" />
<xsl:text>AutoType/DataTransferObfuscation</xsl:text>
<xsl:value-of select="$CRLF" />
<xsl:text>AutoType/Association/Window</xsl:text>
<xsl:value-of select="$CRLF" />
<xsl:text>AutoType/Association/KeystrokeSequence</xsl:text>
<xsl:value-of select="$CRLF" />
</xsl:variable>
AutoType entrys with more than one associated window will result in the export of multiple values within a single column.
→ Details in the next two sub-chapters ⬎
Output when selecting tags with multiple occurrences
All matching tags will be exported into a single CSV cell!
Details
When selecting tags with multiple occurrences …
→ … all occurrences will be exported!
An entry will always have more than one string field.
Consequently, the following will export all KeePass string field names (called Key
):
<xsl:text>String/Key</xsl:text>
Results containing more than one match will be numbered.
I.e. a match of Notes
, Password
& Title
would appear as:
⟨String|1/Key⟩Notes%%%⟨String|2/Key⟩Password%%%⟨String|3/Key⟩Title
- Separating characters can be modified:
Output when selecting tags with descendant elements
Only text nodes at the selected element’s topmost level are exported.
Details
When selecting tags with descendant elements …
→ … text nodes within child elements won’t be exported.
<String>
has the child nodes <Key>
and <Value>
<Key>User One</Key>
<Value>Michael321</Value>
</String>
So when selecting <xsl:text>String</xsl:text>,
the text nodes in <Key>
and <Value>
won’t be exported because child elements are excluded.
In case of three string elements, the output would be:
⟨String|1⟩%%%⟨String|2⟩%%%⟨String|3⟩
I.e. three matching <String>
elements without content.
Attachment reference №
Whether or not to include the attachment reference number in the output.
As in e.g.:"4: attachment.txt"
<xsl:variable name="print_attachment_ref_no" select="true()" />
The separator between the reference number and the attachment’s file name.
As in e.g.:"4: attachment.txt"
<xsl:variable name="attachment_refnum_separator">
<xsl:text>: </xsl:text>
</xsl:variable>
Soft break – multi line KeePass fields
Which line separator to use when exporting multi line KeePass fields.
Details
- When setting the separator to line-feed,
<xsl:value-of select="$LF" />
, reading the CSV with a spreadsheet app (e.g. LibreOffice Calc, Google Sheets or MS Excel), will result in multi-line cells. - Using line-feeds as softbreak has its downsides:
- When editing the CSV text file: A line no longer automatically is a single row.
- When viewing spreadsheets: The resulting row height can be obtrusive and get in the way.
- Softbreaks can be set to any text string and later be converted back to line feeds.
- To convert a text string to a soft break new line in Excel and Calc:
- Assuming:
- The cells to be converted are in Sheet1.
- The soft-break string is ####.
- Add an empty sheet (Sheet2).
- Enter the following formula in Sheet2, cell A1:
=SUBSTITUTE($Sheet1.A1,"####",CHAR(10))
- Copy the formula to the same range of cells as in Sheet1.
- Assuming:
Soft break setting | Resulting CSV/text file | CSV imported to spreadsheet | ||||||
Soft break set to linefeed
<xsl:variable name="softbrk"> |
8 rows 1
2 3 4 5 6 7 8 "r1 c1","r1 c2 L1
r1 c2 L2 r1 c2 L3","r1 c3  L1 r1 c3  L2" "r2 c1","r2 c2 L1 r2 c2 L2 r2 c2 L3","r2 c3  L1 r2 c3  L2" |
|
||||||
Soft break set to ##
<xsl:variable name="softbrk"> |
2 rows 1
2 "R1C1","R1##C2##bla","R1C3##Bar"
"R2C1","R2##C2##Foo","R2C3##FBar" |
|
Extended configuration
For information on the “extended” / all other configuration options
Look through the comments in csv export options */4.
Changeworthy extended options
group_delim, gpath_leading_char & gpath_trailing_char
Delimiter for nested KeePass groups.
→ Change in case of conflict with your group names.
- Example:
MyGroup/MySubgroup
🛈
- Example:
-
All KeePass groups are descendants of the root group, i.e. the top most group.
As there is only one root group, excluding it from the output frees up some space.
- Example 🛈 with setting
true()
→MyGroup/MySubgroup
- Example 🛈 with setting
false()
→MyDatabase/MyGroup/MySubgroup
- Example 🛈 with setting
header_list_only_last_fieldname
Example: KeePass field
Times/CreationTime
- when true() → the header will show
CreationTime
- when false() → the header will show
Times/CreationTime
- when true() → the header will show
Potentially changeworthy extended options
Custom column and row header names
→ Look through csv export options 3/4: Names
Empty CSV rows when headers and entry types change
→ Look through csv export options 4/4: Empty rows
Adding empty rows can be useful for two reasons:
- Visual separation
- Spreadsheet apps: Jumping to empty cells with Control + ↑↓
Less likely to be changed extended options
-
When escaping wildcards, the code internally replaces them with a unicode character.
I.e.*
is replaced with escaped_aster and?
with escaped_qmark.→ Change in case of conflict with your field names.
Downloads
All files including documentation
XSL files
Master XSL file
This will export the database summary and all three styles .
- Html rendered XSL .
- Example TXT / CSV output + .
- Example XML file to use with the XSL
Pre-selected XSL files
These files are structurally identical to the master XSL, with the only difference being the configuration (only one export active instead of the master file’s four).