[xsl] Identity Transform Grouping Question

Subject: [xsl] Identity Transform Grouping Question
From: <ethan.kalfus@xxxxxxx>
Date: Wed, 13 Oct 2004 11:44:46 -0400
   I have an identity transform that attempts to group elements at a deeply
nested level, but I am having trouble accomplishing this.  The xslt performs
the identity transform and sorts the result tree on a few levels, all
successfully.  The problem occurs when I attempt the deep-level grouping.

Given the following source tree sample:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="offices.xsl" ?>
<locations version="1.0">
   <divisions>
      <division id="B">
         <regions>
            <region name="europe">
               <countries>
                  <country name="England">
                     <offices>
                        <office id="BR4">
                           <names>
                              <name lang='en'>Branch 4</name>
                           </names>
                           <address>
                              <line>26 Abbey Lane</line>
                              <line>London</line>
                           </address>
                           <cities>
                              <city>London</city>
                           </cities>
                           <phone>+44-22-2222 2222</phone>
                        </office>
                        <office id="BR5">
                           <names>
                              <name lang='en'>AAAAA Branch 4</name>
                           </names>
                           <address>
                              <line>7 Kings Highway</line>
                              <line>London</line>
                           </address>
                           <cities>
                              <city>London</city>
                           </cities>
                           <phone>+44-99-9999 9999</phone>
                        </office>
                        <office id="BR6">
                           <names>
                              <name lang='en'>Branch 4</name>
                           </names>
                           <address>
                              <line>22 Abbey Lane</line>
                              <line>London</line>
                           </address>
                           <cities>
                              <city>London</city>
                           </cities>
                           <phone>+44-55-555 5555</phone>
                        </office>
                     </offices>
                  </country>
               </countries>
            </region>
         </regions>
      </division>
   </divisions>
</locations>

...the xslt should produce the following result tree:

<?xml version="1.0" encoding="UTF-8"?>
<locations>
   <divisions>
      <division id="B">
         <regions>
            <region name="europe">
               <countries>
                  <country name="England">
                     <cities>
                        <city>
                           <name>London</name>
                           <offices>
                              <office>
                                 <names>
                                    <name lang='en'>AAAAA Branch 4</name>
                                 </names>
                                 <location>
                                   <address>
                                       <line>7 Kings Highway</line>
                                       <line>London</line>
                                    </address>
                                    <phone>+44-99-9999 9999</phone>
                                 </location>
                              </office>
                              <office>
                                 <names>
                                    <name lang='en'>Branch 4</name>
                                 </names>
                                 <location>
                                    <address>
                                       <line>22 Abbey Lane</line>
                                       <line>London</line>
                                    </address>
                                    <phone>+44-55-555 5555</phone>
                                 </location>
                                 <location>
                                    <address>
                                       <line>26 Abbey Lane</line>
                                       <line>London</line>
                                    </address>
                                    <phone>+44-22-2222 2222</phone>
                                 </location>
                              </office>
                           </offices>
                        </city>
                     </cities>
                  </country>
               </countries>
            </region>
         </regions>
      </division>
   </divisions>
</locations>

  The <office> elements contain all child elements where the
office/names/name[@lang='en'] values match.  Further, a new element --
<location> has been added as a child to <office>.  The goal is to have only
one <names> element under an <office> element for each unique occurrence of
names/name where @lang='en' -- in other words, group all office locations by
their common English office name, then copy all location-specific information
for each office to the result tree as new <location> elements underneath the
<office> element.

  My current XSLT is as follows:

<?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="xml" encoding="UTF-8" indent="yes"/>
<xsl:key name="by-city" match="temp/office/cities/city" use="."/>

<xsl:template match="/">
   <locations>
      <divisions>
         <xsl:for-each select="locations/divisions/division">
            <xsl:sort select="@id"/>
            <division id="{@id}">
               <regions>
                  <xsl:for-each select="regions/region">
                     <xsl:sort select="@name"/>
                     <region name="{@name}">
                        <countries>
                           <xsl:for-each select="countries/country">
                              <xsl:sort select="@name"/>
                              <country name="{@name}">
                                 <xsl:variable name="rtf">
                                    <temp>
                                       <xsl:copy-of select="offices/office"/>
                                    </temp>
                                 </xsl:variable>
                                 <xsl:for-each
select="msxsl:node-set($rtf)/temp/office/cities/city[generate-id(.) =
generate-id(key('by-city', .)[1])]">
                                    <xsl:sort select="."/>
                                    <cities>
                                       <city>
                                          <name>
                                             <xsl:value-of select="."/>
                                          </name>
                                          <offices>
                                                <xsl:for-each
select="key('by-city', .)">
                                                   <xsl:sort
select="../../names/name[@lang='en']"/>
                                                   <xsl:sort
select="../../address"/>
                                                <xsl:if
test="../../names/name[@lang='en']
!=../../preceding-sibling::names/name[@lang='en']">
                                                <!-- <office>  new <office>
element should be inserted here -->
                                                      <xsl:copy-of
select="../../names"/>
                                                </xsl:if>
                                                   <location>
                                                      <xsl:copy-of
select="../preceding-sibling::address"/>
                                                      <xsl:copy-of
select="../following-sibling::phone"/>
                                                   </location>
                                                <!-- </office>  if the current
node is the last in a group with the same /names/name[@lang='en'] value,
                                                                    then
closing tag should be inserted here -->
                                                </xsl:for-each>
                                          </offices>
                                       </city>
                                    </cities>
                                 </xsl:for-each>
                              </country>
                           </xsl:for-each>
                        </countries>
                     </region>
                  </xsl:for-each>
               </regions>
            </division>
         </xsl:for-each>
      </divisions>
   </locations>
</xsl:template>
</xsl:stylesheet>

...and my result tree looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<locations>
   <divisions>
      <division id="B">
         <regions>
            <region name="europe">
               <countries>
                  <country name="England">
                     <cities>
                        <city>
                           <name>London</name>
                           <offices>
                              <location>
                                 <address>
                                    <line>7 Kings Highway</line>
                                    <line>London</line>
                                 </address>
                                 <phone>+44-99-9999 9999</phone>
                              </location>
                              <location>
                                 <address>
                                    <line>22 Abbey Lane</line>
                                    <line>London</line>
                                 </address>
                                 <phone>+44-55-555 5555</phone>
                              </location>
                              <location>
                                 <address>
                                    <line>26 Abbey Lane</line>
                                    <line>London</line>
                                 </address>
                                 <phone>+44-22-2222 2222</phone>
                              </location>
                           </offices>
                        </city>
                     </cities>
                  </country>
               </countries>
            </region>
         </regions>
      </division>
   </divisions>
</locations>

   The missing pieces are unique <names> elements (selected according to the
above-specified criteria) and the appropriate placement of <office> elements
(encapsulating <names> and <location> elements).

   I appreciate any help that can be offered!


Visit our website at http://www.ubs.com

This message contains confidential information and is intended only
for the individual named.  If you are not the named addressee you
should not disseminate, distribute or copy this e-mail.  Please
notify the sender immediately by e-mail if you have received this
e-mail by mistake and delete this e-mail from your system.

E-mail transmission cannot be guaranteed to be secure or error-free
as information could be intercepted, corrupted, lost, destroyed,
arrive late or incomplete, or contain viruses.  The sender therefore
does not accept liability for any errors or omissions in the contents
of this message which arise as a result of e-mail transmission.  If
verification is required please request a hard-copy version.  This
message is provided for informational purposes and should not be
construed as a solicitation or offer to buy or sell any securities or
related financial instruments.

Current Thread