【问题标题】:XSLT Grouping on one subnode through multiple higher level nodes通过多个更高级别节点在一个子节点上进行 XSLT 分组
【发布时间】:2013-08-16 02:34:41
【问题描述】:

我对 XSLT 还很陌生,但我正在尝试获取一个 XML 文件来显示通过 XSLT 设置的某个节点。我正在使用 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,但它需要时间,因为有这么多。

【问题讨论】:

    标签: xslt


    【解决方案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! 
    

    【讨论】:

    • 我的错...我使用的是 XSLT 1.0
    【解决方案2】:

    这是一个使用 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! 
    

    【讨论】:

    • 两种 xsl 1.0 解决方案都很好用。这个让我可以更好地控制输出中的异常(但我有另一个供将来参考)。我唯一需要处理的是 1) 可能有超过 2 个日期,我只需要前两个日期和 2) 同一日期可能有多个条目,我需要最后两个日期的最后一组。
    • 这是一个相当单一且重复的解决方案,它将受益于重构。 &lt;xsl:choose&gt; 可以抽象成一个命名模板,例如,像这样:bpaste.net/show/YRwcsRexg9vA81kS0oAl
    • @Tomalak 将其拉出到它自己的模板中的唯一问题是 3 个选项之一不需要显示单位,只需要显示值,因此它在所有选项中并不统一。否则,我绝对会把它拉出来重用,以减少所有代码行。
    • @Tomalak - 是否可以使用抽象模板仅提取文档顺序中的第一项(根据另一篇文章中的区别)?
    • @jjasper 是的,如my comment 所示。
    【解决方案3】:
    <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

    此解决方案使用&lt;xsl:key&gt; 进行分组。

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

    请注意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" />
    

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

    【讨论】:

    • 你指的是xml节点中的日期格式吗?如有必要,我可以调整它。现在,它被作为日期/时间字段从数据库存储过程中提取出来,并且可以很容易地格式化为其他内容。但是,此实例需要日期和时间。感谢您的帮助。
    • 我注意到这并没有给出所要求的  原始海报要求的表格单元格中的字符。他可能需要这样做才能使用表格边框。
    • 关于显示空单元格的主题我建议this question,尽管我倾向于在桌子上使用border-collapse: collapse;。无论如何,从 XSLT 中将不存在的属性显示为 &amp;nbsp; 是可能的,但我不知道是否值得通过 CSS 解决它。
    • 我能不能在模板的 部分周围放置一个条件,基于那个特定的值是适当的,如果没有一个 else/otherwise 来显示   ?此外,可能存在超过 2 个不同日期的情况。如何调整它以仅显示最后两个。它们将在 xml 中(来自数据库输出)按降序正确排序。
    • 在处理日期方面的另一种可能性是使用EXSLT date functions,只要您的 XSLT 处理器支持它们。我建议仅使用它们来格式化日期,同时出于显而易见的原因将日期保留在 ISO8601 中的 XML 中。
    猜你喜欢
    • 2017-12-06
    • 1970-01-01
    • 2021-12-05
    • 2018-04-20
    • 1970-01-01
    • 2014-09-02
    • 2015-05-15
    • 1970-01-01
    • 2021-03-14
    相关资源
    最近更新 更多