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