Dschmied
Dschmied

Reputation: 15

XSLT Grouping Specific Siblings

I'm working in XSLT 1.0 right now, and I'm having trouble grouping correctly.My xml cannot be changed and it looks like:

<Analysis>
  <comment>Test 1</comment>
  <Action>
    <Result>Passed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <comment>Start 2nd Phase</comment>
  <Action>
    <Result>Failed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <comment>Test 2</comment>
  <Action>
    <Result>Failed</Result>
  </Action>
  <Action>
    <Result>Failed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <comment>Start 2nd Phase</comment>
  <Action>
    <Result>Failed</Result>
  </Action>
  <comment>Start 3rd Phase</comment>
  <Action>
    <Result>Failed</Result>
  </Action>
  <comment>Test 3</comment>
  <Action>
    <Result>Passed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <comment>Test 4</comment>
  <Action>
    <Result>Failed</Result>
  </Action>
  <Action>
    <Result>Failed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
  <comment>Start 2nd Phase</comment>
  <Action>
    <Result>Failed</Result>
  </Action>
  <Action>
    <Result>Passed</Result>
  </Action>
</Analysis>

The code I currently have will print a Failed if it finds a failed underneath the Test, but it only does it once. However, right now it does it underneath each comment, not each comment that has a "Test" in it.

<xsl:key name="actions" match="Action" use="generate-id(preceding-sibling::comment[1])" />

<xsl:template match="/Analysis">
    <table>
        <xsl:for-each select="comment">
            <tr>
                <td>
                    <xsl:if test="starts-with(text(),'Test')">
                        <xsl:value-of select="."/>
                    </xsl:if>
                </td>
            </tr>   
            <xsl:if test="key('actions', generate-id())[Result='Failed']">
                <tr>
                    <td>Failed</td>
                </tr>
            </xsl:if>
        </xsl:for-each>
    </table>
</xsl:template>

Is there a way to make "preceding-sibling::comment[1]" work only for comments that start with "Test" or contain "Test"? There are a different number of non-important comments that are interspersed with the important information. Like with the example above, I want to see an output that would be like

Test 1
Failed
Test 2
Failed
Test 3
Test 4
Failed

However, with the code I currently have I get

Test 1  
Failed 
Test 2 
Failed 
Failed 
Failed 
Test 3 
Test 4 
Failed 
Failed 

Any help would be appreciated.

Upvotes: 1

Views: 69

Answers (1)

Daniel Haley
Daniel Haley

Reputation: 52858

You should be able to add the predicate directly to the xsl:key...

<xsl:key 
  name="actions" 
  match="Action" 
  use="generate-id(preceding-sibling::comment[starts-with(normalize-space(),'Test')][1])" 
/>

I'd also remove the xsl:if and put the test on the xsl:for-each. That way you don't get blank rows for the comments you want to ignore.

Full example...

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

  <xsl:key 
    name="actions" 
    match="Action" 
    use="generate-id(preceding-sibling::comment[starts-with(normalize-space(),'Test')][1])" 
  />

  <xsl:template match="/Analysis">
    <table>
      <xsl:for-each select="comment[starts-with(normalize-space(),'Test')]">
        <tr>
          <td>
            <xsl:value-of select="."/>
          </td>
        </tr>
        <xsl:if test="key('actions', generate-id())[Result='Failed']">
          <tr>
            <td>Failed</td>
          </tr>
        </xsl:if>
      </xsl:for-each>
    </table>
  </xsl:template>

</xsl:stylesheet>

Upvotes: 1

Related Questions