2013-10-04 77 views
2

給定一個xpath語句列表,我想編寫一個樣式表,該樣式表將運行一個xml文檔並輸出相同的文檔,但在標識爲每個xpath語句。我們來舉個例子。與XML實例開始舉行的XPath語句:使用xslt從xpath語句列表中註釋xml實例

<paths> 
    <xpath location="/root/a" annotate="1"/> 
    <xpath location="/root/a/b" annotate="2"/> 
</paths> 

給定輸入:

<root> 
    <a> 
    <b>B</b> 
    </a> 
    <c>C</c> 
</root> 

應該產生:

<root> 
    <!-- 1 --> 
    <a> 
    <!-- 2 --> 
    <b>B</b> 
    </a> 
    <c>C</c> 
</root> 

我最初的想法是有身份的樣式表這需要一個file-list param,調用它的document函數來獲取xpath節點的列表。然後,它會根據該列表檢查輸入的每個節點,然後在發現它時插入註釋節點,但是我認爲這可能是非常低效的,因爲xpath的列表變大(或者可能不是,告訴我,我正在使用撒克遜9)。

所以我的問題:有沒有一種有效的方式來做這樣的事情?

回答

2

概述:

元XSLT轉換,是以paths文件作爲輸入,併產生一個新的XSLT轉換爲輸出。這個新的XSLT將從您的root輸入XML轉換爲帶註釋的複製輸出XML。

注:

  1. 工程用XSLT 1.0,2.0,或3.0。
  2. 應該是非常有效的,特別是如果產生 轉型有過大的輸入要運行或已被反覆運行 ,因爲它有效地編譯爲本地XSLT而不是 重新實現與基於XSLT解釋器匹配。
  3. 比必須在代碼中手動重建 元素血統的方法更加健壯。由於它將路徑映射到 template/@match屬性,因此可以高效地使用完整的 複雜性。我已經包含了一個屬性值測試 的一個例子。
  4. 請務必考慮@DanielHaley 和@MartinHonnen的優雅XSLT 2.0和3.0解決方案,特別是如果中間元XSLT文件 不適合您。通過利用XSLT 3.0的XPath評估 工具,@ MartinHonnen的答案似乎能夠提供 甚至比template/@match更強大的匹配。

此XML輸入指定的XPath和註解:

<paths> 
    <xpath location="/root/a" annotate="1"/> 
    <xpath location="/root/a/b" annotate="2"/> 
    <xpath location="/root/c[@x='123']" annotate="3"/> 
</paths> 

當輸入到該元XSLT轉換:

<?xml version="1.0" encoding="ISO-8859-1"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="xml" indent="yes"/> 

    <xsl:template match="/paths"> 
    <xsl:element name="xsl:stylesheet"> 
     <xsl:attribute name="version">1.0</xsl:attribute> 
     <xsl:element name="xsl:output"> 
     <xsl:attribute name="method">xml</xsl:attribute> 
     <xsl:attribute name="indent">yes</xsl:attribute> 
     </xsl:element> 
     <xsl:call-template name="gen_identity_template"/> 
     <xsl:apply-templates select="xpath"/> 
    </xsl:element> 
    </xsl:template> 

    <xsl:template name="gen_identity_template"> 
    <xsl:element name="xsl:template"> 
     <xsl:attribute name="match">node()|@*</xsl:attribute> 
     <xsl:element name="xsl:copy"> 
     <xsl:element name="xsl:apply-templates"> 
      <xsl:attribute name="select">node()|@*</xsl:attribute> 
     </xsl:element> 
     </xsl:element> 
    </xsl:element> 
    </xsl:template> 

    <xsl:template match="xpath"> 
    <xsl:element name="xsl:template"> 
     <xsl:attribute name="match"> 
     <xsl:value-of select="@location"/> 
     </xsl:attribute> 
     <xsl:element name="xsl:comment"> 
     <xsl:value-of select="@annotate"/> 
     </xsl:element> 
     <xsl:element name="xsl:text"> 
     <xsl:text disable-output-escaping="yes">&amp;#xa;</xsl:text> 
     </xsl:element> 
     <xsl:element name="xsl:copy"> 
     <xsl:element name="xsl:apply-templates"> 
      <xsl:attribute name="select">node()|@*</xsl:attribute> 
     </xsl:element> 
     </xsl:element> 
    </xsl:element> 
    </xsl:template> 
</xsl:stylesheet> 

會產生這種XSLT轉換:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> 
    <xsl:output method="xml" indent="yes"/> 
    <xsl:template match="node()|@*"> 
     <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="/root/a"> 
     <xsl:comment>1</xsl:comment> 
     <xsl:text>&#xa;</xsl:text> 
     <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="/root/a/b"> 
     <xsl:comment>2</xsl:comment> 
     <xsl:text>&#xa;</xsl:text> 
     <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="/root/c[@x='123']"> 
     <xsl:comment>3</xsl:comment> 
     <xsl:text>&#xa;</xsl:text> 
     <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
     </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 

,當提供這種輸入XML文件:

<root> 
    <a> 
    <b>B</b> 
    </a> 
    <c x="123">C</c> 
</root> 

將產生所需的輸出XML文件:

<?xml version="1.0" encoding="UTF-8"?> 
<root> 
    <!--1--> 
    <a> 
    <!--2--> 
     <b>B</b> 
    </a> 
    <!--3--> 
    <c x="123">C</c> 
</root> 
+0

您的示例在「povided this input XML file」標題下提供,沒有帶'x'屬性的'c'元素,爲什麼輸出突然有一個?我想你發佈了錯誤的輸入樣本。 –

+0

很好,@MartinHonnen。當我編輯我的答案以顯示更強大的匹配示例時,我忽略了更新該輸入XML。現在修復。謝謝。 – kjhughes

+0

我認爲這可能是我會採取的方法,因爲在這一點上我完全不熟悉3.0。我已經嘗試過使用'saxon:evaluate'函數,我懷疑它與Martin的解決方案大致相同。關於這個解決方案,我還沒有找到一種方法來註釋屬性節點的xpath。例如' 「/根/ C/@ X」'。 – stand

2

我不知道如果kjhughes'的建議創建第二個轉換將比您的原創想法更有效率。如果您的XML變大,我確實發現第二次轉換的可能性變得很大。

以下是我會做...

XML輸入

<root> 
    <a> 
     <b>B</b> 
    </a> 
    <c>C</c> 
</root> 

「路徑」 XML(paths.xml)

<paths> 
    <xpath location="/root/a" annotate="1"/> 
    <xpath location="/root/a/b" annotate="2"/> 
</paths> 

XSLT 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output indent="yes"/> 
    <xsl:strip-space elements="*"/> 

    <xsl:param name="paths" select="document('paths.xml')"/> 

    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="*" priority="1"> 
     <xsl:variable name="path"> 
      <xsl:for-each select="ancestor-or-self::*"> 
       <xsl:value-of select="concat('/',local-name())"/> 
      </xsl:for-each> 
     </xsl:variable> 
     <xsl:if test="$paths/*/xpath[@location=$path]"> 
      <xsl:comment select="$paths/*/xpath[@location=$path]/@annotate"/> 
     </xsl:if> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 

</xsl:stylesheet> 

XML輸出

<root> 
    <!--1--> 
    <a> 
     <!--2--> 
     <b>B</b> 
    </a> 
    <c>C</c> 
</root> 
+0

糟糕。在那裏留下一些調試代碼。它現在被刪除。 –

+0

這是一個非常不同但很好的解決方案。 +1 – kjhughes

+0

謝謝@Daniel,這或多或少是我想出來的。我猜我跟隨的問題涉及'xsl:if'元素中'test'謂詞的性能。它有可能是除了'O(n)'之外的任何東西,其中'n'是路徑的數量?我沒有很好的處理。 – stand

3

假設撒克遜9 PE或EE,它也應該可以使用XSLT 3.0和xsl:evaluate如下:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:math="http://www.w3.org/2005/xpath-functions/math" 
    xmlns:map="http://www.w3.org/2005/xpath-functions/map" 
    xmlns:mf="http://example.com/mf" 
    exclude-result-prefixes="xs math map mf" 
    version="3.0"> 

    <xsl:output indent="yes"/> 

    <xsl:param name="paths-url" as="xs:string" select="'paths1.xml'"/> 
    <xsl:param name="paths-doc" as="document-node()" select="doc($paths-url)"/> 

    <xsl:variable name="main-root" select="/"/> 

    <xsl:variable 
     name="mapped-nodes"> 
     <map> 
      <xsl:for-each select="$paths-doc/paths/xpath"> 
       <xsl:variable name="node" as="node()?" select="mf:evaluate(@location, $main-root)"/> 
       <xsl:if test="$node"> 
        <entry key="{generate-id($node)}"> 
         <xsl:value-of select="@annotate"/> 
        </entry> 
       </xsl:if> 
      </xsl:for-each> 
     </map> 
    </xsl:variable> 

    <xsl:key name="node-by-id" match="map/entry" use="@key"/> 

    <xsl:function name="mf:evaluate" as="node()?"> 
     <xsl:param name="path" as="xs:string"/> 
     <xsl:param name="context" as="node()"/> 
     <xsl:evaluate xpath="$path" context-item="$context"></xsl:evaluate> 
    </xsl:function> 

    <xsl:template match="@* | node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@* , node()"/> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="node()[key('node-by-id', generate-id(), $mapped-nodes)]"> 
     <xsl:comment select="key('node-by-id', generate-id(), $mapped-nodes)"/> 
     <xsl:text>&#10;</xsl:text> 
     <xsl:copy> 
      <xsl:apply-templates select="@* , node()"/> 
     </xsl:copy> 
    </xsl:template> 


</xsl:stylesheet> 

這裏是一個已編輯原始發佈代碼的版本,它使用XSLT 3.0地圖特性而不是臨時文檔來存儲動態XPath評估發現的節點的生成ID與註釋之間的關聯:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:math="http://www.w3.org/2005/xpath-functions/math" 
    xmlns:map="http://www.w3.org/2005/xpath-functions/map" 
    xmlns:mf="http://example.com/mf" 
    exclude-result-prefixes="xs math map mf" 
    version="3.0"> 

    <xsl:param name="paths-url" as="xs:string" select="'paths1.xml'"/> 
    <xsl:param name="paths-doc" as="document-node()" select="doc($paths-url)"/> 

    <xsl:output indent="yes"/> 

    <xsl:variable 
     name="mapped-nodes" 
     as="map(xs:string, xs:string)" 
     select="map:new(for $path in $paths-doc/paths/xpath, $node in mf:evaluate($path/@location, /) return map:entry(generate-id($node), string($path/@annotate)))"/> 

    <xsl:function name="mf:evaluate" as="node()?"> 
     <xsl:param name="path" as="xs:string"/> 
     <xsl:param name="context" as="node()"/> 
     <xsl:evaluate xpath="$path" context-item="$context"></xsl:evaluate> 
    </xsl:function> 

    <xsl:template match="@* | node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@* , node()"/> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="node()[map:contains($mapped-nodes, generate-id())]"> 
     <xsl:comment select="$mapped-nodes(generate-id())"/> 
     <xsl:text>&#10;</xsl:text> 
     <xsl:copy> 
      <xsl:apply-templates select="@* , node()"/> 
     </xsl:copy> 
    </xsl:template> 


</xsl:stylesheet> 

作爲第一個樣式表,它需要運行Saxon 9.5 PE或EE。

+0

我喜歡通過利用XSLT 3.0的XPath評估工具,您的答案似乎能夠提供比template/@ match更強大的匹配。 +1 – kjhughes