XSLT将平面结构转换为数组

时间:2011-07-11 20:12:15

标签: arrays xslt flat

这是源XML:

<customers>
    <firstname1>Sean</firstname1>
    <lastname1>Killer</lastname1>
    <sex1>M</sex1>
    <firstname2>Frank</firstname2>
    <lastname2>Woods</lastname2>
    <sex2>M</sex2>
    <firstname3>Jennifer</firstname3>
    <lastname3>Lee</lastname3>
    <sex3>F</sex3>
</customers>

如何将其转换为此?

<MyCustomers>
    <Customer>
        <Name> Sean Killer</Name>
        <Sex>M</Sex>
    </Customer>
    <Customer>
        <Name> Frank Woods</Name>
        <Sex>M</Sex>
    </Customer>
    <Customer>
        <Name>Jennifer Lee</Name>
        <Sex>F</Sex>
    </Customer>
</MyCustomers>

3 个答案:

答案 0 :(得分:4)

根据评论:

  

如果元素不在后续订单中会怎样?

在这种情况下(假设XSLT 1.0),您可以使用translate()获取元素的id,然后使用concat()构建的正确名称搜索相应的元素。我会将following-sibling::轴更改为../parent::的缩写),以确保最终捕获当前firstname之前的元素。

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

    <xsl:template match="customers">
        <MyCustomers>
            <xsl:apply-templates select="*[starts-with(name(),'firstname')]"/>
        </MyCustomers>
    </xsl:template>

    <xsl:template match="*[starts-with(name(),'firstname')]">
        <xsl:variable name="id" select="translate(name(),'firstname','')"/>

        <Customer>
            <Name><xsl:value-of select="concat(.,' ',
                    ../*[name()=concat('lastname',$id)])"/></Name>
            <Sex><xsl:value-of select="../*[name()=concat('sex',$id)]"/></Sex>
        </Customer>
    </xsl:template>

</xsl:stylesheet>

过时的回答

假设问题中显示的是固定输入文档结构,那么精细工作的XSLT 1.0转换是:

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

    <xsl:template match="customers">
        <MyCustomers>
            <xsl:apply-templates select="*[starts-with(name(),'firstname')]"/>
        </MyCustomers>
    </xsl:template>

    <xsl:template match="*[starts-with(name(),'firstname')]">
        <Customer>
            <Name><xsl:value-of select="concat(.,' ',
                    following-sibling::*[1]
                    [starts-with(name(),'lastname')])"/></Name>
            <Sex><xsl:value-of select="following-sibling::*[2]
                    [starts-with(name(),'sex')]"/></Sex>
        </Customer>
    </xsl:template>

</xsl:stylesheet>

小解释

您需要XPath 1.0函数starts-with(),因为XML输入中标记的名称令人遗憾。您可以使用following-sibling::轴获取名称以firstname开头的任何元素所需的以下兄弟标记。

答案 1 :(得分:0)

这是一个XSLT 2.0样式表,它将获得您正在寻找的输出,即使它们不是有序的。它还按“firstname”元素名称排序。

示例XML输入(混合显示不同的顺序):

<customers>
  <lastname1>Killer</lastname1>
  <sex3>F</sex3>
  <firstname2>Frank</firstname2>
  <firstname1>Sean</firstname1>
  <lastname2>Woods</lastname2>
  <sex2>M</sex2>
  <firstname3>Jennifer</firstname3>
  <sex1>M</sex1>
  <lastname3>Lee</lastname3>
</customers>

XSLT 2.0样式表(使用Saxon-HE 9.3测试):

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

  <xsl:template match="node()|@*">
    <xsl:choose>
      <xsl:when test="name()[starts-with(.,'firstname')]">
        <xsl:variable name="suffix" select="substring(name(),10)"></xsl:variable>
        <xsl:message><xsl:value-of select="$suffix"/></xsl:message>
        <customer>
          <Name>
            <xsl:value-of select="concat(.,' ',/customers/*[starts-with(name(),'lastname')][ends-with(name(),$suffix)])"/>  
          </Name>
          <Sex>
            <xsl:value-of select="/customers/*[starts-with(name(),'sex')][ends-with(name(),$suffix)]"/>
          </Sex>
        </customer>
      </xsl:when>
      <xsl:when test="name()='customers'">
        <MyCustomers>
          <xsl:apply-templates>
            <xsl:sort select="name()[starts-with(.,'firstname')]"></xsl:sort>
          </xsl:apply-templates>
        </MyCustomers>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

<强>输出:

<MyCustomers>
   <customer>
      <Name>Sean Killer</Name>
      <Sex>M</Sex>
   </customer>
   <customer>
      <Name>Frank Woods</Name>
      <Sex>M</Sex>
   </customer>
   <customer>
      <Name>Jennifer Lee</Name>
      <Sex>F</Sex>
   </customer>
</MyCustomers>

答案 2 :(得分:0)

即使top元素的子元素以任意方式洗牌,此转换也会产生想要的结果

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

 <xsl:variable name="vNumCustomers"
      select="count(/*/*) div 3"/>

 <xsl:template match="/*">
     <MyCustomers>
       <xsl:for-each select=
           "*[not(position() > $vNumCustomers)]">
         <xsl:variable name="vNum" select="position()"/>

         <Customer>
          <Name>
            <xsl:value-of select=
             "concat(/*/*[name()=concat('firstname',$vNum)],
                     ' ',
                     /*/*[name()=concat('lastname',$vNum)]
                     )
             "/>
          </Name>
          <Sex>
            <xsl:value-of select=
             "/*/*[name()=concat('sex',$vNum)]
             "/>
          </Sex>
         </Customer>
       </xsl:for-each>
     </MyCustomers>
 </xsl:template>
</xsl:stylesheet>

应用于此XML文档(对提供的文档进行任意重新洗牌):

<customers>
    <sex1>M</sex1>
    <lastname2>Woods</lastname2>
    <lastname1>Killer</lastname1>
    <sex2>M</sex2>
    <firstname3>Jennifer</firstname3>
    <firstname2>Frank</firstname2>
    <lastname3>Lee</lastname3>
    <firstname1>Sean</firstname1>
    <sex3>F</sex3>
</customers>

产生了想要的正确结果

<MyCustomers>
   <Customer>
      <Name>Sean Killer</Name>
      <Sex>M</Sex>
   </Customer>
   <Customer>
      <Name>Frank Woods</Name>
      <Sex>M</Sex>
   </Customer>
   <Customer>
      <Name>Jennifer Lee</Name>
      <Sex>F</Sex>
   </Customer>
</MyCustomers>

<强>解释

  1. 我们计算出现数据的客户数量。变量$vNumCustomers保存此数据。

  2. 对于每个客户{i}(i = 1到$vNumCustomers),我们会创建相应的<Customer{i}>元素。为了避免使用递归,我们在这里使用 the Piez method