2011-10-30 45 views
3

從另一個XML結構中獲取XML信息我使用XSLT 1.0和我特林讓以下內容:通過XSLT

我已經得到了1.XML文件:

<root> 
    <elem1>value1</elem1> 
    <elem2> 
     <elem3> 
      <param1>value2</param1> 
      <param2>value3</param2> 
     </elem3> 
    </elem2> 
    <elem4> 
     <param3>value4</param3> 
    </elem4> 
</root> 

現在客戶通過我一個XML文件,它告訴我,他希望我什麼元素讓他回來(客戶端之間會發生變化),即:

<root> 
    <RequiredElements> 
    <elementName>elem1</elementName> 
    <elementName>elem2/elem3/param1</elementName> 
    </RequiredElements> 
</root> 

這意味着,在這種情況下,我應該讓另一個XML文件,與這個結構:

<root> 
    <elem1>value1</elem1> 
    <elem2> 
    <elem3> 
     <param1>value2</param1> 
    </elem3> 
    </elem2> 
</root> 

我試着拿出在XSLT東西拿到我需要的結構(比其他任何編程語言),但無法做到這一點。

任何想法或指示我該怎麼做?

感謝您的幫助。

回答

1

這是我已經能夠拿出這麼迄今爲止最好的:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

    <xsl:output method="xml" encoding="UTF-8" indent="yes" version="1.0"/> 

    <xsl:template match="/root"> 
     <xsl:copy> 
      <xsl:for-each select="*"> 
       <xsl:call-template name="check-if-allowed"> 
        <xsl:with-param name="path" select="local-name(.)"/> 
       </xsl:call-template> 
      </xsl:for-each> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template name="check-if-allowed"> 

     <xsl:param name="path"/> 

     <xsl:copy> 

      <xsl:if test="$path = document('filter.xml')//RequiredElements/elementName/text()"> 
       <xsl:attribute name="flagged-by-filter">true</xsl:attribute> 
      </xsl:if> 

      <xsl:choose> 
       <xsl:when test="*"> 
        <xsl:for-each select="*"> 
         <xsl:call-template name="check-if-allowed"> 
          <xsl:with-param name="path" select="concat($path, '/', local-name(.))"/> 
         </xsl:call-template> 
        </xsl:for-each> 
       </xsl:when> 
       <xsl:otherwise> 
        <xsl:copy-of select="text()"/> 
       </xsl:otherwise> 
      </xsl:choose> 

     </xsl:copy> 

    </xsl:template> 

</xsl:stylesheet> 

讓我們過的是:第一個模板您/root元素相匹配。它將製作一個淺度副本,然後爲每個子元素調用模板check-if-allowed,並將該子元素的本地名稱作爲參數path傳入。

check-if-allowed模板接受名稱爲path的參數。它會對其節點進行淺度複製,然後測試參數是否包含在從文檔​​中進行的選擇中。這必須是第二個文檔的路徑,其中包含允許的路徑列表。如果測試成功(即如果path參數在​​中顯示爲elementName的文本內容),那麼它也將添加名稱爲flagged-by-filter且值爲true的屬性。

之後,xsl:choose是要做的兩件事之一。如果當前的元素有子元素,它將調用相同的check-if-allowed模板,但每次使用path參數時,將添加該元素的本地名稱。如果沒有子元素,只需要複製當前元素中可能存在的任何文本。

請注意,這是一個非常不完整的解決方案。它忽略了屬性,並且不適用於混合內容(即與元素混合的文本)。這裏

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> 

    <xsl:template match="/"> 
     <xsl:copy><xsl:apply-templates select="*"/></xsl:copy> 
    </xsl:template> 

    <xsl:template match="*[//*[@flagged-by-filter='true']]"> 
     <xsl:copy><xsl:apply-templates select="*"/></xsl:copy> 
    </xsl:template> 

    <xsl:template match="*[* and not(//*[@flagged-by-filter='true']) and @flagged-by-filter='true']"> 
     <xsl:copy></xsl:copy> 
    </xsl:template> 

    <xsl:template match="*[not(*) and @flagged-by-filter='true']"> 
     <xsl:copy-of select="."/> 
    </xsl:template> 

    <xsl:template match="*[not(*) and not(@flagged-by-filter='true')]"/> 

</xsl:stylesheet> 

同樣,非常基本的工作:

這第二個樣式可以應用到第一個這樣做的實際濾波的結果。它不處理屬性,並且由於某種原因,總是會傳遞elem4,所以仍然存在問題。不知道爲什麼,調試器顯示它總是匹配第二個模板,但我無法想象如何。

這是更常用的聲明式XSLT風格。第一個模板匹配根。它只是複製它,然後將模板應用到其子元素。第二個模板匹配具有flagged-by-filter="true"屬性的後代的任何元素。第三個模板匹配任何至少有一個子元素的元素,具有標記的過濾器屬性但沒有具有所述屬性的後代。第四個模板匹配任何沒有子元素但本身被標記的元素。最終模板匹配任何沒有子元素的元素,也不會被標記。

雖然這不是您的問題的完整解決方案,但我希望這足以讓您走上正軌。如果您無權自由地應用兩個連續的XSLT轉換,則需要根據需要找到從第一個XSLT應用內容的方法。我想不出如何做到這一點,但也許別人有一個好主意。

說了這麼一句話,對於像這樣的問題,要麼不使用XSLT,要麼只是基於過濾器XML以編程方式生成樣式表。以上在性能方面將會非常糟糕,因爲我們一直在將其他文檔應用XPath表達式。不僅如此,每次都必須完全解析。我曾經有過類似的設置,發現性能很差。所以我將對第二個文檔的訪問權限改爲一個擴展函數,該函數將使用預加載的數據調用Java方法。

XSLT對於某些東西來說非常棒,但是當你陷入這樣的複雜情況時,我認爲它最好與其他語言結合使用。

+0

@_G_H:在您的回答結束時,您意味着每次調用document()函數都會導致(重新)解析XML文檔。這是不正確的,事實上,如果一個給定的XSLT處理器重新解析,它是不符合規範的 - 因爲隨着W3C XSLT規範定義它,document()函數必須是* stable * - 即每次使用相同的參數調用時都會生成完全相同的節點。 –

+0

@DimitreNovatchev有趣......我已經觀察到使用'document()'調光性能,但。儘管在將第二個文檔拖放到評估XPath表達式的過程中時,可能會出現這種情況。這是因爲第二個文檔基本上是鍵值對,這意味着搜索屬性值而不是元素名稱。不是一個很好的方法。我將它改爲將屬性文檔讀入「Properties」對象,並通過擴展函數簡單地訪問它。性能提高了不少。 –

+0

@_G_H:如果鍵值對文檔不小,則通過它進行篩選必然會導致性能瓶頸。這裏的正確解決方案是使用''和'key()'函數 - 如果執行多個搜索,這會顯着提高性能。 –

3

這個簡單的變換(小於30行,如果參數-doc的未內聯):

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

<my:paramDoc> 
     <root> 
      <RequiredElements> 
      <elementName>elem1</elementName> 
      <elementName>elem2/elem3/param1</elementName> 
      </RequiredElements> 
     </root> 
</my:paramDoc> 

<xsl:variable name="vPaths" select= 
    "document('')/*/my:paramDoc/*/*/*"/> 

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

<xsl:template match="*/*"> 
    <xsl:variable name="vPath"> 
    <xsl:for-each select= 
     "ancestor-or-self::*[not(position()=last())]"> 
     <xsl:value-of select="concat(name(), '/')"/> 
    </xsl:for-each> 
    </xsl:variable> 

    <xsl:if test= 
    "$vPaths[starts-with(concat(.,'/'), $vPath)]"> 
    <xsl:call-template name="identity"/> 
    </xsl:if> 
</xsl:template> 
</xsl:stylesheet> 

當所提供的XML文檔施加:

<root> 
    <elem1>value1</elem1> 
    <elem2> 
     <elem3> 
      <param1>value2</param1> 
      <param2>value3</param2> 
     </elem3> 
    </elem2> 
    <elem4> 
     <param3>value4</param3> 
    </elem4> 
</root> 

產生想要的,正確的結果

<root> 
    <elem1>value1</elem1> 
    <elem2> 
     <elem3> 
     <param1>value2</param1> 
     </elem3> 
    </elem2> 
</root> 

說明

  1. identity rule副本的每個節點 「原樣」。

  2. 有一個壓倒一切的模板即其父是元件在該模板被做了以下任何元素相匹配:

  3. 一個字符串,它是一個相對的XPath表達式與上下文節點的頂部元件該文檔是爲當前(匹配的)節點生成的。

  4. 如果此相對路徑是作爲參數傳遞的文檔中指定的某個表達式的前綴,那麼我們將對此元素執行標識轉換。

+0

美麗!我甚至不知道你可以使用嵌入在'xsl:variable'中的元素構造變量。 XSLT可以在合適的手中如此優雅。 –

+0

@G_H:是的,XSLT可以非常強大和優雅。只需看看FXSL(2.0 for XSLT 2.0)。 –