簡體   English   中英

使用XSLT 1.0,如何編寫一個模板來處理來自平面XML記錄的編號屬性?

[英]Using XSLT 1.0, how do I write one template to handle numbered attributes from a flat XML record?

我有一個具有這樣結構的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>

元素(每個數字代表一個受撫養人)最多可以增加十個,因此我真的想編寫一個模板來處理受撫養人的每個分組(數目)。 如果沒有該組的數據,我不會將其復制(Person可能只有兩個受撫養人,而不是十個)。 例如,我只會使用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>

$ NUMBER將需要為10個中的每一個進行更新(假設存在依賴項)。 除了為每個違反DRY的數字編寫一個模板(不要重復自己)之外,還有沒有一種干凈的方法可以做到這一點?

編輯:我已經更新了很多詳細的源文檔結構,因為問題的答案已經與依賴全局變量(如xsl:key)的答案一起消失了,因此,其余文檔結構比我更相關本來以為。

當我看到這樣瘋狂地濫用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>

完成此操作后,其他所有操作都會順利進行。

這當然是一個分組問題,其中分組鍵是name屬性的第4個字符( substring(@name, 4, 1) name substring(@name, 4, 1) )。 我不為人們做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>

元素最多可以增加十個,因此我真的想編寫一個模板來處理每個分組(數字)。

我認為您的意思是您想編寫一個模板來處理ns:field元素組,其中元素根據name屬性中'Dep'后面的十進制數字進行分組。 我進一步假設在您的真實數據中,元素是非空的。 另外,從您呈現的樣式表的結構中,我推斷出您願意假定每個分組都將包含一個具有'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()而不是contains()來檢查Dep部分。 這更精確。

  • 我選擇每個組的編號,以便對它進行xsl:sort ,但是不需要它。 我改為通過將發現的前綴與適當的尾部連接起來來形成每個組的其他成員的名稱。 並注意輸出顯示排序有效。

  • 我確實使用模板來變換每個組的元素,因為示例數據似乎表明您不能依賴每個組中存在的所有字段。 將模板應用於空節點列表不會影響輸出樹。

假設您打算對Dep1Dep2等分組的@name的前四個字符進行分組,請考慮XSLT 1.0中的Muenchian方法分組:

輸入項

<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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM