ToDoList 的样式表:教程
我在为 ToDoList 制作第一个样式表时学到的知识和遇到的问题
引言
我刚刚为 TDL 构建了一个自定义样式表,这是我的第一个 XSLT 文件,当你从未接触过 XSLT 文件时,这并不是一件容易的事。TDL 提供的样式表不适合初学者学习,这并不奇怪,但令我惊讶的是,所有需要的信息都分散在许多地方。
因此,我写这篇文章是为了整合我发现的有用信息,希望能帮助你开始制作自己的 ToDoList 样式表。
请注意,英语对我来说仍然是一门外语。请随时报告错误。
背景
对于初学者,最好具备 XML 格式的基础知识,HTML 知识也会有帮助。对于高级样式表,XML、HTML 和 XPath 知识是必不可少的。
XML 文件格式
XML 文件格式简介。ToDoList 的任务列表和样式表都是 XML 文件。
XML 文件的基本结构是**元素**,一个元素由一个开始标签和一个结束标签组成。
<TODOLIST>
</TODOLIST>
元素的开始标签可以包含**属性**,每个属性都关联着一个值,值始终是一个字符串,即使是数值。
<TODOLIST PROJECTNAME="My TaskList"; FILEFORMAT="10"> </TODOLIST>
一个元素可以在其两个标签之间包含一些文本或其他**嵌套**的元素。
<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>
XML 文件由一个包含所有嵌套内容的单一主元素(根)组成。XML 文件本身组织成一棵树,单一主元素就是树的根。
TODOLIST
是**根元素**。它包含其他元素。TASK
是一个**元素**。它包含其他元素。COMMENTS
是一个**元素**。它包含文本。PROJECTNAME
是TODOLIST
的一个**属性**,其**值**为FILEFORMAT
是TODOLIST
的一个**属性**,其**值**为TITLE
是TASK
的一个**属性**,其**值**为
<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>
一个TaskList
包含许多更多的元素和属性。
有关 XML、XSLT、HTML 或 XPath 的更多详细信息,我推荐 w3schools.com。
首先,获取一个 TDL 样式表示例
起初,我搜索了一个示例样式表
,并发现文章 XSL Transform for ToDoList 包含了一个针对 TDL 的样式表
,旨在作为示例。我的第一次实验相当令人沮丧,直到我明白那个示例是有 bug 的。
以下是样式表
的修正。
在所有需要的地方,替换
<xsl:apply-templates />
用
<xsl:apply-templates select="TASK" />
然后,替换
... "@COMMENTS"
用
... "COMMENTS"
修正后,示例运行得更好。修正后的文件是 bpsToDoListStyler_Rev019.xsl。
试验示例样式表
要玩转示例,我建议从 Tree view 开始并获取所有属性。输出会以 HTML 格式显示,可用于打印、预览或转换TaskList
。许多选项被注释掉了,只需取消注释即可查看结果。
ToDoList 如何处理样式表
TDL 可以使用样式表
来执行打印、导出或转换等操作。在任何情况下,TDL 都会生成一个反映实际设置的特定临时TaskList
,然后将样式表
应用于该临时TaskList
。
特定的TaskList
是根据以下标准构建的
- 如果在 Tree view 中,临时列表将是树状的;如果在 List view 中,临时列表将是平坦的。
- 将使用实际的排序。
- 对话框允许你选择包含哪些任务和哪些属性。
选择你想要的结果
- 打印:输出将打印在纸上。
- 预览:输出将在屏幕上显示,与打印时相同。
- 转换:输出将保存到磁盘,如果使用文本或 HTML 格式,将在你的浏览器中显示。与打印和预览的区别在于,HTML 不仅限于可打印功能。转换的另一种用途是生成其他 XML 文件。
临时TaskList文件存储在%temp%目录中,文件名是TaskList名称后跟.intermediate.txt。因此,MyTasks.tdl的临时文件将是%temp%目录中的MyTasks.intermediate.txt。在文本编辑器中打开它,查看文件结构以及其中包含的元素和属性。
TDL 样式表的陷阱
结果为空
问题
标题即是内容。结果为空。
答案
第一种可能性是编译错误。你必须找到错误并纠正它。不幸的是,TDL 不提供任何可以给出错误线索的功能。如果你没有任何调试器,最好的办法是从一个通用的工作示例开始,进行增量更改,并对每次更改进行测试以验证。
第二种可能性是,没有匹配项或没有要输出的内容。首先确保你的样式表
在根匹配时始终输出一些内容。最简单的方法是确保无论TaskList
的内容如何,都会产生一些结果。通过获得一些结果,可以确保样式表
编译成功。如果你没有得到预期的结果,那么样式表
的逻辑肯定有问题。
被忽略的 XML 文档带有 <xsl:template match="/"> 模板
问题
显然,XML 树没有被遍历,如果
<xsl:template match="/">
被替换为
<xsl:template match="/TODOLIST">
一切都会正常工作。
答案
整个窍门在于,<xsl:template match="/">
匹配的是 XML 文档,而其子元素是 TODOLIST
,而此时需要的是 TASK
。
如果你有一个文档匹配模板,你也需要一个根匹配模板,因为前者必须调用后者。
样式表中的重音符号
问题
当你使用重音符号时,文件编码可能是一个问题,这个问题非常大,即使只在注释中,也足以导致编译失败。你需要应用正确的编码。
答案
要解决这个问题,需要检查两个地方
- 在
xml
标签中声明编码<?xml version="1.0" encoding="utf-8" ?>
- 在你的文本编辑器中,选择适合你需求的
字符
编码:UTF-8、UTF-16、iso-8859-1……
UTF-8 通常是一个不错的选择。无论如何,确保两者匹配。
对没有“模板”的元素执行“apply-templates”
问题
这就是 TDL 示例中的错误。通用的...
<xsl:apply-templates />
...将匹配当前元素内的任何元素。当你遇到一个 COMMENT
(或任何不是 TASK
的元素)时,这会成为一个问题。由于 COMMENT
没有匹配的模板,将应用默认行为,COMMENT
中的文本将被直接输出。
这就是运行原始有 bug 的示例(如文件 bpsToDoListStyler_Rev018.xsl)时出现未格式化文本的原因。
答案
仅对嵌套的 TASK
s 使用 apply-templates
。
<xsl:apply-templates select="TASK" />
注意:同样的问题也适用于 TDL 6.9.6 及更早版本的样式表
:Project-Overview-HTML.xsl、SimpStyler0.2.xsl、TodoListStyler_Firefox.xsl 和 TodoListStyler_v1.5.xsl。
一种替代方法是声明一个匹配所有内容的模板。
<xsl:template match="*">
小心你的操作,它和你掌握的程度一样强大也一样危险,如果不熟练,很容易导致不必要的结果。
XML 样式表片段
XML 文件以树状结构组织,XSLT 的基本原理是在遍历树的同时应用转换,更复杂的转换可以在输入树结构与输出树结构不匹配的情况下完成,但代价是更复杂的编程。
一些示例使用了 html 标签,请参阅下一章关于样式表
中 html 标签的说明。
XSLT 注释
建议:在你的 xslt样式表
中,凡是做了一些不明显的事情的地方,都加上**注释**。
<!-- My Comments anywhere I need -->
初学者常常认为文档记录是浪费时间,这是错误的。当你六个月或一年后回顾代码时,你会发现它不是浪费时间,因为注释将帮助你回忆起代码的预期用途。
样式表组织
xslt 基于模板匹配语言。这意味着没有明确的入口点,也没有明确的匹配模板链,它是数据驱动的。如果多个模板匹配当前数据,这并不是一个错误,而是通过遵循一些规则在运行时选择正确的模板。
样式表启动
样式表
总是通过尝试执行一个匹配整个文档的模板来开始。
<xsl:template match="/">
如果失败,解释器会查找一个匹配 XML 文件根的模板。例如...
<xsl:template match="/TODOLIST">
...这是一个只匹配名为 TODOLIST
的根元素的模板。或者...
<xsl:template match="TODOLIST">
...这是一个只匹配名为 TODOLIST 的元素(不一定是根)的模板。
匹配模板
请注意,匹配模板在 XSLT 代码中没有链接到任何地方。
<xsl:apply-templates />
将动态决定调用哪个匹配模板以及调用的顺序。为了保持对样式表
执行的一些控制,最好使用语法...
<xsl:apply-templates select="TASK"/>
...在这里你指定了你想要匹配的元素。
Hello World
经典的。请参阅文件 HelloWord.xsl 获取完整示例。
<?xml version="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>
<xsl:output method="text"/>
表示输出是纯文本,没有任何格式,就是文本。
<xsl:template match="/">
匹配任何文档的根元素。
<xsl:text>
元素包围了你想要输出的任何文本。
Hello ToDoList
请参阅文件 TDLSS 1.xsl 获取完整示例。
<?xml version="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>
<xsl:output method="html" ...
表示输出是一个 html 页面,具有所有常规格式,但会稍微复杂一些。<xsl:template match="/TODOLIST">
将只匹配一个名为 TODOLIST
的根元素,这是任务列表的根元素名称。
读取属性值
请参阅文件 TDLSS 2.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:value-of select="@PROJECTNAME" />
<xsl:element name="br" />
<xsl:value-of select="@FILENAME" />
<xsl:element name="br" />
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:value-of select="@PROJECTNAME" />
输出 PROJECTNAME
属性的值。
遍历树
请参阅文件 TDLSS 3.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: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>
<xsl:apply-templates select="TASK" />
查找 TODOLIST
元素下的所有名为 TASK
的子元素,并将模板 <xsl:template match="TASK">
应用于每个匹配项。
递归遍历树
请参阅文件 TDLSS 4.xsl 获取完整示例。
<xsl:template match="TASK">
<xsl:value-of select="@TITLE" />
<xsl:element name="br" />
<xsl:apply-templates select="TASK" />
</xsl:template>
<xsl:apply-templates select="TASK" />
使树遍历递归。
读取文本元素值
请参阅文件 TDLSS 5.xsl 获取完整示例。
<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>
<xsl:value-of select="COMMENTS" />
输出 COMMENTS
文本元素的值。
单条件:if
请参阅文件 TDLSS 6.xsl 获取完整示例。
<xsl:if test="COMMENTS">
<xsl:text>COMMENTS: </xsl:text>
<xsl:value-of select="COMMENTS" />
<xsl:element name="br" />
</xsl:if>
xsl:if
是当 test 为 true 时执行某些操作,当 test 为 false 时则什么也不做。
多条件:choose
请参阅文件 TDLSS 7.xsl 获取完整示例。
<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>
xsl:choose
是一个多条件结构。第一个 when
结构,其 test
为 true,将被执行;如果没有 test 为 true,则执行 otherwise 结构。
调用模板
请参阅文件 TDLSS 8.xsl 获取完整示例。
<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>
命名模板 <xsl:template name="fix-breaks">
相当于其他语言中的子程序。它们通过 <xsl:call-template name="fix-breaks">
调用。
修复换行
请参阅文件 TDLSS 8.xsl 获取完整示例。
<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>
多行文本需要为 html 输出进行修复,因为 CRLF
不被理解,必须用 <br/>
替换。
从树视图获取任务路径
请参阅文件 TDLSS 9.xsl 获取完整示例。
<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>
此模板仅在任务嵌套(树视图)时有效。它从根开始列出父级的 TITLE
。
从列表视图获取任务路径
请参阅文件 TDLSS13.xsl 获取完整示例。
<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>
此模板仅在任务不嵌套(列表视图)时有效。它从根开始列出父级的 TITLE
。检查父级是否包含在列表中。
显示任务嵌套
请参阅文件 TDLSS10.xsl 获取完整示例。
在 html 中,列表是显示缩进的自然方式,只需嵌套列表即可。
<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>
只需在遍历树的代码的正确位置添加 <ul>
和 <li>
。
使用 XML 文档匹配模板
请参阅文件 TDLSS11.xsl 和 TDLSS12.xsl 获取完整示例。
通常,我们从根匹配模板开始,如示例文件 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>
但有时需要从文档匹配模板开始,如示例文件 TDLSS12.xsl,它的功能基本相同。
<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>
你可以看到,HTML 文档部分始终在第一个调用的模板中,而文档匹配模板将根作为子元素调用。
在样式表中包含 HTML 标签
当你想要格式化输出时,最好使用 html 标签。
HTML 根
一个 html 页面的根结构大致是这样的
<html>
<head>
. . .
</head>
<body>
. . .
</body>
</html>
由于它在输出中只出现一次,所以必须放在匹配根的模板中。
<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>
请参阅文件 TDLSS 3.xsl 获取完整示例。
HTML 中的 CRLF
CRLF 只在 html 源代码中使用,在页面渲染中不起作用,必须使用 <br />
标签代替。
<xsl:element name="br" />
结果是
<br />
列表
HTML 列表是通过缩进任务在输出中显示任务嵌套的简单方法。这很容易,因为任务的嵌套与列表的嵌套相匹配。
一个 HTML 列表包含 2 部分
- 一个
List
分隔符<ul>
- 一个
ListLine
分隔符<li>
List
分隔符包围一个列表,每个列表出现一次。它的自然位置是在处理父任务的模板中。
<xsl:element name="ul">
<xsl:apply-templates select="TASK" />
</xsl:element>
ListLine
分隔符每个列表项出现一次。它的自然位置是在处理子任务的模板中。
<xsl:element name="li">
<xsl:text>Title: </xsl:text>
<xsl:value-of select="@TITLE" />
<xsl:element name="br" />
</xsl:element>
下载
zip 文件包含示例文件
- 文件bpsToDoListStyler_Rev018.xsl是文章 XSL Transform for ToDoList 中的示例文件。
- 文件bpsToDoListStyler_Rev019.xsl是根据以下内容修正后的文件。
- 文件HelloWord.xsl,示例文件,参见片段
- 文件HelloTDL99.xsl,示例文件,参见片段
链接
- ToDoList-An-effective-and-flexible-way-to-keep-on:关于 ToDoList 软件的文章
- XSL Transform for ToDoList:我找到 ToDoList 示例样式表的文章
- AbstractSpoon Software ToDoList Wiki:TDL 作者制作的 TDL Wiki 页面
- w3schools.com:教授 XML、HTML、XSLT、XPath 等的网站
历史
- 2015年5月1日:初稿
- 2015年5月19日:添加了一些内容,更新了下载和修正
- 2015年6月1日:排版错误
- 2015年6月15日:添加了更多内容,更新了下载
- 2015年11月27日:添加了更多内容,更新了下载