Uche Ogbuji, 首席顾问, Fourthought Inc.
2006 年 5 月 08 日
消除内容
清单 1 (kill-content.xslt) 中的 XSLT 脚本可以删除所有的文本节点和属性值,仅留下节点结构骨架。
清单 1 (kill-content.xslt). 清除字符数据的 XSLT 脚本
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute namespace="{namespace-uri()}" name="{name()}"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
|
<script type="text/javascript">
</script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
从第一个模板可以看出,与很多有用的脚本一样,这里也使用了恒等变换。第二个模板将所有属性复制到输出中,但省略了属性值。第三个模板删除了所有文本节点。所有其他节点类型,包括元素,都由第一个模板处理,该模板将节点的基本结构复制到输出中。清单 2 是用该脚本处理的一个示例文件。
清单 2 (patients.xml). 示例 XML 文件
<?xml version="1.0" encoding="iso-8859-1"?>
<patients>
<patient id='ep' admitted="2003-06-10">
<name>Ezra Pound</name>
<address>
<street>45 Usura Place</street>
<city>Hailey</city>
<province>ID</province>
</address>
<condition>ore infectus</condition>
</patient>
<patient id='tse' admitted="2003-06-20">
<name>Thomas Eliot</name>
<address>
<street>3 Prufrock Lane</street>
<city>Stamford</city>
<province>CT</province>
</address>
<condition>Sartor resartus</condition>
</patient>
<patient id="co" admitted="2004-11-15">
<name>Christopher Okigbo</name>
<address>
<street>7 Heaven's Gate</street>
<city>Idoto</city>
<province>Anambra</province>
</address>
<condition>caeli porta quaerit</condition>
</patient>
</patients>
|
如果将 清单 1 中的 XSLT 脚本应用到 清单 2 中的 XML 例子,就会得到下面的 XML(忽略了 XML 声明)。
<patients><patient id="" admitted=""><name/><address><street/><city/>... |
可以看到,结果中保留了基本的元素和属性结构,但是没有内容了。
增加空格
如果彻底删除字符数据过于极端,希望至少保留每个节点的长度,可以使用 清单 1 的一个变体。清单 3 中的 XSLT 脚本用长度相同但仅包含空格的节点代替所有字符数据。
清单 3 (blank-content.xslt). 将所有字符数据替换为空白内容的 XSLT 脚本
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://ns.ogbuji.net/articles"
>
<x:wsbuffer xml:space="preserve"> </x:wsbuffer>
<xsl:variable name="wsbuf" select="string(document('')/*/x:wsbuffer)"/>
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute namespace="{namespace-uri()}" name="{name()}">
<xsl:value-of select="translate(., ., $wsbuf)"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="translate(., ., $wsbuf)"/>
</xsl:template>
</xsl:transform>
|
关键是顶层的扩展元素 x:wsbuffer
,它提供了替换操作中所使用的空白字符内容。由于格式方面的原因,我减少了空格,但是该元素可以增加更多的空格,因为原始数据中的所有字符数据节点都用同样长度的空白替换,最大长度为 x:wsbuffer
中的字符数。xml:space
属性用于防止 XSLT 处理程序压缩 x:wsbuffer
中的空白。x:wsbuffer
实际上可使用任何内容,作为字符数据的常备替代文本。如果使用任何非空白字符,就不需要 xml:space
属性了。
通过自引用 XSLT 文档(使用 document('')
的特殊形式),变量 wsbuf
设置为替换文本。转换的其他部分与 清单 1 类似,只不过没有删除文本,而是用 wsbuf
中相同位置的字符替换每个字符。与 清单 1 一样,最终所有的输出内容都是空白。如果希望改变结果可以尝试修改 x:wsbuffer
的内容。
如果将 清单 3 中的 XSLT 脚本应用于 清单 2 中的 XML,就会得到下面的 XML。
<patients> <patient id=" " admitted=" "> <name> </name>... |
结束语
本文介绍了如何修改 XML 文档,在保留基本结构不变的同时删除或者改变内容以免泄漏敏感信息。这种技术有一定的局限性,主要是由于 XSLT 本身的局限性造成的。特别需要指出的是,如果请求解决的问题涉及到字符数据本身的特点(比方说,处理字符实体的 bug),如此处理的示例文件就丢失了关键信息。记住,XSLT 还丢失了 XML 文件中的其他信息,如顶层声明、实体引用和 CDATA 节。如果这些内容对解决问题很重要,就不能使用 XSLT 清理文件。我遇到很多情形使用这些脚本就足够了,因此这些技术仍然值得学习。如果愿意使用 EXSLT 扩展,还可以进一步控制字符数据。