xslt-如何对重复元素进行分组并组合子元素和属性

时间:2013-04-08 18:46:21

标签: xslt

我如何改变以下内容以结合每个菜肴的所有条件和测量值?我正在努力结合重复的子元素。

<?xml version="1.0" encoding="UTF-8"?>
<Test>
  <Experiment id='1'>
    <Dish1>
      <Conditions pressure='x' temp='y'/>
      <Measurement timeStamp='8am' reading='y'/>
    </Dish1>
    <Dish2>
      <Conditions pressure='x' temp='y'/>
      <Measurement timeStamp='8am' reading='y'/>
    </Dish2>
    <Dish1>
      <Conditions pressure='x' temp='y'/>
      <Measurement timeStamp='2pm' reading='y'/>
    </Dish1>
    <Dish2>
      <Conditions pressure='x' temp='y'/>
      <Measurement timeStamp='2pm' reading='y'/>
    </Dish2>
   </Experiment>
   <Experiment id='2'>
    <Dish1>
      <Conditions pressure='x' temp='y'/>
      <Measurement timeStamp='9am' reading='y'/>
    </Dish1>
    </Experiment>
    <Experiment id='2'>
      <Dish1>
        ...
</Test>

期望的结果:

<?xml version="1.0" encoding="UTF-8"?>
<Test>
   <Experiment id='1'>
     <Dish1>
        <Observation pressure='x' temp='y' timeStamp='8am' reading='y'/>
        <Observation pressure='x' temp='y' timeStamp='2pm' reading='y'/>
     </Dish1>
     <Dish2>
        <Observation pressure='x' temp='y' timeStamp='8am' reading='y'/>
        <Observation pressure='x' temp='y' timeStamp='2pm' reading='y'/>
     </Dish2>
   </Experiment>
   <Experiment id='2'>
     <Dish1>
         <Observation pressure='x' temp='y' timeStamp='9am' reading='y'/>
          ...

拜托,谢谢!

这是我到目前为止所尝试的内容:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="Experiment">
        <xsl:copy>
            <xsl:for-each select="Dish1">
                <xsl:element name="Observation">
                    <xsl:attribute name="pressure"><xsl:value-of select="Conditions/@pressure"/></xsl:attribute>
                    <xsl:attribute name="temp"><xsl:value-of select="Conditions/@temp"/></xsl:attribute>
                    <xsl:attribute name="TimeStamp"><xsl:value-of select="Measurement/@TimeStamp"/></xsl:attribute>
                    <xsl:attribute name="reading"><xsl:value-of select="Measurement/@reading"/></xsl:attribute>
                </xsl:element>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <!-- copy everthing not covered above-->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

在写下失败的尝试时,我提出了上述转变。它似乎工作,但我需要验证输出。任何建议/改进将不胜感激。谢谢。

...我的转换仅适用于第一个实验。当我添加:

<xsl:template match="Experiment">
  <xsl:copy>
  <xsl:for-each select="Dish2">
  <xsl:element name="Observation">
  <xsl:attribute name="pressure"><xsl:value-of select="Conditions/@pressure"/></xsl:attribute>
  <xsl:attribute name="temp"><xsl:value-of select="Conditions/@temp"/></xsl:attribute>
  <xsl:attribute name="TimeStamp"><xsl:value-of select="Measurement/@TimeStamp"/></xsl:attribute>
  <xsl:attribute name="reading"><xsl:value-of select="Measurement/@reading"/></xsl:attribute>
  </xsl:element>
  </xsl:for-each>
  </xsl:copy>
</xsl:template>

...它找不到任何Dish2元素。

感谢Ian Roberts,让我写下我失败的尝试引发了一个想法,这导致了下面的工作解决方案。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="Experiment">
        <xsl:copy>
            <xsl:for-each select="Dish1">
                <xsl:element name="Observation">
                    <xsl:attribute name="pressure"><xsl:value-of select="Conditions/@pressure"/></xsl:attribute>
                    <xsl:attribute name="temp"><xsl:value-of select="Conditions/@temp"/></xsl:attribute>
                    <xsl:attribute name="TimeStamp"><xsl:value-of select="Measurement/@TimeStamp"/></xsl:attribute>
                    <xsl:attribute name="reading"><xsl:value-of select="Measurement/@reading"/></xsl:attribute>
                </xsl:element>
            </xsl:for-each>
            <xsl:for-each select="Dish2">
                <xsl:element name="Observation">
                    <xsl:attribute name="pressure"><xsl:value-of select="Conditions/@pressure"/></xsl:attribute>
                    <xsl:attribute name="temp"><xsl:value-of select="Conditions/@temp"/></xsl:attribute>
                    <xsl:attribute name="TimeStamp"><xsl:value-of select="Measurement/@TimeStamp"/></xsl:attribute>
                    <xsl:attribute name="reading"><xsl:value-of select="Measurement/@reading"/></xsl:attribute>
                </xsl:element>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <!-- copy everthing not covered above-->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

1 个答案:

答案 0 :(得分:2)

为了完全灵活,您可能希望避免硬编码 Dish1 Dish2 并允许任意数量的菜肴。实际上,您希望根据实验ID和菜肴名称对“菜肴”元素进行分组。

在XSLT 1.0中,您将使用一种名为Muenchian Grouping的技术。首先定义一个键来分组您的菜肴元素

<xsl:key name="Dish" match="Experiment/*" use="concat(../@id, '-', local-name())" />

然后,为了获得实验中独特的“菜肴”元素,您必须选择组中首先出现的菜单元素为其名称

<xsl:apply-templates 
    select="*[generate-id() = generate-id(key('Dish', concat(../@id, '-', local-name()))[1])]" />

然后,要获得该组中的所有菜肴,您可以选择它们:

<xsl:apply-templates select="key('Dish', concat(../@id, '-', local-name()))" mode="group" />

要组合'Dish'元素的子元素,您只需要一个模板

<xsl:template match="Experiment/*" mode="group">
    <Observation>
        <xsl:apply-templates select="*/@*" />
    </Observation>
</xsl:template>

这是完整的XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>

   <xsl:key name="Dish" match="Experiment/*" use="concat(../@id, '-', local-name())" />

   <xsl:template match="Experiment">
      <xsl:copy>
         <xsl:apply-templates select="@*" />
         <xsl:apply-templates select="*[generate-id() = generate-id(key('Dish', concat(../@id, '-', local-name()))[1])]" />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="Experiment/*">
      <xsl:copy>
         <xsl:apply-templates select="key('Dish', concat(../@id, '-', local-name()))" mode="group" />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="Experiment/*" mode="group">
      <Observation>
         <xsl:apply-templates select="*/@*" />
      </Observation>
   </xsl:template>

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

请注意此处使用模式,因为XSLT包含两个与实验的子元素匹配的模板,因此XSLT需要区分它们。 (第一个是输出不同的'Dish'名称,第二个用于匹配和处理具有该名称的所有元素并组合子项。)

当应用于(截断的)XML示例时,输出以下内容

<Test>
   <Experiment id="1">
      <Dish1>
         <Observation pressure="x" temp="y" timeStamp="8am" reading="y"/>
         <Observation pressure="x" temp="y" timeStamp="2pm" reading="y"/>
      </Dish1>
      <Dish2>
         <Observation pressure="x" temp="y" timeStamp="8am" reading="y"/>
         <Observation pressure="x" temp="y" timeStamp="2pm" reading="y"/>
      </Dish2>
   </Experiment>
   <Experiment id="2">
      <Dish1>
         <Observation pressure="x" temp="y" timeStamp="9am" reading="y"/>
      </Dish1>
   </Experiment>
</Test>

如果你可以使用XSLT 2.0,那么 xsl:for-each-group 是你最好的朋友。试试这个XSLT for XSLT 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="Experiment">
      <xsl:copy>
         <xsl:apply-templates select="@*" />
         <xsl:for-each-group select="*" group-by="local-name()">
            <xsl:copy>
               <xsl:apply-templates select="current-group()" />
            </xsl:copy>
         </xsl:for-each-group>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="Experiment/*">
      <Observation>
         <xsl:apply-templates select="*/@*" />
      </Observation>
   </xsl:template>

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

请注意,在这两种情况下都没有提及 Dish1 Dish ,因此您可以轻松地拥有 Dish3 或更多,而无需更改XSLT。