XHTML 是编写为结构良好的 XML 的 HTML,这通常意味着 HTML 必须坚持遵循 XML 规则。这些规则比用于 HTML 的那些更严格 — 例如:
- 标签名称是区分大小写的,特别是小写 — 例如,
<p>而非<P>。 - 引用属性值 — 例如,
<input type="checkbox">而非<input type=checkbox>。 - 如果应用了一个属性,就要提供一个值。对于没有明确值的 HTML 属性,将属性名称作为其值 — 例如,
<input selected="selected">not<input selected>。无用的属性包括:checkeddisabledselectednowrap
- 合理嵌套标签 — 例如,
<b><i>…</i></b>而非<b><i>…</b></i>。 - 不要忽略可选结束标记 — 例如,
<p>…</p><p>…</p>而非<p>…<p>…。
然而在某一方面,XML 没有 HTML 严格 — 即关闭标签的方式。对于 XML,您可以使用具有单独结束标记的短形式(自闭合)或长形式选择空元素(即没有文本或其它标签在其中的任何元素):
- 正斜杠前带有或没有空格的短形式(
/):<tag/>或<tag /> - 长形式:
<tag></tag>
但是对于 HTML,有些标签需要结束标记,而其他的不需要。
需要 结束标记的标签包括 <a>、<abbr>、<acronym>、<address>、<b>、<big>, <blockquote>、<button>、<code>、<dir>、<div>、<em>、<font>、<form>、<h1>、<i>、<label>、<li>、<map>、
<ol>、<pre>、<script>、<span>、<strong>、<style>、<sub>、<table>、<tt>、<ul> 和 <xml>。
禁止使用 结束标记的标签包括 <area>、<base>、<br>, <col>、<frame>、<img>、<isindex>、<link>、<meta> 和 <param>。
此外,W3C 推荐将空格放在自闭合标签的结尾,以改进与浏览器的兼容性:
- 推荐:
<input type="checkbox" /> - 不推荐:
<input type="checkbox"/>
参见 参考资料 获取 HTML Compatibility Guidelines 的链接。
由于 XHTML 是 XML,XSLT 可以转换 XHTML。XSLT 的原始意图是充当将 XML 数据转换为 HTML 的灵活而强大的方式。对 XML 技术 — 特别是 XHTML — 的广泛接受,增加了 XSLT 解决的应用程序的数量。XHTML 可以是转换的一个输入,或转换所生成的,或者两者兼而有之。使用 XSLT 来生成 XHTML 涉及到如何以符合 HTML 的方式关闭空标签的问题。
如何合理关闭了空标签,会发生什么?
- 如果以短形式关闭,用于下载 JavaScript 文件的脚本标签就无法获得文件。
失败:
<script type="text/javascript" href="myfile.js" />成功:
<script type="text/javascript" href="myfiles.js"></script> - 一个自闭合的空标签
<div>被视为一个开始标签。自闭合的<div>元素捕获以下元素并将其作为自己的文本内容,直至下一个开始标签<div>出现为止。例如:
<div id="mydiv1" /> <p>This paragraph will be contained within mydiv1</p> <div id="mydiv2"></div> <p>This paragraph will NOT be contained in either 'div'</p>
浏览器按如下方式解译标记,添加了
<div>隐式结束标记并将其标为注释:
<div id="mydiv1"> <p>This paragraph is contained within mydiv1</p> </div> <!-- implied closing tag --> <div id="mydiv2"></div> <p>This paragraph is NOT contained in either 'div'</p>
- 以长格式
<br></br>表示的<br>元素被解译为两个元素:<br><br>,因此重复了换行符的数量。
有三种解决方案可合理关闭 XHTML 标签,具体取决于开发环境。序列化涉及到编写代码(例如,C#
或 Java™ 代码),以将一个 XML 文档对象转化为一个字符串。序列化是最复杂的解决方案,但是也是最灵活的。另两个解决方案取决于 XSLT 的版本(XSLT 2.0 是最简单的解决方案)。
序列化 是将内存中的二进制对象转化为适合于在文件系统或通过网络传输的字符串。不管是您将一个对象模型的序列化编码为 XHTML 还是 XSLT 转换的结果已经是一个字符串,都要通过控制序列化解决合理关闭空 HTML 标签的问题。
如果转换的结果是一个对象,以简短、自闭合的形式序列化禁止使用结束标记的标签:
"<" tag-name [ attributes ] " />" |
使用一个单独的结束标记关闭所有其他空标签:
"<" tag-name [ attributes ] "></" tag-name ">" |
这里有 C# 中的两个例子:一个用于
XmlTextWriter 且另一个用于
a StringWriter。在 清单 1 中,XhtmlTextWriter 衍生自 XmlTextWriter 并重写 WriteEndElement 方法,以便以短形式或长形式关闭元素。
清单 1. XhtmlTextWriter
public class XhtmlTextWriter : System.Xml.XmlTextWriter
{
private string tagName = string.Empty;
private string elementNamespace = string.Empty;
public XhtmlTextWriter(System.IO.TextWriter w)
: base(w)
{
}
public override void WriteEndElement()
{
bool isShortNotation = true;
// Check if XHTML Namespace
if (string.IsNullOrEmpty(this.elementNamespace) ||
(this.elementNamespace.Contains("www.w3.org") &&
this.elementNamespace.Contains("xhtml")))
{
switch (this.tagName)
{
case "area":
isShortNotation = true;
break;
case "base":
isShortNotation = true;
break;
case "basefont":
isShortNotation = true;
break;
case "bgsound":
isShortNotation = true;
break;
case "br":
isShortNotation = true;
break;
case "col":
isShortNotation = true;
break;
case "frame":
isShortNotation = true;
break;
case "hr":
isShortNotation = true;
break;
case "img":
isShortNotation = true;
break;
case "input":
isShortNotation = true;
break;
case "isindex":
isShortNotation = true;
break;
case "keygen":
isShortNotation = true;
break;
case "link":
isShortNotation = true;
break;
case "meta":
isShortNotation = true;
break;
case "param":
isShortNotation = true;
break;
default:
isShortNotation = false;
break;
}
}
if (isShortNotation)
{
base.WriteEndElement();
}
else
{
base.WriteFullEndElement();
}
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
this.tagName = localName.ToLower();
this.elementNamespace = ns;
base.WriteStartElement(prefix, localName, ns);
}
public override void WriteStartDocument()
{
// Don't emit XML declaration
}
public override void WriteStartDocument(bool standalone)
{
// Don't emit XML declaration
}
}
|
清单 2 显示 XhtmlStringWriter 类,该类衍生自 StringWriter 且重写 Write 方法,以便为需要的那些标签将短形式转化为长形式。您可以用其他编程语言(比如 Java 语言)编写类似的方法。
清单 2. XhtmlStringWriter
public class XhtmlStringWriter : System.IO.StringWriter
{
public override void Write(string value)
{
bool isShortNotation = false;
switch (value)
{
case "></area>":
isShortNotation = true;
break;
case "></base>":
isShortNotation = true;
break;
case "></basefont>":
isShortNotation = true;
break;
case "></bgsound>":
isShortNotation = true;
break;
case "></br>":
isShortNotation = true;
break;
case "></col>":
isShortNotation = true;
break;
case "></frame>":
isShortNotation = true;
break;
case "></hr>":
isShortNotation = true;
break;
case "></img>":
isShortNotation = true;
break;
case "></input>":
isShortNotation = true;
break;
case "></isindex>":
isShortNotation = true;
break;
case "></keygen>":
isShortNotation = true;
break;
case "></link>":
isShortNotation = true;
break;
case "></meta>":
isShortNotation = true;
break;
case "></param>":
isShortNotation = true;
break;
}
if (isShortNotation)
{
base.Write(" />");
}
else
{
base.Write(value);
}
}
}
|
首先,确保 XSLT 输出方法为 xml,而非 html。html 方法不是 XHTML;HTML 不是 XML。XSLT 处理器或 XML 解析器都不能处理 HTML。
如果转换的结果是一个字符串或文件,编码 XSLT 模板以强行合理关闭空标签,从而间接控制序列化。关闭空标签的方式取决于 XSLT 处理器的实现。
如果输入 也是 XHTML,使用身份模板来将未改变的标签复制到输出。身份模板处理输入元素和属性,并将它们复制到输出。没有身份模板,只有标签之间的文本可以复制到输出。
清单 3 中的 XSLT 缺乏身份模板,仅输出纯文本。
清单 3. 结果只能是纯文本
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
|
清单 4 中的 XSLT 有身份模板,可以复制不由其他模板处理的元素。一个身份模板匹配一个节点,并复制该节点。有两种方式可复制元素:本例使用 xsl:copy。另一种方式就是使用 xsl:element,这在稍后将加以讨论。
清单 4. 结果包括标签但可能没有得到合理关闭
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<!-- put your templates here -->
<!-- identity templates -->
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
|
要合理关闭标签,身份模板必须选择需要短形式的标签。在一个模板的匹配表达式中选择标签需要知道标签的名称空间,或知道没有使用名称空间。控制如何将空标签呈现为短形式或长形式的窍门不是处理子节点(短形式)或处理子节点(长形式),即使没有子节点可处理。在这点上,XSLT 处理器起重要作用。Microsoft 处理器 — Microsoft® .NET 和
MSXML — 使用一种无需处理子节点即可以短形式输出标签的诀窍。其他处理器,比如 Saxon,总是为空标签使用短形式,因此对于需要结束标记的 HTML 元素,必须插入一些文本。对于大部分元素,一个空格很合适。对于 <script> 标签,一个 JavaScript 注释标记(即 //)将开始和结束标记分离开来。所幸,该方法也适用于 Microsoft 处理器。
如果输入文档没有名称空间,如 清单 5 所示,那么 XSLT 也不需要一个名称空间。
清单 5. 没有名称空间的 XHTML 输入文档
<html> ... </html> |
清单 6 展示匹配自闭合标签的 XSLT。自闭合标签经过处理,以便 Microsoft XSLT 处理器使用短形式。因为没有名称空间,标签名称没有一个名称空间前缀。
清单 6. 具有名称空间的 XSLT
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<!-- identity templates -->
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="area[not(node())]|base[not(node())]|
basefont[not(node())]|bgsound[not(node())]|br[not(node())]|
col[not(node())]|frame[not(node())]|hr[not(node())]|
img[not(node())]|input[not(node())]|isindex[not(node())]|
keygen[not(node())]|link[not(node())]|meta[not(node())]|
param[not(node())]">
<!-- identity without closing tags -->
<xsl:copy>
<xsl:apply-templates select="@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
|
如果输入文档有一个名称空间,如 清单 7 所示,XSLT 需要一个名称空间,且标签名需要一个前缀。
清单 7. 具有名称空间的 XHTML 输入文档
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"> ... </html> |
清单 8 展示匹配自闭合标签的 XSLT。因为有一个名称空间,所以标签名需要一个名称空间前缀。没有前缀,标签就不会匹配。注意,XHTML 名称空间声明以 xmlns:htm 开头。前缀 htm 是任意的。
清单 8. 具有名称空间的 XSLT
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:htm="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<!-- identity templates -->
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="htm:area|htm:base|htm:basefont|
htm:bgsound|htm:br|htm:col|htm:frame|htm:hr|htm:img|
htm:input|htm:isindex|htm:keygen|htm:link|htm:meta|
htm:param">
<!-- identity without closing tags -->
<xsl:copy>
<xsl:apply-templates select="@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
|
如果输入文档没有名称空间,如 清单 9 所示,那么 XSLT 也不需要名称空间。
清单 9. 没有名称空间的 XHTML 输入参数
<html> ... </html> |
清单 10 展示匹配自闭合标签的 XSLT。需要单独结束标记且不为空的标签在输出中带有一个空格,以防止使用短形式序列化它们。一个例外是空元素 script,对其提供了 JavaScript 注释符号(//)。因为没有名称空间,标签名没有一个名称空间前缀。
清单 10. 具有匹配的自闭合标签的 XSLT
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<!-- identity templates -->
<xsl:template match="*[not(node())]">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:text> </xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="script[not(node())]">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:text>//</xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="area[not(node())]|base[not(node())]|
basefont[not(node())]|bgsound[not(node())]|br[not(node())]|
col[not(node())]|frame[not(node())]|hr[not(node())]|
img[not(node())]|input[not(node())]|isindex[not(node())]|
keygen[not(node())]|link[not(node())]|meta[not(node())]|
param[not(node())]">
<!-- identity without closing tags -->
<xsl:copy>
<xsl:apply-templates select="@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
|
如果输入文档有一个名称空间,如 清单 11 中的 XHTML 文档所示,XSLT 需要一个名称空间,且标签名需要一个前缀。
清单 11. 具有名称空间的 XHTML 输入文档
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
...
</html>
|
清单 12 展示匹配自闭合标签的 XSLT。需要单独结束标记且不为空的标签在输出中带有一个空格,以防止使用短形式序列化它们。一个例外是空元素 script,对其提供了 JavaScript 注释符号(//)。因为有一个名称空间,所以标签名需要一个名称空间前缀。没有前缀,标签便不会匹配。注意,XHTML 名称空间声明以 xmlns:htm 开头。前缀 htm 是任意的。
没有优先级的模板支持将匹配表达式用于自闭合标签,以获得更高优先级。没有它,就会忽略自闭合标签的模板。
清单 12. 具有匹配的自闭合标签和名称空间的 XSLT
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:htm="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<!-- identity templates -->
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="htm:area|htm:base|htm:basefont|
htm:bgsound|htm:br|htm:col|htm:frame|htm:hr|htm:img|
htm:input|htm:isindex|htm:keygen|htm:link|htm:meta|
htm:param">
<!-- identity without closing tags -->
<xsl:copy>
<xsl:apply-templates select="@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(node())]" priority="-0.5">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:text> </xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="htm:script[not(node())]">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:text>//</xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
|
要从输出中排除 XHTML 名称空间,比如在转化为另一个 XML 格式时,使用 <xsl:element> 标签,而非 <xsl:copy>,如 清单 13 所示。
清单 13. 不包括输出名称空间的 XSLT 模板
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:htm="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- identity templates -->
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="htm:area|htm:base|htm:basefont|
htm:bgsound|htm:br|htm:col|htm:frame|htm:hr|
htm:img|htm:input|htm:isindex|htm:keygen|
htm:link|htm:meta|htm:param">
<!-- identity without closing tags -->
<xsl:element name="{name()}">
<xsl:apply-templates select="@*"/>
</xsl:element>
</xsl:template>
<xsl:template match="*[not(node())]" priority="-0.5">
<xsl:element name="{name()}">
<xsl:apply-templates select="@*"/>
<xsl:text> </xsl:text>
</xsl:element>
</xsl:template>
<xsl:template match="htm:script[not(node())]">
<xsl:element name="{name()}">
<xsl:apply-templates select="@*"/>
<xsl:text>//</xsl:text>
</xsl:element>
</xsl:template>
<xsl:template match="@*|text()">
<xsl:copy/>
</xsl:template>
<xsl:template match="comment()">
<xsl:comment xml:space="preserve">
<xsl:value-of select="."/>
</xsl:comment>
</xsl:template>
<xsl:template match="processing-instruction()">
<xsl:processing-instruction name="{name()}">
<xsl:value-of select="."/>
</xsl:processing-instruction>
</xsl:template>
</xsl:stylesheet>
|
使用 XSLT 2.0 可得到另一种方法 — xhtml — 顾名思义,它解决合理关闭空 XHTML 标签的问题。如果将名称空间应用于输入文档,必须在 xpath-default-namespace 属性中指定名称空间。清单 14 展示 xsl:output 标签上的 method 和 xpath-default-namespace 属性。
要使用 XSLT 2.0,使用一个支持它的 XSLT 处理器,比如 Saxon。此时,Microsoft 处理器不支持 XSLT 2.0。
清单 14. XSLT 2.0
<?xml version="1.0" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xhtml"
xpath-default-namespace="http://www.w3.org/1999/xhtml"/>
<!-- put your templates here -->
<!-- identity templates -->
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
|
要从输出中排除 XHTML 名称空间,比如在转化为另一种 XML 格式时,使用 <xsl:element> 标签,而非 <xsl:copy>,如 清单 15 所示。
清单 15. 不包括输出名称空间的 XSLT 2.0 模板
<?xml version="1.0" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xhtml"
xpath-default-namespace="http://www.w3.org/1999/xhtml"/>
<!-- put your templates here -->
<!-- identity templates -->
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*|text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
|
您必须合理关闭 XHTML 标签,要么使用一个单独的标签,要么使用自闭合标签,具体取决于标签名。当您通过 XSLT 转换生成 XHTML 时,用于控制如何关闭标签的方法取决于 XSLT 处理器。普遍而复杂的解决方案是编写一个序列化方法。XSLT 1.0 的其他解决方案涉及到以某种方式编码 XSL 模板。目前为止最简单的解决方案就是 XSLT 2.0,它具有对 XHTML 的本机支持。
学习
- XHTML:两种语言的力量(Sathyan Munirathinam,developerWorks,2002 年 7 月):进一步了解 XHTML。
- XHTML 1.0:标记新的开端(Molly Holzschlag,developerWorks,2005 年 2 月):阅读了解 XHTML 简介及其标准。
- XHTML 1.0 The Extensible HyperText Markup Language (Second Edition):阅读 W3C 的 XHTML 建议。
- 空元素 以及 元素最小化和空元素内容:在 W3C 的 HTML Compatibility Guidelines 中了解更多内容。
- developerWorks XML 专区:在 XML 专区获取提高您的专业技能所需的资源。
- My developerWorks 中文社区:个性化您的 developerWorks 体验。
- IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- XML 技术库:访问 developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。另外,阅读更多 XML 技巧。
- developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
- developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
- 观看 developerWorks 按需演示,包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
获得产品和技术
- IBM 产品评估版:下载或 在线试用 IBM SOA Sandbox,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
- The developerWorks 社区:查看开发人员推动的博客、论坛、组和 wikis,并与其他 developerWorks 用户交流。
Doug Domeny 使用 XSLT、W3C XML Schema、DHTML、JavaScript、jQuery、正则表达式和 CSS 编写开发了基于浏览器的、多语言的、对业务用户友好的 XML 编辑器。他持有韦纳姆戈登学院的计算机科学和数学学士学位,曾多年服务于 OASIS 技术委员会,比如 XML Localization Interchange File Format (XLIFF) 和 Open Architecture for XML Authoring and Localization (OAXAL)。在作为软件工程师期间,他发展了在软件工程设计和架构、UI 设计和技术写作方面的重要技能。