2016-06-15 26 views
0

我有一個像這樣的結構的XML文檔。使用XSLT 1.0,我該如何編寫一個模板來處理平面XML記錄中的編號屬性?

<Document> 
    <record>  
     <field name="Dep1FirstName">Frank</field> 
     <field name="Dep1MiddleName"/> 
     <field name="Dep1LastName">Billings</field> 
     <field name="Dep1DoB">1952-01-20</field> 
     <field name="Dep1Gender"/> 
     <field name="Dep2Prefix"/> 
     <field name="Dep2FirstName"/> 
     <field name="Dep2MiddleName"/> 
     <field name="Dep2LastName"/> 
    </record> 
    <record> 
     <field name="Date_of_Birth">1978-09-20</field>  
     <field name="Dep1FirstName"/> 
     <field name="Dep1MiddleName"/> 
     <field name="Dep1LastName"/> 
     <!-- many more --> 
    </record> 
</Document> 

元素(代表一個因每個號碼)可以達到十個,所以我真的想寫一個模板來處理每個分組(編號)的家屬。如果沒有該組的數據,我不會複製它(人可能只有兩個家屬而不是十個)。例如,我只會使用Dep1。到目前爲止,我已經提出了這樣的事情:

<xsl:template match="ns:Document"> 
    <div class="container"> 
     <xsl:apply-templates select="ns:Content"/> 
    </div> 
</xsl:template> 
<xsl:template match="ns:record">                   
     <div class="page">                      
      <div>                        
        <xsl:apply-templates/>                  
      </div>                       
    </div>                         
</xsl:template> 
<xsl:template match="ns:field[@name='Dep1FirstName' and text()]"> 
    <div class="dependents_info"> 
     <xsl:apply-templates select="../ns:field[contains(@name,'Dep') and contains(@name,'1')" mode="secondary"/> 
    </div> 
</xsl:template> 
<!-- make per dependent template (can be up to ten per schema) --> 
<xsl:template match="ns:field[contains(@name,'Dep') and contains(@name,'$NUMBER') and contains(@name,'FirstName')]" mode="secondary"> 
    <div class="dependent"> 
     <xsl:value-of select="."/> 
     <xsl:value-of select="../ns:field[@name='Dep$NUMBERLastName']" /> 
     ... 
    </div> 
</xsl:template> 

的$數量將需要爲每個10(假定存在依賴性)的更新。有沒有一種乾淨的方式來做到這一點,而不是爲每個違反DRY的數字寫一個模板(不要重複自己)?

編輯:我更新了很多更詳細的源文件的結構,因爲問題的答案已經與依賴於像xsl:key這樣的全局變量的答案相結合,因此,其餘的文檔結構是比我原先想象的更有意義。

+1

我不明白你的問題。 「一個模板處理每個分組(數字)」是什麼意思? –

+0

另外,請發佈整個XML和XSL。您引用了名稱空間謂詞ns:,但不顯示字段元素的父項。而通過分組,你的意思是#在Dep#中......? – Parfait

+0

是的,該組是由#中的屬性值。實際的XML文檔和XSL片是巨大的,所以我真的試圖削減下來到最低限度,給的什麼,我試圖做的感覺。不幸的是,生成XML文檔的工具超級有限,所以記錄只是手動複製。 – lithiumfrost

回答

0

元素可以達到10個,所以我真的想寫一個模板來處理每個分組(數字)。

我想你的意思是你想要編寫模板與處理ns:field元素,其中的元素按照下面「德普」在他們name屬性的十進制數分組組。我還假設在你的真實數據中,元素是非空的。此外,根據您提供的樣式表的結構,我推斷您願意假定每個分組都將包含一個具有「DepXFirstName」形式的name屬性的字段。

我想你對數字的關注度有點太高。 XSLT 1.0沒有C風格的for循環。另一方面,它可以非常靈活地處理例如組號碼中有空位的情況 - 甚至可能在號碼1處。

由於您願意假設每個組都將包含'FirstName '元素,你可以使用這些元素作爲每個組的代表性元素。因此,您可以將模板應用於實現所需轉換的特定元素。此外,您還可以通過「FirstName」前面的前綴關聯其他相關元素。並且,如果元素或組存在冒出數字順序的風險,那麼您可以挑選數字並按數字排序。

這裏是一個樣式表,做一切:

<?xml version="1.0"?> 
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:ns="urn:x:ns"> 
    <xsl:template match = 'ns:root'> 
    <xsl:if test="ns:field[starts-with(substring-before(@name, 'FirstName'), 'Dep')]"> 
     <div class="dependents_info"> 
     <xsl:for-each select="ns:field[starts-with(substring-before(@name, 'FirstName'), 'Dep')]"> 
     <xsl:sort select="substring-after(substring-before(@name, 'FirstName'), 'Dep')" 
      data-type="number" /> 
     <xsl:variable name="prefix" select="substring-before(@name, 'FirstName')"/> 
     <div class="dependent"> 
     <xsl:apply-templates 
      select="../ns:field[@name = concat($prefix, 'Prefix')]" 
      mode="name" /> 
     <xsl:apply-templates 
      select="../ns:field[@name = concat($prefix, 'FirstName')]" 
      mode="name" /> 
     <xsl:apply-templates 
      select="../ns:field[@name = concat($prefix, 'MiddleName')]" 
      mode="name" /> 
     <xsl:apply-templates 
      select="../ns:field[@name = concat($prefix, 'LastName')]" 
      mode="name" /> 
     <xsl:apply-templates 
      select="../ns:field[@name = concat($prefix, 'Gender')]" 
      mode="paren-name" /> 
     <xsl:apply-templates 
      select="../ns:field[@name = concat($prefix, 'DoB')]" 
      mode="name" /> 
     </div> 
     </xsl:for-each> 
     </div> 
    </xsl:if> 
    </xsl:template> 

    <xsl:template match="ns:field" mode="name"> 
    <xsl:value-of select='.'/><xsl:text> </xsl:text> 
    </xsl:template> 

    <xsl:template match="ns:field" mode="paren-name">(<xsl:value-of select='.'/>)<xsl:text> </xsl:text> 
    </xsl:template> 

</xsl:stylesheet> 

使用樣式表,此數據...

<?xml version="1.0"?> 
<root xmlns="urn:x:ns"> 
<field name="Dep1MiddleName">Jane</field> 
<field name="Dep1LastName">Smith</field> 
<field name="Dep1DoB">2/21/64</field> 
<field name="Dep1Gender">F</field> 
<field name="Dep2Prefix">Mr.</field> 
<field name="Dep2FirstName">John</field> 
<field name="Dep2LastName">Smith</field> 
<field name="Dep1FirstName">Sarah</field> 
</root> 

... xsltproc的產生這樣的輸出:

<?xml version="1.0"?> 
<div class="dependents_info"><div class="dependent">Sarah Jane Smith (F) 2/21/64 </div><div class="dependent">Mr. John Smith </div></div> 

輸出不完全漂亮,但你可以放入任何額外的標記和格式你喜歡。需要注意的是:

  • 我使用xsl:if,而不是一個單獨的模板,以確定是否存在任何相關團體。這是一種風格的選擇,但它有它正常工作,如果相關的編號恰好開始於不同的數從1

  • 我用substring-before()從「DepXFirstName」挑出「DepX」前綴就好優勢我們假設的元素可以用作他們組的代表成員。

  • 我用starts-with()而不是檢查Dep部分。這更精確。

  • 我挑出每個組的號碼,以便xsl:sort就可以了,否則就不需要了。我改爲通過將發現的前綴與適當的尾部連接起來來形成每個組的其他成員的名字。並注意輸出結果表明排序工作。

  • 我使用模板來改變每個組的元素,因爲示例數據似乎表明,你不能依賴於存在每個組中的所有領域。將模板應用於空節點列表不會影響輸出樹。

+0

我非常欣賞擴展的解釋和推理以及樣式表。這與我原先想象的方向不同,但它運作良好,即使看起來有點不同。如果第一個依賴項具有值,我將它調整爲僅生成整個部分。我還進一步調整它,只爲那些有價值的家屬產生一條線。非常感謝! – lithiumfrost

1

當我看到像這樣濫用XML的瘋狂方式時,我的直覺就是先寫一個轉換,將其變成理智的東西。也就是說,把

<e> 
    <field name="Dep1FirstName"/> 
    <field name="Dep1MiddleName"/> 
    <field name="Dep1LastName"/> 
    <field name="Dep1DoB"/> 
    <field name="Dep1Gender"/> 
    <field name="Dep2Prefix"/> 
    <field name="Dep2FirstName"/> 
    <field name="Dep2MiddleName"/> 
    <field name="Dep2LastName"/> 
</e> 

<e> 
    <dep nr="1"> 
     <FirstName/> 
     <MiddleName/> 
     <LastName/> 
     <DoB/> 
     <Gender/ 
    </dep> 
    <dep nr="2"> 
     <Prefix/> 
     <FirstName/> 
     <MiddleName/> 
     <LastName/> 
    </dep> 
</e> 

一旦你做到了這一點,其他一切都是一帆風順的。

當然,這是一組問題,其中關鍵的分組是名稱屬性(substring(@name, 4, 1))的第4個字符。我不爲人們做XSLT 1.0分組,它更容易下載一個XSLT 2.0處理器,這使得任務變得簡單。在XSLT 2.0是:

<xsl:for-each-group select="field" 
    group-adjacent="substring(@name, 4, 1)"> 
    <xsl:element name="{substring(@name, 3, 1)}"> 
    <xsl:attribute name="nr" select="{current-grouping-key()}"/> 
    <xsl:for-each select="current-group()"> 
     <xsl:element name="{substring(@name, 5)}"> 
     <xsl:copy-of select="node()"/> 
     </xsl:element> 
    </xsl:for-each> 
    </xsl:element> 
</xsl:for-each-group> 
+0

我同意,在XSLT v2中分組更容易,而源文檔是糟糕的結構,但我多少會陷入我所擁有的,因爲這是一個我無法控制的雲SaaS應用程序。只是一個用戶。 – lithiumfrost

0

假設,你打算@name的前四個字符分組爲DEP1DEP2等分組,考慮Muenchian方法XSLT 1.0分組:

輸入

<root> 
    <field name="Dep1FirstName"/> 
    <field name="Dep1MiddleName"/> 
    <field name="Dep1LastName"/> 
    <field name="Dep1DoB"/> 
    <field name="Dep1Gender"/> 
    <field name="Dep2Prefix"/> 
    <field name="Dep2FirstName"/> 
    <field name="Dep2MiddleName"/> 
    <field name="Dep2LastName"/> 
</root> 

XSLT腳本

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

<xsl:key name="depnum" match="field" use="substring(@name, 1, 4)" /> 

    <xsl:template match="root"> 
    <div class="dependents_info"> 
     <xsl:for-each select="field[generate-id() = 
          generate-id(key('depnum', substring(@name, 1, 4))[1])]"> 
     <div class="dependent"> 
      <xsl:for-each select="key('depnum', substring(@name, 1, 4))"> 
       <field><xsl:copy-of select="@name"/></field> 
      </xsl:for-each> 
     </div> 
     </xsl:for-each>  
    </div> 
    </xsl:template> 

</xsl:transform> 

輸出

<?xml version='1.0' encoding='UTF-8'?> 
<div class="dependents_info"> 
    <div class="dependent"> 
    <field name="Dep1FirstName"/> 
    <field name="Dep1MiddleName"/> 
    <field name="Dep1LastName"/> 
    <field name="Dep1DoB"/> 
    <field name="Dep1Gender"/> 
    </div> 
    <div class="dependent"> 
    <field name="Dep2Prefix"/> 
    <field name="Dep2FirstName"/> 
    <field name="Dep2MiddleName"/> 
    <field name="Dep2LastName"/> 
    </div> 
</div> 
相關問題