Skip to content

Instantly share code, notes, and snippets.

@xokomola
Created December 21, 2016 14:43
Show Gist options
  • Save xokomola/59a590a423b86bb3ea809c1b03706c4b to your computer and use it in GitHub Desktop.
Save xokomola/59a590a423b86bb3ea809c1b03706c4b to your computer and use it in GitHub Desktop.
Create ODS Spreadsheet with XQuery and BaseX
<tables>
<table name="Sheet 1">
<thead>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
</thead>
<tbody>
<tr>
<td>10</td>
<td>20</td>
<td>30</td>
</tr>
</tbody>
</table>
</tables>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="template" as="xs:string"/>
<xsl:variable name="content" select="doc($template)" as="document-node()"/>
<xsl:template match="/">
<xsl:apply-templates select="$content/office:document-content">
<xsl:with-param name="tables" select="tables/table" tunnel="yes"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="office:spreadsheet">
<xsl:param name="tables" tunnel="yes" as="element(table)*"/>
<xsl:variable name="template" select="table:table[1]"/>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:for-each select="$tables">
<xsl:apply-templates select="$template">
<xsl:with-param name="table" select="." tunnel="yes"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="table:table">
<xsl:param name="table" tunnel="yes" as="element(table)"/>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="table:name" select="$table/@name"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="table:table-row"/>
<xsl:template match="table:table-row[2]">
<xsl:param name="table" tunnel="yes"/>
<xsl:call-template name="create-data-rows">
<xsl:with-param name="table" select="$table"/>
<xsl:with-param name="model" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template match="table:table-row[1]">
<xsl:param name="table" tunnel="yes"/>
<xsl:if test="exists($table/thead/tr)">
<xsl:call-template name="create-header-rows">
<xsl:with-param name="table" select="$table"/>
<xsl:with-param name="model" select="."/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="create-header-rows">
<xsl:param name="table" as="element(table)"/>
<xsl:param name="model" as="element(table:table-row)"/>
<xsl:variable name="template-row" select="$model"/>
<xsl:variable name="template-cell" select="$model/table:table-cell[1]"/>
<xsl:for-each select="$table/thead/tr">
<xsl:variable name="row" select="."/>
<xsl:for-each select="$template-row">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:for-each select="$row/td">
<xsl:apply-templates select="$template-cell">
<xsl:with-param name="cell" select="."/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="create-data-rows">
<xsl:param name="table" as="element(table)"/>
<xsl:param name="model" as="element(table:table-row)"/>
<xsl:variable name="template-row" select="$model"/>
<xsl:variable name="template-cell" select="$model/table:table-cell[1]"/>
<xsl:for-each select="$table/tbody/tr">
<xsl:variable name="row" select="."/>
<xsl:for-each select="$template-row">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:for-each select="$row/td">
<xsl:apply-templates select="$template-cell">
<xsl:with-param name="cell" select="."/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="table:table-cell">
<xsl:param name="cell"/>
<xsl:copy>
<!-- TODO: may need to 'cook up' a set of default attributes -->
<!-- such as: office:value-type="string" calcext:value-type="string" -->
<xsl:apply-templates select="@* except (@table:number-columns-repeated, @table:number-columns-spanned)"/>
<text:p><xsl:value-of select="$cell"/></text:p>
</xsl:copy>
</xsl:template>
<xsl:template match="@office:value-type">
<xsl:attribute name="office:value-type" select="'string'"/>
</xsl:template>
<xsl:template match="@calcext:value-type">
<xsl:attribute name="calcext:value-type" select="'string'"/>
</xsl:template>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
declare function ods:write(
$tables as element(tables),
$path as xs:string,
$options as map(*))
as empty-sequence()
{
let $template := file:path-to-uri(file:resolve-path(concat(file:base-dir(), 'templates/default.ods')))
let $xsl := file:resolve-path(concat(file:base-dir(), 'ods-tables.xsl'))
let $template-dir := file:create-temp-dir('ods-', '.ods', file:temp-dir())
let $extract archive:extract-to($template-dir, file:read-binary($template))
for $entry in file:list($template-dir, true())
where ends-with(file:path-to-uri($entry),'/content.xml')
let $uri := file:path-to-uri(file:resolve-path(concat($template-dir, '/', $entry)))
let $result := file:write($uri, xslt:transform($tables, $xsl, map { 'template': $uri }))
return
file:write-binary($path, archive:create-from($template-dir))
};
@xokomola
Copy link
Author

Not tested.
Copied from an internal module I use at work to quickly generate an ODS spreadsheet from a generic table structure. XLSX goes along the same lines though the XML is not as nice as for ODS (libreOffice, OpenOffice).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment