在處理 XML 文件時,您可能會遇到這種情況:文件中包含敏感數據,而您喜歡的 XML 處理工具又出現了問題,比方說一個 bug。您需要向供應商提供一個引起 bug 的示例文件。當然不能隨便發送一個 XML 文件,因為可能是示例文件中的特殊標記造成了問題。您需要有一種方法清除文件中的敏感數據,同時保持文件的特殊結構特征,以便仍然能夠說明問題。如本文所述,只要一點 XSLT 技巧就能解決。
消除內容
清單 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>
從第一個模板可以看出,與很多有用的腳本一樣,這裡也使用了恆等變換。第二個模板將所有屬性復制到輸出中,但省略了屬性值。第三個模板刪除了所有文本節點。所有其他節點類型,包括元素,都由第一個模板處理,該模板將節點的基本結構復制到輸出中。清單 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 擴展(請參閱 參考資料),還可以進一步控制字符數據。