aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Kummerlaender2014-10-12 12:42:11 +0200
committerAdrian Kummerlaender2014-10-12 12:42:11 +0200
commitbd072c48f380aaf25756c72e2d2ed2474ae9e96d (patch)
tree4b08d282395d51049d35b5a2fd118a8ae5288a8c
downloadStaticXSLT-bd072c48f380aaf25756c72e2d2ed2474ae9e96d.tar
StaticXSLT-bd072c48f380aaf25756c72e2d2ed2474ae9e96d.tar.gz
StaticXSLT-bd072c48f380aaf25756c72e2d2ed2474ae9e96d.tar.bz2
StaticXSLT-bd072c48f380aaf25756c72e2d2ed2474ae9e96d.tar.lz
StaticXSLT-bd072c48f380aaf25756c72e2d2ed2474ae9e96d.tar.xz
StaticXSLT-bd072c48f380aaf25756c72e2d2ed2474ae9e96d.tar.zst
StaticXSLT-bd072c48f380aaf25756c72e2d2ed2474ae9e96d.zip
Extracted the static site generation framework into BuildXSLT module
* i.e. the `detail` transformation chain of [blog.kummerlaender.eu](https://github.com/KnairdA/blog.kummerlaender.eu/) * added module defintion file `StaticXSLT.xml` * revamped documentation accordingly * added MIT license
-rw-r--r--LICENSE20
-rw-r--r--README.md47
-rw-r--r--StaticXSLT.xml6
-rw-r--r--src/steps/list.xsl49
-rw-r--r--src/steps/plan.xsl99
-rw-r--r--src/steps/process.xsl250
-rw-r--r--src/steps/summarize.xsl75
-rw-r--r--src/utility/datasource.xsl24
8 files changed, 570 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2c1c5d6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Adrian Kummerländer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..38b2e25
--- /dev/null
+++ b/README.md
@@ -0,0 +1,47 @@
+# StaticXSLT
+
+…is a [BuildXSLT](https://github.com/KnairdA/BuildXSLT) module implementing a static site generator in XSLT using [InputXSLT](https://github.com/KnairdA/InputXSLT).
+
+## Overview:
+
+The `src/steps` directory contains a chain of transformations which are processed by the build system as specified in `StaticXSLT.xml`.
+
+The first of these transformations `list.xsl` traverses and lists a `source` directory containing various _levels_ depicting the different stages of the actual static site generation process as a base for all further processing.
+
+Based on the results of the `list.xsl` transformation the next transformation `plan.xsl` schedules a number of different tasks to be processed by `process.xsl`. Examples for these tasks are cleaning a `target` directory, linking files and folders and of course generating transformation stylesheets contained within the various levels of the `source` tree.
+
+After the various tasks are processed by `process.xsl` the results of all tasks are summarized by `summarize.xsl` to provide the user with a easy to read plain-text output.
+
+## Usage:
+
+The `StaticXSLT.xml` file defines a [BuildXSLT](https://github.com/KnairdA/BuildXSLT) module which may be called by _StaticXSLT_ based applications as follows:
+
+```
+<task type="module">
+ <input mode="embedded">
+ <datasource>
+ <meta>
+ <source>source</source>
+ <target>target</target>
+ </meta>
+ </datasource>
+ </input>
+ <definition mode="file">[StaticXSLT.xml]</definition>
+</task>
+```
+
+In this example the input tree defines both the `source` and `target` directories relative to the working directory of the `ixslt` executable which is required to make use of the external functions provided by [InputXSLT](https://github.com/KnairdA/InputXSLT). The square brackets around the `StaticXSLT.xml` filename are instructing the custom include entity resolver to resolve the given path against the include path array provided to `ixslt`. This means that _StaticXSLT_ based websites may be generated as follows:
+
+```
+xslt --input make.xml --transformation ../BuildXSLT/build.xsl --include ../StaticXSLT
+```
+
+## Levels:
+
+A _level_ is simply a folder within a given `source` directory which may in turn contain a arbitrary number of transformations and source documents inside subfolders. All transformations within these _levels_ are processed by the _StaticXSLT_ transformation chain which handles datasource dependency resolution and preserves the correct result path context. _Levels_ are processed according to their alphabetic order. Subfolders of _level_ directories that do not contain any XSLT stylesheets and non-stylesheet files are automatically symlinked to their appropriate target directory.
+
+## Data Source and target resolution:
+
+Every transformation contained in one of the levels contains a `meta` variable defining the required data sources and target paths. This information is read during task processing by the `process.xsl` transformation and used to provide each transformation with the data sources it requires and write the output to the path it desires. This definition of requirements and targets directly inside each transformation is an essential part of how this static site generation concept works.
+
+The system currently provides a couple of different data source reading modes such as `full` for reading a complete XML file as input, `iterate` for iterating the second-level elements of a given XML source and `expression` for evaluating a arbitrary XPath expression against a given XML file. Target modes include `plain` for writing a the result into a given file at the appropriate target level and `xpath` for evaluating a XPath expression to generate the target path. This XPath evaluation functionality in combination with the `iterate` data source mode is especially helpful in situations where one wants to generate multiple output files from a single transformation such as when generating article pages or the pages of the article stream.
diff --git a/StaticXSLT.xml b/StaticXSLT.xml
new file mode 100644
index 0000000..80fa071
--- /dev/null
+++ b/StaticXSLT.xml
@@ -0,0 +1,6 @@
+<transformation mode="chain">
+ <link>src/steps/list.xsl</link>
+ <link>src/steps/plan.xsl</link>
+ <link>src/steps/process.xsl</link>
+ <link>src/steps/summarize.xsl</link>
+</transformation>
diff --git a/src/steps/list.xsl b/src/steps/list.xsl
new file mode 100644
index 0000000..c70a6bf
--- /dev/null
+++ b/src/steps/list.xsl
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:InputXSLT="function.inputxslt.application"
+ exclude-result-prefixes="InputXSLT"
+>
+
+<xsl:output
+ method="xml"
+ omit-xml-declaration="yes"
+ encoding="UTF-8"
+ indent="no"
+/>
+
+<xsl:include href="../utility/datasource.xsl"/>
+
+<xsl:template name="list">
+ <xsl:param name="base"/>
+
+ <xsl:for-each select="InputXSLT:read-directory($base)/entry">
+ <xsl:choose>
+ <xsl:when test="@type = 'directory'">
+ <directory name="{./name}">
+ <xsl:call-template name="list">
+ <xsl:with-param name="base" select="./full"/>
+ </xsl:call-template>
+ </directory>
+ </xsl:when>
+ <xsl:otherwise>
+ <file name="{./name}" extension="{./extension}">
+ <xsl:copy-of select="full"/>
+ </file>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+</xsl:template>
+
+<xsl:template match="datasource">
+ <xsl:copy-of select="meta"/>
+
+ <source>
+ <xsl:call-template name="list">
+ <xsl:with-param name="base" select="meta/source"/>
+ </xsl:call-template>
+ </source>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/steps/plan.xsl b/src/steps/plan.xsl
new file mode 100644
index 0000000..653a9ca
--- /dev/null
+++ b/src/steps/plan.xsl
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:dyn="http://exslt.org/dynamic"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ xmlns:InputXSLT="function.inputxslt.application"
+ exclude-result-prefixes="dyn xalan InputXSLT"
+>
+
+<xsl:output
+ method="xml"
+ omit-xml-declaration="yes"
+ encoding="UTF-8"
+ indent="no"
+/>
+
+<xsl:include href="../utility/datasource.xsl"/>
+
+<xsl:template name="traverse">
+ <xsl:param name="source"/>
+ <xsl:param name="target"/>
+ <xsl:param name="path"/>
+ <xsl:param name="node"/>
+
+ <xsl:for-each select="$node/directory">
+ <xsl:choose>
+ <xsl:when test=".//file/@extension = '.xsl'">
+ <xsl:call-template name="traverse">
+ <xsl:with-param name="source" select="$source"/>
+ <xsl:with-param name="target" select="$target"/>
+ <xsl:with-param name="path" select="concat($path, '/', @name)"/>
+ <xsl:with-param name="node" select="."/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <task type="link">
+ <from>
+ <xsl:value-of select="concat($target, '/', $path, '/', @name)"/>
+ </from>
+ <to>
+ <xsl:value-of select="concat($source, '/', $path, '/', @name)"/>
+ </to>
+ </task>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+
+ <xsl:for-each select="$node/file">
+ <xsl:choose>
+ <xsl:when test="@extension = '.xsl'">
+ <task type="generate">
+ <meta>
+ <datasource_prefix>
+ <xsl:value-of select="$target"/>
+ </datasource_prefix>
+ </meta>
+ <source>
+ <xsl:value-of select="concat($source, '/', $path, '/', @name, @extension)"/>
+ </source>
+ <target>
+ <xsl:value-of select="concat($target, '/', $path)"/>
+ </target>
+ </task>
+ </xsl:when>
+ <xsl:when test="@extension = '.css'">
+ <task type="link">
+ <from>
+ <xsl:value-of select="concat($target, '/', $path, '/', @name, @extension)"/>
+ </from>
+ <to>
+ <xsl:value-of select="concat($source, '/', $path, '/', @name, @extension)"/>
+ </to>
+ </task>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:for-each>
+</xsl:template>
+
+<xsl:template match="datasource">
+ <xsl:copy-of select="source"/>
+ <xsl:copy-of select="meta"/>
+
+ <tasks>
+ <task type="clean">
+ <path>
+ <xsl:value-of select="meta/target"/>
+ </path>
+ </task>
+
+ <xsl:call-template name="traverse">
+ <xsl:with-param name="source" select="$root/meta/source"/>
+ <xsl:with-param name="target" select="$root/meta/target"/>
+ <xsl:with-param name="node" select="source"/>
+ </xsl:call-template>
+ </tasks>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/steps/process.xsl b/src/steps/process.xsl
new file mode 100644
index 0000000..7b596b6
--- /dev/null
+++ b/src/steps/process.xsl
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:dyn="http://exslt.org/dynamic"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ xmlns:InputXSLT="function.inputxslt.application"
+ exclude-result-prefixes="dyn xalan InputXSLT"
+>
+
+<xsl:output
+ method="xml"
+ omit-xml-declaration="yes"
+ encoding="UTF-8"
+ indent="no"
+/>
+
+<xsl:include href="../utility/datasource.xsl"/>
+
+<xsl:variable name="source_tree" select="$root/source"/>
+
+<xsl:template name="create_link">
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+
+ <xsl:value-of select="InputXSLT:external-command(
+ concat('ln -sr ./', $to, ' ./', $from)
+ )/self::command/@result"/>
+</xsl:template>
+
+<xsl:template name="clean">
+ <xsl:param name="path"/>
+
+ <xsl:value-of select="InputXSLT:external-command(
+ concat('rm -r ./', $path, '; mkdir ./', $path)
+ )/self::command/@result"/>
+</xsl:template>
+
+<xsl:template name="generate">
+ <xsl:param name="input"/>
+ <xsl:param name="transformation"/>
+ <xsl:param name="target"/>
+
+ <xsl:variable name="generation_result" select="InputXSLT:generate(
+ $input,
+ $transformation,
+ $target
+ )/self::generation"/>
+
+ <subtask>
+ <xsl:attribute name="result">
+ <xsl:value-of select="$generation_result/@result"/>
+ </xsl:attribute>
+ <xsl:if test="$generation_result/@result = 'error'">
+ <log>
+ <xsl:copy-of select="$generation_result/error"/>
+ </log>
+ </xsl:if>
+
+ <target>
+ <xsl:value-of select="$target"/>
+ </target>
+ </subtask>
+</xsl:template>
+
+<xsl:template name="merge_datasource">
+ <xsl:param name="main"/>
+ <xsl:param name="support"/>
+
+ <datasource>
+ <xsl:copy-of select="$main"/>
+ <xsl:copy-of select="$support"/>
+ </datasource>
+</xsl:template>
+
+<xsl:template name="resolve_target">
+ <xsl:param name="prefix"/>
+ <xsl:param name="target"/>
+ <xsl:param name="datasource"/>
+
+ <xsl:choose>
+ <xsl:when test="$target/@mode = 'plain'">
+ <xsl:value-of select="concat($prefix, '/', $target/@value)"/>
+ </xsl:when>
+ <xsl:when test="$target/@mode = 'xpath'">
+ <xsl:value-of select="concat($prefix, '/', dyn:evaluate($target/@value))"/>
+ </xsl:when>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="resolve_datasource">
+ <xsl:param name="prefix"/>
+ <xsl:param name="datasource"/>
+
+ <xsl:for-each select="$datasource">
+ <xsl:element name="{@target}">
+ <xsl:choose>
+ <xsl:when test="@mode = 'full'">
+ <xsl:copy-of select="InputXSLT:read-file(
+ concat($prefix, '/', @source)
+ )/self::file/*/*"/>
+ </xsl:when>
+ <xsl:when test="@mode = 'expression'">
+ <xsl:copy-of select="dyn:evaluate(@source)"/>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:element>
+ </xsl:for-each>
+</xsl:template>
+
+<xsl:template name="compile">
+ <xsl:param name="main"/>
+ <xsl:param name="support"/>
+ <xsl:param name="transformation"/>
+ <xsl:param name="datasource_prefix"/>
+ <xsl:param name="target_prefix"/>
+ <xsl:param name="target"/>
+
+ <xsl:variable name="datasource">
+ <xsl:call-template name="merge_datasource">
+ <xsl:with-param name="main" select="$main"/>
+ <xsl:with-param name="support">
+ <xsl:call-template name="resolve_datasource">
+ <xsl:with-param name="prefix" select="$datasource_prefix"/>
+ <xsl:with-param name="datasource" select="$support"/>
+ </xsl:call-template>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="resolved_target">
+ <xsl:call-template name="resolve_target">
+ <xsl:with-param name="prefix" select="$target_prefix"/>
+ <xsl:with-param name="target" select="$target"/>
+ <xsl:with-param name="datasource" select="xalan:nodeset($datasource)/*[1]"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:call-template name="generate">
+ <xsl:with-param name="input" select="$datasource"/>
+ <xsl:with-param name="transformation" select="$transformation"/>
+ <xsl:with-param name="target" select="$resolved_target"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template name="process">
+ <xsl:param name="task"/>
+
+ <xsl:variable name="transformation" select="InputXSLT:read-file($task/source)/self::file/node()"/>
+ <xsl:variable name="meta" select="$transformation/self::*[name() = 'xsl:stylesheet']/*[name() = 'xsl:variable' and @name = 'meta']"/>
+ <xsl:variable name="main_source" select="$meta/datasource[@type = 'main']"/>
+ <xsl:variable name="support_source" select="$meta/datasource[@type = 'support']"/>
+
+ <xsl:choose>
+ <xsl:when test="$main_source/@mode = 'iterate'">
+ <xsl:for-each select="InputXSLT:read-file(
+ concat($task/meta/datasource_prefix, '/', $main_source/@source)
+ )/self::file/*/entry">
+ <xsl:call-template name="compile">
+ <xsl:with-param name="main">
+ <xsl:element name="{$main_source/@target}">
+ <xsl:copy-of select="."/>
+ </xsl:element>
+ </xsl:with-param>
+ <xsl:with-param name="support" select="$support_source"/>
+ <xsl:with-param name="transformation" select="$transformation"/>
+ <xsl:with-param name="datasource_prefix" select="$task/meta/datasource_prefix"/>
+ <xsl:with-param name="target_prefix" select="$task/target"/>
+ <xsl:with-param name="target" select="$meta/target"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="compile">
+ <xsl:with-param name="main">
+ <xsl:choose>
+ <xsl:when test="$main_source/@mode = 'full'">
+ <xsl:element name="{$main_source/@target}">
+ <xsl:copy-of select="InputXSLT:read-file(
+ concat($task/meta/datasource_prefix, '/', $main_source/@source)
+ )/self::file/*/*"/>
+ </xsl:element>
+ </xsl:when>
+ <xsl:when test="$main_source/@mode = 'expression'">
+ <xsl:element name="{$main_source/@target}">
+ <xsl:copy-of select="dyn:evaluate($main_source/@source)"/>
+ </xsl:element>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:with-param>
+ <xsl:with-param name="support" select="$support_source"/>
+ <xsl:with-param name="transformation" select="$transformation"/>
+ <xsl:with-param name="datasource_prefix" select="$task/meta/datasource_prefix"/>
+ <xsl:with-param name="target_prefix" select="$task/target"/>
+ <xsl:with-param name="target" select="$meta/target"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="task[@type = 'clean']">
+ <xsl:copy>
+ <xsl:attribute name="result">
+ <xsl:call-template name="clean">
+ <xsl:with-param name="path" select="path"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:copy-of select="@* | node()"/>
+ </xsl:copy>
+</xsl:template>
+
+<xsl:template match="task[@type = 'generate']">
+ <xsl:variable name="results">
+ <xsl:call-template name="process">
+ <xsl:with-param name="task" select="."/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="total_count" select="count(xalan:nodeset($results)/subtask)"/>
+ <xsl:variable name="success_count" select="count(xalan:nodeset($results)/subtask[@result = 'success'])"/>
+
+ <xsl:copy>
+ <xsl:attribute name="result">
+ <xsl:choose>
+ <xsl:when test="$success_count = $total_count">
+ <xsl:text>success</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>error</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:copy-of select="@* | source"/>
+ <xsl:copy-of select="$results"/>
+ </xsl:copy>
+</xsl:template>
+
+<xsl:template match="task[@type = 'link']">
+ <xsl:copy>
+ <xsl:attribute name="result">
+ <xsl:call-template name="create_link">
+ <xsl:with-param name="from" select="from"/>
+ <xsl:with-param name="to" select="to"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:copy-of select="@* | node()"/>
+ </xsl:copy>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/steps/summarize.xsl b/src/steps/summarize.xsl
new file mode 100644
index 0000000..0399507
--- /dev/null
+++ b/src/steps/summarize.xsl
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+>
+
+<xsl:output
+ method="text"
+ omit-xml-declaration="yes"
+ encoding="UTF-8"
+ indent="no"
+/>
+
+<xsl:template match="task[@result = 'error']">
+ <xsl:text>&#xa;Error #</xsl:text>
+ <xsl:value-of select="position()"/>
+ <xsl:text>: </xsl:text>
+
+ <xsl:choose>
+ <xsl:when test="@type = 'generate'">
+ <xsl:for-each select="subtask[@result = 'error']">
+ <xsl:text>Generation of "</xsl:text>
+ <xsl:value-of select="target"/>
+ <xsl:text>" failed.</xsl:text>
+
+ <xsl:for-each select="log/error">
+ <xsl:text>&#xa;</xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:when>
+ <xsl:when test="@type = 'link'">
+ <xsl:text>Link from "</xsl:text>
+ <xsl:value-of select="from"/>
+ <xsl:text>" to "</xsl:text>
+ <xsl:value-of select="to"/>
+ <xsl:text>" failed.</xsl:text>
+ </xsl:when>
+ <xsl:when test="@type = 'clean'">
+ <xsl:text>Cleaning of "</xsl:text>
+ <xsl:value-of select="path"/>
+ <xsl:text>" failed.</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:copy-of select="."/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="datasource">
+ <xsl:variable name="total_count" select="count(task)"/>
+ <xsl:variable name="success_count" select="count(task[@result = 'success'])"/>
+
+ <xsl:text>Tasks processed: </xsl:text>
+ <xsl:value-of select="$total_count"/>
+ <xsl:text>&#xa;</xsl:text>
+ <xsl:text>Tasks successful: </xsl:text>
+ <xsl:value-of select="$success_count"/>
+ <xsl:text>&#xa;</xsl:text>
+
+ <xsl:text>▶ Generation </xsl:text>
+ <xsl:choose>
+ <xsl:when test="$total_count = $success_count">
+ <xsl:text>successful</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>failed</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>.&#xa;</xsl:text>
+
+ <xsl:apply-templates select="task[@result = 'error']"/>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/utility/datasource.xsl b/src/utility/datasource.xsl
new file mode 100644
index 0000000..411086a
--- /dev/null
+++ b/src/utility/datasource.xsl
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+>
+
+<xsl:output
+ method="xml"
+ omit-xml-declaration="no"
+ encoding="UTF-8"
+ indent="no"
+/>
+
+<xsl:variable name="root" select="/datasource"/>
+
+<xsl:template match="/">
+ <datasource>
+ <xsl:apply-templates />
+ </datasource>
+</xsl:template>
+
+<xsl:template match="text()|@*"/>
+
+</xsl:stylesheet>