XSLT通过多个更高级别的节点在一个子节点上进行分组

时间:2013-08-15 14:48:02

标签: xslt

我是XSLT的新手,但我正在尝试通过XSLT获取XML文件以显示某个节点集。我正在使用XSL 1.0。 xml看起来像这样:

<...>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Weight" />
          <effectiveTime value="5/21/2013 12:00:00 AM" />
          <value value="75" unit="lbs" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="BMI" />
          <effectiveTime value="5/21/2013 12:00:00 AM" />
          <value value="14.6" unit="98" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Weight" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="255" unit="lbs" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="BMI" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="49.8" unit="98" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Blood Pressure" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="100/76" unit="mm Hg" />
        </observation>
      </component>
    </organizer>
  </entry>
</...>

我希望输出看起来像这样:

<table>
  <tr>
    <td>5/21/2013</td>
    <td>&nbsp;</td>
    <td>Weight: 75lbs</td>
    <td>BMI: 14.6 90</td>
  </tr>
  <tr>
    <td>5/20/2013</td>
    <td>Blood Pressure: 100/76 mm Hg</td>
    <td>Weight: 255lbs</td>
    <td>BMI: 49.8 90</td>
  </tr>
</table>

基本上,按有效时间(在观察节点内)分组,并将血压,体重和bmi放在后续列中。如果特定日期的特定代码显示名称不存在,我还需要一个空白表格单元格(请参阅第一次日期未列出的血压)。

感谢您的帮助。我正在接受XSLT,但由于有这么多,所以需要时间。

3 个答案:

答案 0 :(得分:1)

你没有说哪个版本的XSLT,所以我假设2.0:

T:\ftemp>type entries.xml 
<entries>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Weight" />
          <effectiveTime value="5/21/2013 12:00:00 AM" />
          <value value="75" unit="lbs" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="BMI" />
          <effectiveTime value="5/21/2013 12:00:00 AM" />
          <value value="14.6" unit="98" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Weight" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="255" unit="lbs" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="BMI" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="49.8" unit="98" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Blood Pressure" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="100/76" unit="mm Hg" />
        </observation>
      </component>
    </organizer>
  </entry>
</entries>
T:\ftemp>call xslt2 entries.xml entries.xsl 
<table>
   <tr>
      <td>5/21/2013</td>
      <td>&nbsp;</td>
      <td>Weight: 75 lbs</td>
      <td>BMI: 14.6 98</td>
   </tr>
   <tr>
      <td>5/20/2013</td>
      <td>Blood Pressure: 100/76 mm Hg</td>
      <td>Weight: 255 lbs</td>
      <td>BMI: 49.8 98</td>
   </tr>
</table>
T:\ftemp>type entries.xsl 
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

<xsl:output method="html"/>

<xsl:template match="entries">
  <!--dictate the order of the columns this way-->
  <xsl:variable name="fields" select="('Blood Pressure','Weight','BMI')"/>
  <!--create the table-->
  <table>
    <!--grouped by time-->
    <xsl:for-each-group select="entry"
              group-by="organizer/component/observation/effectiveTime/@value">
      <tr>
        <!--subset of time-->
        <td>
          <xsl:value-of select="substring-before(
                  organizer/component/observation/effectiveTime/@value,' ')"/>
        </td>
        <!--the fields in order-->
        <xsl:for-each select="$fields">
          <xsl:variable name="this" 
    select="current-group()[organizer/component/observation/code/@displayName=
    current()]/organizer/component/observation/value"/>
          <xsl:choose>
            <xsl:when test="$this">
              <td>
                <xsl:value-of 
                    select="concat(.,': ',$this/@value,' ',$this/@unit)"/>
              </td>
            </xsl:when>
            <xsl:otherwise><td>&#xa0;</td></xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
      </tr>
    </xsl:for-each-group>
  </table>
</xsl:template>
</xsl:stylesheet>
T:\ftemp>rem Done! 

答案 1 :(得分:0)

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

  <xsl:key name="kObservationByTime" match="observation" use="effectiveTime/@value" />

  <xsl:template match="/">
    <table>
      <tr>
        <th>Time</th>
        <th>Blood Pressure</th>
        <th>Weight</th>
        <th>BMI</th>
      </tr>
      <xsl:apply-templates mode="group" select="
        *//observation[
          generate-id() 
          =
          generate-id(key('kObservationByTime', effectiveTime/@value)[1])
        ][position() &gt; last() - 3]
      " />
        <xsl:sort select="effectiveTime/@value" order="descending" />
      </xsl:apply-templates>
    </table>
  </xsl:template>

  <xsl:template match="observation" mode="group">
    <xsl:variable name="time" select="effectiveTime/@value" />
    <xsl:variable name="thisGroup" select="key('kObservationByTime', $time)" />

    <tr>
      <td><xsl:value-of select="substring-before($time, ' ')" /></td>
      <td><xsl:apply-templates select="$thisGroup[code[@displayName = 'Blood Pressure']]" /></td>
      <td><xsl:apply-templates select="$thisGroup[code[@displayName = 'Weight']]" /></td>
      <td><xsl:apply-templates select="$thisGroup[code[@displayName = 'BMI']]" /></td>
    </tr>
  </xsl:template>

  <xsl:template match="observation">
    <xsl:value-of select="normalize-space(concat(value/@value, ' ', value/@unit))" />
  </xsl:template>
</xsl:stylesheet>

给你

<table>
<tr>
<th>Time</th>
<th>Blood Pressure</th>
<th>Weight</th>
<th>BMI</th>
</tr>
<tr>
<td>5/21/2013</td>
<td></td>
<td>75 lbs</td>
<td>14.6 98</td>
</tr>
<tr>
<td>5/20/2013</td>
<td>100/76 mm Hg</td>
<td>255 lbs</td>
<td>49.8 98</td>
</tr>
</table>

http://www.xmlplayground.com/O9Z9DT

此解决方案使用<xsl:key>进行分组。

[position() &gt; last() - 3]谓词选择最后2个组进行显示。替代方案[position() &lt; 3]将选择前两个。两者都按文档顺序工作,而<xsl:sort>仅影响输出顺序。

请注意due to an unwise choice in date formatting,使用漂亮的代码无法明智地对结果进行排序。

按日期对条目进行排序的非漂亮代码如下所示:

<xsl:sort select="
  concat(
    substring-after(substring-after(substring-before(effectiveTime/@value, ' '), '/'), '/'),
    '-',
    10 + substring-before(effectiveTime/@value, '/'),
    '-',
    10 + substring-before(substring-after(effectiveTime/@value, '/'), '/')
  )
" order="descending" />

...或者您只是使用合理的日期格式,痛苦就会消失。 ;)

答案 2 :(得分:0)

以下是使用XSLT 1.0的解决方案版本...而不是使用字段名称变量,我为每个字符编码列处理。

T:\ftemp>type entries.xml 
<entries>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Weight" />
          <effectiveTime value="5/21/2013 12:00:00 AM" />
          <value value="75" unit="lbs" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="BMI" />
          <effectiveTime value="5/21/2013 12:00:00 AM" />
          <value value="14.6" unit="98" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Weight" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="255" unit="lbs" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="BMI" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="49.8" unit="98" />
        </observation>
      </component>
    </organizer>
  </entry>
  <entry>
    <organizer>
      <component>
        <observation>
          <code displayName="Blood Pressure" />
          <effectiveTime value="5/20/2013 12:00:00 AM" />
          <value value="100/76" unit="mm Hg" />
        </observation>
      </component>
    </organizer>
  </entry>
</entries>
T:\ftemp>call xslt entries.xml entries1.xsl 
<table>
   <tr>
      <td>5/21/2013</td>
      <td>&nbsp;</td>
      <td>Weight: 75 lbs</td>
      <td>BMI: 14.6 98</td>
   </tr>
   <tr>
      <td>5/20/2013</td>
      <td>Blood Pressure: 100/76 mm Hg</td>
      <td>Weight: 255 lbs</td>
      <td>BMI: 49.8 98</td>
   </tr>
</table>
T:\ftemp>type entries1.xsl 
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

<xsl:output method="html"/>

<xsl:key name="times" match="entry"
         use="organizer/component/observation/effectiveTime/@value"/>

<xsl:template match="entries">
  <!--create the table-->
  <table>
    <!--grouped by time-->
    <xsl:for-each select="entry[generate-id(.)=
  generate-id(key('times',
                  organizer/component/observation/effectiveTime/@value)[1])]">
      <tr>
        <!--subset of time-->
        <td>
          <xsl:value-of select="substring-before(
                  organizer/component/observation/effectiveTime/@value,' ')"/>
        </td>
        <!--the fields in order-->
        <xsl:variable name="values" 
          select="key('times',
                      organizer/component/observation/effectiveTime/@value)/
                  organizer/component/observation"/>
        <xsl:choose>
          <xsl:when test="$values/code[@displayName='Blood Pressure']">
            <td>
              <xsl:for-each
                select="$values[code/@displayName='Blood Pressure']">
                <xsl:value-of 
          select="concat('Blood Pressure: ',value/@value,' ',value/@unit)"/>
              </xsl:for-each>
            </td>
          </xsl:when>
          <xsl:otherwise><td>&#xa0;</td></xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="$values/code[@displayName='Weight']">
            <td>
              <xsl:for-each
                select="$values[code/@displayName='Weight']">
                <xsl:value-of 
                    select="concat('Weight: ',value/@value,' ',value/@unit)"/>
              </xsl:for-each>
            </td>
          </xsl:when>
          <xsl:otherwise><td>&#xa0;</td></xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="$values/code[@displayName='BMI']">
            <td>
              <xsl:for-each
                select="$values[code/@displayName='BMI']">
                <xsl:value-of 
                  select="concat('BMI: ',value/@value,' ',value/@unit)"/>
              </xsl:for-each>
            </td>
          </xsl:when>
          <xsl:otherwise><td>&#xa0;</td></xsl:otherwise>
        </xsl:choose>
      </tr>
    </xsl:for-each>
  </table>
</xsl:template>
</xsl:stylesheet>
T:\ftemp>rem Done!