In this article, I have put together the things that I found useful in the hope that it will help you to put you on track to make your own StyleSheets for ToDoList.
Introduction
I have just built a custom StyleSheet for TDL, my very first XSLT file, it is not an easy task when you have never touched an XSLT file. It is not a surprise that StyleSheets delivered with TDL are not suited to beginners learning, but it was a surprise to me that all the needed information was spread across so many places.
So, I write this article to put together things that I have found useful in hope it will help you to put you on track to make your own StyleSheets for ToDoList.
Keep in mind that English is still a foreign language for me. Please, feel free to report mistakes.
Background
For beginners, basic knowledge of XML format is preferable, and knowledge of HTML will help too. For advanced StyleSheets, knowledge of XML, HTML and XPath is mandatory.
The XML File Format
A quick introduction to XML file format. ToDoList's TaskLists and StyleSheets are XML files.
The basic structure of an XML file is an Element, an element is made of an opening tag and a closing tag.
<TODOLIST>
</TODOLIST>
The opening tag of an element can contain Attributes, a value is associated with each attribute, the value is always a string, even numeric values.
<TODOLIST PROJECTNAME="My TaskList"; FILEFORMAT="10"> </TODOLIST>
And an element can contain some text or other elements nested between its two tags.
<TODOLIST PROJECTNAME="My TaskList" FILEFORMAT="10">
<TASK TITLE="My first Task">
<TASK TITLE="My sub task">
<COMMENTS>My comments</COMMENTS>
<CATEGORY>MyCat</CATEGORY>
</TASK>
</TASK>
<TASK TITLE="My second Task">
</TASK>
</TODOLIST>
An XML file is made of a single main element (root) containing everything nested inside. By itself, an XML file is organized as a tree, the single main element is the root of the tree.
TODOLIST
is the Root Element. It contains other elements. TASK
is an Element. It contains other elements. COMMENTS
is an Element. It contains text. PROJECTNAME
is an Attribute of TODOLIST
with Value FILEFORMAT
is an Attribute of TODOLIST
with Value TITLE
is an Attribute of TASK
with Value
<TODOLIST PROJECTNAME="My TaskList" FILEFORMAT="10">
<TASK TITLE="My first Task">
<TASK TITLE="My sub task">
<COMMENTS>My comments</COMMENTS>
<CATEGORY>MyCat</CATEGORY>
</TASK>
</TASK>
<TASK TITLE="My second Task">
</TASK>
</TODOLIST>
A TaskList
contains many more elements and attributes.
For more details on XML, XSLT, HTML or XPath, I recommend w3schools.com.
First, Get a TDL StyleSheet Example
At first, I searched for an example StyleSheet
and found that the article XSL Transform for ToDoList contains a StyleSheet
for TDL aimed to be an example. My first experiments were rather frustrating until I understood that the example was buggy.
Here are the corrections for the StyleSheet
.
Everywhere it is needed, replace:
<xsl:apply-templates />
with:
<xsl:apply-templates select="TASK" />
and, replace:
... "@COMMENTS"
with:
... "COMMENTS"
After correction, the example works far better. The corrected file is bpsToDoListStyler_Rev019.xsl.
Experimenting with the Example StyleSheet
To play with the example, I recommend to start with Tree view and get all attributes. The output is formatted in HTML and can be used to Print, Preview or Transform the TaskList
. A lot of options are commented out, just uncomment them to see what happens.
How ToDoList Deals with StyleSheets
TDL can use StyleSheet
s for operations such as Print, Export or Transform. In every case, TDL generate a specific temporary TaskList
that reflect actual settings, and the StyleSheet
is applied on that temporary TaskList
.
The specific TaskList
is built using these criteria:
- If in Tree view, the temporary list will be a tree, if in List view, the temporary list will be flat.
- Actual sorting will be used.
- The dialog box lets you choose which tasks are included and which attributes.
Choose the result you want:
- Print: The output will be printed on paper.
- Preview: The output will be shown on screen the same as it would be printed.
- Transform: The output will be saved on disk and shown on your internet navigator if Text or HTML formats are used. The difference with Print and Preview is that the HTML is not limited to printable features. The other usage of Transform is to generate other XML files.
The temporary TaskList file is stored in directory %temp%, the file name is TaskList name followed by .intermediate.txt. So, the temp file of MyTasks.tdl will be MyTasks.intermediate.txt in the %temp% directory. Open in a text editor to see the structure and which elements and attributes are in the file.
Pitfalls with TDL StyleSheets
Empty Result
The Problem
It is all in the title. The result is empty.
The Answer
First possibility, there is a compilation error. You have to find what is the error and correct it. Unfortunately, TDL does not provide anything that can give even basic clue of what is going wrong. If you don't have any kind of debugger, the best is to start with a generic working example and make incremental changes with a test to validate each change.
Second possibility, there is no match or nothing to output. First make sure your StyleSheet
always outputs something when the root matches. The easiest way to do this is by ensuring that something will be put on the result whatever the content of the TaskList
. By getting something as a result, you ensure that the StyleSheet
compiles OK. If you don't get what you should, something is certainly wrong in the logic of the StyleSheet
.
Ignored XML Document with an <xsl:template match="/"> Template
The Problem
Obviously, the XML tree is not walked, and if:
<xsl:template match="/">
is replaced with:
<xsl:template match="/TODOLIST">
everything works.
The Answer
The whole trick is that <xsl:template match="/">
matches the XML document and the child element is TODOLIST
when TASK
is expected.
If you have a document matching template, you also need a root matching template, because the first has to call the second.
Accents in StyleSheets
The Problem
The file encoding can be a problem when you use accents, the problem is so huge that even if only in a comment, it is enough to fail to compile. You will need to apply the right encoding.
The Answer
To solve the problem, there is two places to check:
- Declare the encoding in the
xml
tag:
="1.0"="utf-8"
- In your text editor, choose the
char
encoding that fit your needs: UTF-8, UTF-16, iso-8859-1 ...
UTF-8 is generally a good choice. In any case, make sure both match.
Doing "apply-templates" on Elements that don't have "template"
The Problem
That was the error in the example for TDL. The generic...
<xsl:apply-templates />
...will match any element inside the current element. It is a problem when you encounter a COMMENT
(or any element that is not a TASK
). Since the COMMENT
has no matching template, the default behavior is applied, the text in the COMMENT
is simply dump as is to output.
This is the reason of the unformatted text that appears when you run the original buggy example as in file bpsToDoListStyler_Rev018.xsl.
The Answer
Use apply-templates
only for nested TASK
s.
<xsl:apply-templates select="TASK" />
Note: The same problem also applies to TDL 6.9.6 and earlier StyleSheet
s: Project-Overview-HTML.xsl, SimpStyler0.2.xsl, TodoListStyler_Firefox.xsl and TodoListStyler_v1.5.xsl.
An alternative is to declare a template that will match everything.
<xsl:template match="*">
Be careful of what you do, it is as powerful as dangerous if not mastered, it easily leads to unwanted results.
XML StyleSheets Snippets
XML files are organized like trees and XSLT are basically about walking the tree while applying the transformation, more sophisticated transformations can be done at the cost of more complicated programming if input tree structure doesn't match the output tree structure.
Some of the examples are using html tags, look at the next chapter for explanations about html tags in a StyleSheet
.
XSLT Comments
Advice: Document your xslt StyleSheet
s with comments everywhere you do something not obvious.
Beginners often think that documenting is a waste of time, it is wrong. You will see that it is not a waste of time when you will come back to your code six months or a year later because comments will help you to recall what the code was intended to do.
StyleSheet Organization
xslt is based on a template matching language. It means that there is no explicit entry point and there is no explicit chaining of matching templates, it is data driven. If more than one template matches the current data, it is not an error, the right template is simply chosen at runtime by following some rules.
StyleSheet Launch
A StyleSheet
always starts by trying to execute a template that matches the whole document.
<xsl:template match="/">
If it fails, the interpreter looks for a template that matches the root of the XML file. Like...
<xsl:template match="/TODOLIST">
...which is a template that matches only a root element named TODOLIST
. Or...
<xsl:template match="TODOLIST">
...which is a template that match only an element named TODOLIST, not necessary a root.
Matching Templates
Note that matching templates are not linked anywhere in the XSLT code.
<xsl:apply-templates />
will decide dynamically which matching template to call and in which order. In order to keep some control over the StyleSheet
execution, it is preferable to use the syntax...
<xsl:apply-templates select="TASK"/>
...where you specify which element you want to match.
Hello World
The classical one. See file HelloWord.xsl for the complete example.
="1.0"
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>Hello Word</xsl:text>
</xsl:template>
</xsl:stylesheet>
The <xsl:output method="text"/>
tells that the output is raw text, no formatting, no nothing, simply text.
The <xsl:template match="/">
is matching the root element of any document.
The element <xsl:text>
surrounds whatever text you want to output
Hello ToDoList
See file TDLSS 1.xsl for the complete example.
="1.0"
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes"
doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" />
<xsl:template match="/TODOLIST">
<xsl:element name="html">
<xsl:element name="body">
<xsl:text>Hello ToDoList</xsl:text>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The <xsl:output method="html" ...
tells that the output is an html page, with all usual formatting, at the cost of a little complication.
The <xsl:template match="/TODOLIST">
will match only a root element named TODOLIST
, this is the name of the root element of a tasklist.
Reading Attributes Values
See file TDLSS 2.xsl for the complete example.
<xsl:template match="TODOLIST">
<xsl:element name="html">
<xsl:element name="head">
<xsl:element name="title">
<xsl:value-of select="@PROJECTNAME" />
</xsl:element>
</xsl:element>
<xsl:element name="body">
<xsl:value-of select="@PROJECTNAME" />
<xsl:element name="br" />
<xsl:value-of select="@FILENAME" />
<xsl:element name="br" />
</xsl:element>
</xsl:element>
</xsl:template>
The <xsl:value-of select="@PROJECTNAME" />
output the value of the PROJECTNAME
attribute.
Walking the Tree
See file TDLSS 3.xsl for the complete example.
<xsl:template match="TODOLIST">
<xsl:element name="html">
<xsl:element name="head">
<xsl:element name="title">
<xsl:value-of select="@PROJECTNAME" />
</xsl:element>
</xsl:element>
<xsl:element name="body">
<xsl:value-of select="@PROJECTNAME" />
<xsl:element name="br" />
<xsl:value-of select="@FILENAME" />
<xsl:element name="br" />
<xsl:element name="br" />
<xsl:apply-templates select="TASK" />
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="TASK">
<xsl:value-of select="@TITLE" />
<xsl:element name="br" />
</xsl:template>
The <xsl:apply-templates select="TASK" />
is looking for any element named TASK
which is a child of element TODOLIST
and will apply the template <xsl:template match="TASK">
to each occurrence.
Walking the Tree Recursively
See file TDLSS 4.xsl for the complete example.
<xsl:template match="TASK">
<xsl:value-of select="@TITLE" />
<xsl:element name="br" />
<xsl:apply-templates select="TASK" />
</xsl:template>
The <xsl:apply-templates select="TASK" />
is what makes the tree walking recursive.
Reading Text Elements Values
See file TDLSS 5.xsl for the complete example.
<xsl:template match="TASK">
<xsl:value-of select="@TITLE" />
<xsl:element name="br" />
<xsl:text>CATEGORY: </xsl:text>
<xsl:value-of select="CATEGORY" />
<xsl:element name="br" />
<xsl:text>COMMENTS: </xsl:text>
<xsl:value-of select="COMMENTS" />
<xsl:element name="br" />
<xsl:element name="br" />
<xsl:apply-templates select="TASK" />
</xsl:template>
The <xsl:value-of select="COMMENTS" />
output the value of the COMMENTS
text element.
Single Condition: if
See file TDLSS 6.xsl for the complete example.
<xsl:if test="COMMENTS">
<xsl:text>COMMENTS: </xsl:text>
<xsl:value-of select="COMMENTS" />
<xsl:element name="br" />
</xsl:if>
The xsl:if
is a do something when test is true and do nothing when test is false.
Multiple Condition: choose
See file TDLSS 7.xsl for the complete example.
<xsl:choose>
<xsl:when test="COMMENTS">
<xsl:text>COMMENTS: </xsl:text>
<xsl:value-of select="COMMENTS" />
<xsl:element name="br" />
</xsl:when>
<xsl:otherwise>
<xsl:text>No COMMENT</xsl:text>
</xsl:otherwise>
</xsl:choose>
The xsl:choose
is a multiple condition structure. The first when
structure, which is true
, is executed, if no test is true, the otherwise structure is executed.
Calling a template
See file TDLSS 8.xsl for the complete example.
<xsl:call-template name="fix-breaks">
<xsl:with-param name="text">
<xsl:value-of select="COMMENTS" />
</xsl:with-param>
</xsl:call-template>
. . .
<xsl:template name="fix-breaks">
<xsl:param name="text" />
. . .
</xsl:template>
Named templates <xsl:template name="fix-breaks">
are nothing else that subprograms in other languages. They are called by <xsl:call-template name="fix-breaks">
.
Fixing Breaks
See file TDLSS 8.xsl for the complete example.
<xsl:template name="fix-breaks">
<xsl:param name="text" />
<xsl:choose>
<xsl:when test="contains($text,' ')">
<xsl:value-of select="substring-before($text,' ')" />
<xsl:element name="br"/>
<xsl:call-template name="fix-breaks">
<xsl:with-param name="text">
<xsl:value-of select="substring-after($text,' ')" />
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Multi lines text need to be fixed for html output because CRLF
is not understood and must be replaced with <br/>
.
Getting Tasks Path from a Tree View
See file TDLSS 9.xsl for the complete example.
<xsl:template name="get_Task_Ancestors">
<xsl:if test="count(ancestor::TASK)>0">
<xsl:for-each select="(ancestor::TASK)">
<xsl:value-of select="@TITLE"/>
<xsl:text> - </xsl:text>
</xsl:for-each>
</xsl:if>
</xsl:template>
This template works only if tasks are nested (tree view). It list the TITLE
of parents starting from the root.
Getting Tasks Path from a List View
See file TDLSS13.xsl for the complete example.
<xsl:template name="get_Task_Path">
<xsl:param name="ref" />
<xsl:choose>
<xsl:when test="$ref='0'">
<xsl:for-each select="../TASK[@ID=$ref]">
<xsl:value-of select="@TITLE"/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="../TASK[@ID=$ref]">
<xsl:if test="@PARENTID!='0'">
<xsl:call-template name="get_Task_Path">
<xsl:with-param name="ref">
<xsl:value-of select="@PARENTID" />
</xsl:with-param>
</xsl:call-template>
<xsl:text> - </xsl:text>
</xsl:if>
<xsl:value-of select="@TITLE"/>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This template works only if tasks are nor nested (list view). It lists the TITLE
of parents starting from the root. Check that the parents are included in the list.
Showing Tasks Nesting
See file TDLSS10.xsl for the complete example.
In html, lists are the natural way to show indenting by simply nesting lists.
<xsl:template match="/TODOLIST">
<xsl:element name="html">
<xsl:element name="head">
<xsl:element name="title">
<xsl:value-of select="@PROJECTNAME" />
</xsl:element>
</xsl:element>
<xsl:element name="body">
. . .
<xsl:element name="ul">
<xsl:apply-templates select="TASK" />
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="TASK">
<xsl:element name="li">
<xsl:text>Title: </xsl:text>
<xsl:value-of select="@TITLE" />
<xsl:element name="br" />
</xsl:element>
<xsl:element name="ul">
<xsl:apply-templates select="TASK" />
</xsl:element>
</xsl:template>
Simply add <ul>
and <li>
at the right place in the code walking the tree.
Using a XML Document Matching Template
See files TDLSS11.xsl and TDLSS12.xsl for complete examples.
Usually, we start with the root matching template, like in example file TDLSS11.xsl.
<xsl:template match="/TODOLIST">
<xsl:element name="html">
<xsl:element name="head">
<xsl:element name="title">
<xsl:value-of select="@PROJECTNAME" />
</xsl:element>
</xsl:element>
<xsl:element name="body">
<xsl:text>Root matching template</xsl:text>
<xsl:element name="br" />
<xsl:element name="ul">
<xsl:apply-templates select="TASK" />
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
But sometimes, it is needed to start with a document matching template, like in example file TDLSS12.xsl which does mostly the same thing.
<xsl:template match="/">
<xsl:element name="html">
<xsl:element name="head">
<xsl:element name="title">
<xsl:value-of select="@PROJECTNAME" />
</xsl:element>
</xsl:element>
<xsl:element name="body">
<xsl:text>Document matching template</xsl:text>
<xsl:element name="br" />
<xsl:apply-templates />
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="/TODOLIST">
<xsl:text>Root matching template</xsl:text>
<xsl:element name="br" />
<xsl:element name="ul">
<xsl:apply-templates select="TASK" />
</xsl:element>
</xsl:template>
You can see that the HTML document stuff is always in the first called template, and the document matching template calls the root as a child element.
Including HTML Tags in a StyleSheet
When you want to format the output, the best is to use html tags.
HTML Root
The root structure of an html page is something like:
<html>
<head>
. . .
</head>
<body>
. . .
</body>
</html>
As it appear only once in the output, it must be in the template that matches the root.
<xsl:template match="/TODOLIST">
<xsl:element name="html">
<xsl:element name="head">
. . .
</xsl:element>
<xsl:element name="body">
. . .
<xsl:apply-templates select="TASK" />
. . .
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
See file TDLSS 3.xsl for the complete example.
CRLF in HTML
CRLF is only used in html source code and doesn't do anything in the rendering of the page, the <br />
tag must be used instead.
<xsl:element name="br" />
and results in:
<br />
List
An html list is a simple way to show tasks nesting by indenting the tasks in output. It is easy because the nesting of tasks match the nesting of lists.
An html list is made of 2 parts:
- a
List
delimiter <ul>
- a
ListLine
delimiter <li>
The List
delimiter surrounds a list and appears once per list. Its natural place is in the template that treats a parent task.
<xsl:element name="ul">
<xsl:apply-templates select="TASK" />
</xsl:element>
The ListLine
delimiter appears once per list item. Its natural place is in the template that treats a child task.
<xsl:element name="li">
<xsl:text>Title: </xsl:text>
<xsl:value-of select="@TITLE" />
<xsl:element name="br" />
</xsl:element>
The zip contains the examples files:
- File bpsToDoListStyler_Rev018.xsl is the example file from article XSL Transform for ToDoList.
- File bpsToDoListStyler_Rev019.xsl is the corrected file as described there after.
- File HelloWord.xsl, example file, see snippet
- File HelloTDL99.xsl, examples files, see snippet
Links
History
- 1st May, 2015: First draft
- 19th May, 2015: Added some stuff, updated download and corrections
- 1st June, 2015: Typos
- 15th June, 2015: Added more stuff and updated download
- 27th November, 2015: Added more stuff and updated download