如何在XSLT中按节点值对XML元素进行分组?

时间:2013-11-04 00:05:28

标签: xml xslt

我正在尝试从文件制作者数据库中对记录进行分组。我正在导出为XML,可以选择使用XSLT进行转换。

我一直在做一些搜索和阅读其他帖子,我认为它们不会涵盖我想要做的事情。

XML的摘录:

<?xml version="1.0" encoding="UTF-8"?>
<!-- This grammar has been deprecated - use FMPXMLRESULT instead -->
<FMPDSORESULT xmlns="http://www.filemaker.com/fmpdsoresult">
   <ERRORCODE>0</ERRORCODE>
   <DATABASE>Artpostersnbbs.fp7</DATABASE>
   <LAYOUT />
   <ROW MODID="19" RECORDID="11116">
      <Art_Type>Poster</Art_Type>
      <Location>1</Location>
      <Line1>ELEVATOR
                   MACHINE
                   ROOM
                   107</Line1>
   </ROW>
   <ROW MODID="19" RECORDID="11116">
      <Art_Type>Poster</Art_Type>
      <Location>2</Location>
      <Line1>ELEVATOR
                   MACHINE
                   ROOM
                   107</Line1>
   </ROW>
   <ROW MODID="19" RECORDID="11116">
      <Art_Type>Poster</Art_Type>
      <Location>3</Location>
      <Line1>ELEVATOR
                   MACHINE
                   ROOM
                   107</Line1>
   </ROW>
</FMPDSORESULT>

我希望该组的每条记录都与ART_TYPE和LINE1相匹配。在分组之后,它应该将匹配中的位置添加到正在分组的位置,因此它应该如下所示:

<ROW MODID="19" RECORDID="11116">
    <Art_Type>Poster</Art_Type>
    <Location>1 2 3</Location>
    <Line1>ELEVATOR
           MACHINE
           ROOM
           107
    </Line1>
</ROW>

如何开始的任何帮助将不胜感激。还有什么好的xslt 1.0测试程序吗?

提前致谢!

编辑:我被指向muenchian分组并找到了这个网站:http://www.jenitennison.com/xslt/grouping/muenchian.html

所以从阅读中得出结论:

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="artTypeNames" match="ROW" use="Art_Type" /> 
  <xsl:key name="artCopy" match="ROW" use="Line1" />
  <xsl:template match="FMPDSORESULT">
    <xsl:for-each select="ROW[count(. | key('artTypeNames', Art_Type)[1]) = 1]">
        <xsl:sort select="Art_Type" />
        <xsl:value-of select="Art_Type" />
        <xsl:for-each select="key('artTypeNames', Art_Type)">
            <xsl:sort select="Art_Type" />
            <xsl:value-of select="Art_Type" />
        </xsl:for-each>
    </xsl:for-each>

    <xsl:apply-templates/>
  </xsl:template>
</xsl:stylesheet>

我将XML和XSLT输入到在线XML Transformer中,我得到'XSLT无效'错误。

这令人沮丧。

EDIT2:在Tim的帮助下,我能够构建一个合适的XSLT转换:

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fm="http://www.filemaker.com/fmpdsoresult">

  

                     

<xsl:template match="fm:FMPDSORESULT">
    <xsl:apply-templates select="fm:ROW[count(. | key('lineData', fm:Line1)[1]) = 1]">
    </xsl:apply-templates>
</xsl:template>

<xsl:template match="fm:ROW">
    <xsl:copy>
       <xsl:apply-templates select="fm:Art_Type" />
       <fm:Location>
          <xsl:apply-templates select="key('artTypeNames', fm:Art_Type)/fm:Location" />
       </fm:Location>
       <xsl:apply-templates select="fm:Line1" />
    </xsl:copy>
</xsl:template>

<xsl:template match="fm:Location">
   <xsl:if test="position() > 1">-</xsl:if>
   <xsl:value-of select="." />
</xsl:template>

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

它将Art_Type分组,然后按Line1文本分组,但现在将位置编号添加到所有这些中,如下所示:

<ROW xmlns="http://www.filemaker.com/fmpdsoresult">
<Art_Type>Poster</Art_Type>
<fm:Location xmlns:fm="http://www.filemaker.com/fmpdsoresult" xmlns="">1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34</fm:Location>
<Line1>CUSTODIAL
  LOUNGE

  117A
</Line1>
</ROW>
<ROW xmlns="http://www.filemaker.com/fmpdsoresult">
<Art_Type>Poster</Art_Type>
<fm:Location xmlns:fm="http://www.filemaker.com/fmpdsoresult" xmlns="">1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34</fm:Location>
<Line1>STORAGE
  ROOM

  117B
</Line1>
</ROW>

如果Line1文本不同,则应将其添加到另一个组中。

2 个答案:

答案 0 :(得分:4)

如果您使用的是XSLT 2.0,请查找有关xsl:for-each-group的信息。如果您使用的是1.0,请查找有关“Muenchian分组”的信息。

答案 1 :(得分:3)

评论中提到的一个问题是名称空间。在XML中,有一个名称空间声明:

<FMPDSORESULT xmlns="http://www.filemaker.com/fmpdsoresult">

这意味着元素及其下面的所有后代元素都属于该命名空间(除非过度下载)。但是在你的XSLT中没有提到任何命名空间,因此XSLT正在寻找属于NO命名空间的元素。

您需要在XSLT中声明命名空间,然后确保在尝试引用原始XML中的任何元素时使用命名空间前缀。

<xsl:stylesheet version="1.1" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     xmlns:fm="http://www.filemaker.com/fmpdsoresult">
  <xsl:key name="artTypeNames" match="fm:ROW" use="fm:Art_Type" /> 
  <xsl:template match="fm:FMPDSORESULT">

至于你的XSLT示例,我在尝试时没有收到任何错误,尽管你可能只是展示了一个片段。分组看起来是正确的(假设您确实打算通过 Art_Type ROW 元素进行分组),但您缺少的是复制现有元素的任何代码。

<xsl:for-each select="fm:ROW[count(. | key('artTypeNames', fm:Art_Type)[1]) = 1]">
    <xsl:sort select="fm:Art_Type" />
    <xsl:copy>
       <xsl:copy-of select="@*" />
       <xsl:copy-of select="fm:Art_Type" />

因此,此代码段会复制现有的 ROW 元素及其属性,然后复制 Art_Type 元素(对于组中的所有元素都是相同的)。< / p>

试试这个(完整的)XSLT

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fm="http://www.filemaker.com/fmpdsoresult">
  <xsl:key name="artTypeNames" match="fm:ROW" use="fm:Art_Type" /> 

  <xsl:template match="fm:FMPDSORESULT">
    <xsl:for-each select="fm:ROW[count(. | key('artTypeNames', fm:Art_Type)[1]) = 1]">
        <xsl:sort select="fm:Art_Type" />
        <xsl:copy>
           <xsl:copy-of select="@*" />
           <xsl:copy-of select="fm:Art_Type" />
           <fm:Location>
             <xsl:for-each select="key('artTypeNames', fm:Art_Type)">
                <xsl:if test="position() > 1">-</xsl:if>
                <xsl:value-of select="fm:Location" />
             </xsl:for-each>
           </fm:Location>
           <xsl:copy-of select="fm:Line1" />
        </xsl:copy>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

请注意,内部for-each循环中不需要排序,因为显然 Art_Type 对于组中的所有元素都是相同的。

编辑:如果要检查两个字段以确定组成一个组,则需要使用连接键来实现此目的。在您的情况下,您说要同时检查 Art_Type fm:Line1 ,因此您的密钥可能看起来像这样。

<xsl:key name="artTypeNames" match="fm:ROW" use="concat(fm:Art_Type, '||', fm:Line1)" /> 

注意使用'||'这里。这可以是任何东西,只要它没有出现在你正在检查的任何一个元素中。

要使用此密钥,您只需使用与之前类似的方式,但使用元素的连接值。例如

<xsl:apply-templates select="fm:ROW[count(. | key('artTypeNames', concat(fm:Art_Type, '||', fm:Line1))[1]) = 1]">

注意,通常最好使用 xsl:apply-templates 而不是 xsl:for-each ,如果只是为了减少缩进。

尝试这个XSLT

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fm="http://www.filemaker.com/fmpdsoresult">
   <xsl:output method="xml" indent="yes"/>
   <xsl:key name="artTypeNames" match="fm:ROW" use="concat(fm:Art_Type, '||', fm:Line1)"/>

   <xsl:template match="fm:FMPDSORESULT">
      <xsl:apply-templates select="fm:ROW[count(. | key('artTypeNames', concat(fm:Art_Type, '||', fm:Line1))[1]) = 1]">
         <xsl:sort select="fm:Art_Type"/>
      </xsl:apply-templates>
   </xsl:template>

   <xsl:template match="fm:ROW">
      <xsl:copy>
         <xsl:apply-templates select="@*"/>
         <xsl:apply-templates select="fm:Art_Type"/>
         <fm:Location>
            <xsl:apply-templates select="key('artTypeNames', concat(fm:Art_Type, '||', fm:Line1))/fm:Location"/>
         </fm:Location>
         <xsl:apply-templates select="fm:Line1"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="fm:Location">
      <xsl:if test="position() &gt; 1">-</xsl:if>
      <xsl:value-of select="."/>
   </xsl:template>

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

另请注意使用XSLT identity transform复制现有元素而不是 xsl:copy-of