65.9K
CodeProject 正在变化。 阅读更多。
Home

ToDoList 的样式表:教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (21投票s)

2015 年 5 月 11 日

CPOL

12分钟阅读

viewsIcon

89660

downloadIcon

1307

我在为 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 是一个**元素**。它包含文本
  • PROJECTNAMETODOLIST 的一个**属性**,其**值**为
  • FILEFORMATTODOLIST 的一个**属性**,其**值**为
  • TITLETASK 的一个**属性**,其**值**为

<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)时出现未格式化文本的原因。

答案

仅对嵌套的 TASKs 使用 apply-templates

<xsl:apply-templates select="TASK" />

注意:同样的问题也适用于 TDL 6.9.6 及更早版本的样式表Project-Overview-HTML.xslSimpStyler0.2.xslTodoListStyler_Firefox.xslTodoListStyler_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 是当 testtrue 时执行某些操作,当 testfalse 时则什么也不做。

多条件: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 结构,其 testtrue,将被执行;如果没有 testtrue,则执行 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,'&#13;&#10;')">
                <xsl:value-of select="substring-before($text,'&#13;&#10;')" />
                <xsl:element name="br"/>
                <xsl:call-template name="fix-breaks">
                    <xsl:with-param name="text">
                        <xsl:value-of select="substring-after($text,'&#13;&#10;')" />
                    </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.xslTDLSS12.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,示例文件,参见片段

链接

历史

  • 2015年5月1日:初稿
  • 2015年5月19日:添加了一些内容,更新了下载和修正
  • 2015年6月1日:排版错误
  • 2015年6月15日:添加了更多内容,更新了下载
  • 2015年11月27日:添加了更多内容,更新了下载
© . All rights reserved.