4

I have an xml datasource which looks like this:

<dsQueryResponse>
  <Department>
    <Rows>
      <Row dept="HR" state="NY" region="East"/>
      <Row dept="HR" state="NJ" region="East"/>
      <Row dept="SD" state="NY" region="East"/>
      <Row dept="MM" state="NY" region="East"/>
      <Row dept="SD" state="NJ" region="East"/>
      <Row dept="SD" state="CO" region="West"/>
      <Row dept="MM" state="CO" region="West"/>
    </Rows>
  </Department>
</dsQueryResponse>

My XSLT looks like this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="html" indent="no"/>
  <xsl:decimal-format NaN=""/>
  <xsl:param name="DeptQS">East</xsl:param>
  <xsl:variable name="deptRows" select="/dsQueryResponse/Department/Rows/Row[@region = $DeptQS]"/>

  <xsl:template match="/">
    <xsl:if test="count($deptRows) &gt; 0">
      <xsl:call-template name="deptList"/>
    </xsl:if>
  </xsl:template>

  <xsl:template name="deptList">
    <xsl:for-each select="$deptRows">
      <!-- process unique depts--> 
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

I want to get all the departments in the region written out. The code I have now will write out duplicate departments. But how can I write out the department for the region only once?

Thanks!

1
  • Please provide the actual and expected result. Otherwise it is hard to understand what you need exactly. Commented May 7, 2014 at 15:23

4 Answers 4

4

You haven't shown your desired output. But in XSLT 2.0, you could do something like this:

<xsl:template match="Rows">
  <xsl:for-each-group select="Row" group-by="@region">
    <region name="{current-grouping-key()}">
      <xsl:for-each-group select="current-group()" group-by="@dept">
        <dept name="{current-grouping-key()}">
          <xsl:for-each select="current-group()">
            <state name="{@state}"/>
          </xsl:for-each>
        </dept>
      </xsl:for-each-group>
    </region>
  </xsl:for-each>
</xsl:template>
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for editing it, Simon. I use </ to end tags in an XML editor with auto-completion, so I tend to use it as a shorthand when writing "sketchy" XML in notes such as this; it's a signal to the reader that they should apply some brain cycles to it rather than just pasting the code and expecting it to work out of the box.
2

For XSLT 1.0 you can use the "Muenchian Method".

You would create a key to index the Row elements by a combination of @region and @dept values.

Then you would get the first occurrence of that region/department combination (that has the wanted region ($DeptQS)) and output the department (dept).

Here's an example stylesheet outputting <test> elements to show the context:

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

  <xsl:key name="kDept" match="Department/Rows/Row" use="concat(@region,'|',@dept)"/>
  <xsl:param name="DeptQS">East</xsl:param>

  <xsl:template match="/*">
    <xsl:for-each select="Department/Rows/Row[count(.|key('kDept',
      concat($DeptQS,'|',@dept))[1])=1]">
      <test>Current dept: <xsl:value-of select="@dept"/></test>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

Here's the output using your input XML:

<test>Current dept: HR</test>
<test>Current dept: SD</test>
<test>Current dept: MM</test>

2 Comments

Variable/parameter references are not valid in XSLT 1.0 patterns like xsl:key/@match
You can't use var/param reference in XSLT 1.0 patterns, but you can in XSLT 2.0. That changes the whole problem, because in XSLT 1.0 you must use push style. Your answer use pull style and param reference in patterns. This is not an XSLT 1.0 solution as your first sentence seems to claim.
2

how can I write out the department for the region only once?

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:param name="pRegion" select="'East'"/>
    <xsl:key name="kRowByDept-Region" match="Row"
             use="concat(@dept,'++',@region)"/>
    <xsl:template match="Rows">
        <xsl:apply-templates select="Row[generate-id()
                                          = generate-id(
                                               key('kRowByDept-Region',
                                                   concat(@dept,'++',$pRegion)
                                               )[1]
                                            )
                                     ]"/>
    </xsl:template>
    <xsl:template match="Row">
        <xsl:value-of select="concat(@dept,'&#xA;')"/>
    </xsl:template>
</xsl:stylesheet>

Output:

HR
SD
MM

Comments

1

The following stylesheet shows a general approach to grouping at multiple levels:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="byRegion" match="Row" use="@region" />
    <xsl:key name="byRegionState" 
             match="Row" use="concat(@region, '|', @state)" />
    <xsl:key name="byRegionStateDept" 
             match="Row" use="concat(@region, '|', @state, '|', @dept)" />
    <xsl:template 
         match="Row[generate-id() = generate-id(key('byRegion', @region)[1])]">
        <region name="{@region}">
            <xsl:apply-templates
                select="key('byRegion', @region)
                         [generate-id() =
                          generate-id(key('byRegionState',
                                      concat(@region, '|', @state))[1])]"
                mode="states" />
        </region>
    </xsl:template>
    <xsl:template match="Row" mode="states">
        <state name="{@state}">
            <xsl:apply-templates
                select="key('byRegionState', concat(@region, '|', @state))
                         [generate-id() =
                          generate-id(key('byRegionStateDept',
                                concat(@region, '|', @state, '|', @dept))[1])]"
                mode="dept" />
        </state>
    </xsl:template>
    <xsl:template match="Row" mode="dept">
        <dept><xsl:value-of select="@dept" /></dept>
    </xsl:template>
</xsl:stylesheet>

It produces the following output on your input:

<region name="East">
    <state name="NY">
        <dept>HR</dept>
        <dept>SD</dept>
        <dept>MM</dept>
    </state>
    <state name="NJ">
        <dept>HR</dept>
        <dept>SD</dept>
    </state>
</region>
<region name="West">
    <state name="CO">
        <dept>SD</dept>
        <dept>MM</dept>
    </state>
</region>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.