Wrox Programmer Forums
Go Back   Wrox Programmer Forums > XML > XSLT
|
XSLT General questions and answers about XSLT. For issues strictly specific to the book XSLT 1.1 Programmers Reference, please post to that forum instead.
Welcome to the p2p.wrox.com Forums.

You are currently viewing the XSLT section of the Wrox Programmer to Programmer discussions. This is a community of software programmers and website developers including Wrox book authors and readers. New member registration was closed in 2019. New posts were shut off and the site was archived into this static format as of October 1, 2020. If you require technical support for a Wrox book please contact http://hub.wiley.com
 
Old April 13th, 2010, 06:59 PM
Registered User
 
Join Date: Apr 2010
Posts: 8
Thanks: 4
Thanked 0 Times in 0 Posts
Default Sibling Recursion, concatenating multiple parents.

I made a general post earlier and narrowed my solution down to Sibling recursion. However the examples I have found are a little confusing when I try to translate them to my problem because I am very new to XSLT.

The problem I am having is trying to convert something like this:
Code:
<ROOT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="record.xsd">

  <ELEMENT_A>
    <LEAF_1>99990023</LEAF_1>
    <LEAF_2>247</LEAF_2>
    <LEAF_3>a cough</LEAF_3>
  </ELEMENT_A>
  
  <ELEMENT_B>
    <LEAF_1>100000045</LEAF_1>
    <LEAF_2>175492</LEAF_2>
    <LEAF_3>77619</LEAF_3>
    <LEAF_4>99990023</LEAF_4>
  </ELEMENT_B>
  
  <ELEMENT_B>
    <LEAF_1>100000052</LEAF_1>
    <LEAF_2>175492</LEAF_2>
    <LEAF_3>90948</LEAF_3>
    <LEAF_4>99990023</LEAF_4>
  </ELEMENT_B>
  
  <ELEMENT_B>
    <LEAF_1>100000054</LEAF_1>
    <LEAF_2>175492</LEAF_2>
    <LEAF_3>110626</LEAF_3>
    <LEAF_4>99990023</LEAF_4>
  </ELEMENT_B>
  
  <ELEMENT_C>
    <LEAF_1>100000011</LEAF_1>
    <LEAF_2>175492</LEAF_2>
    <LEAF_3>99990023</LEAF_3>
  </ELEMENT_C>
  
  <ELEMENT_C>
    <LEAF_1>100000011</LEAF_1>
    <LEAF_2>175492</LEAF_2>
    <LEAF_3>99990023</LEAF_3>
  </ELEMENT_C>
  
  <ELEMENT_C>
    <LEAF_1>100000011</LEAF_1>
    <LEAF_2>175492</LEAF_2>
    <LEAF_3>99990023</LEAF_3>
  </ELEMENT_C>
  
</ROOT>
Into:
Code:
<ROOT>
    <ELEMENT_A>
        <Data>
            <z:row LEAF_1="99990023" LEAF_2="247" LEAF_3="a cough"
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
        </Data>
    </ELEMENT_A>
    <ELEMENT_B>
        <Data>
            <z:row LEAF_1="100000045" LEAF_2="175492" LEAF_3="77619"
                LEAF_4="99990023" 
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
            <z:row LEAF_1="100000045" LEAF_2="175492" LEAF_3="77619"
                LEAF_4="99990023" 
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
            <z:row LEAF_1="100000045" LEAF_2="175492" LEAF_3="77619"
                LEAF_4="99990023" 
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
        </Data>
    </ELEMENT_B>
    <ELEMENT_C>
        <Data>
            <z:row LEAF_1="100000011" LEAF_2="175492" LEAF_3="99990023"  
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
            <z:row LEAF_1="100000011" LEAF_2="175492" LEAF_3="99990023"  
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
            <z:row LEAF_1="100000011" LEAF_2="175492" LEAF_3="99990023"  
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
        </Data>
    </ELEMENT_C>
</ROOT>
Im sure if I was a little less "green" at XSLT I would be able to reverse how it was done from the other posts, but I'm still learning so I need something a bit more specific.

I found this post which is almost what I want, however, I am going the opposite direction so I'm not sure what to do.

I also have a script that converts the files originally, I just need to find a way to make it go in the reverse direction.

I tried several things but none of them really worked well because for-each and apply-templates don't process elements independently. I would either end up with ALL attributes in the correct tag, or the correct attributes with the parent tags all wrong.

The thing that makes this problem hard is that I am unable to specify individual elements by name, it has to be done with axes.

In the above example, I am using generic names, there are maybe 20-50 multiple elements so specifying each one does become a problem. Also, we don't actually know if the couple thousand XML files

we made a general schema out of even contains all the possible elements. So i have to find a way to have the script identify what the multiples are then print accordingly.

Here is the script that converts Example 2 to Example 1.
Code:
<?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:z="#RowsetSchema">
        <xsl:output method="xml" omit-xml-declaration="yes"/>
        <xsl:template match="/">
        	<xsl:text disable-output-escaping="yes">&lt;?xml version="1.0"?&gt;</xsl:text>
            <xsl:text disable-output-escaping="yes">&lt;ROOT&gt;</xsl:text>
            <xsl:apply-templates select="//z:row"/>
            <xsl:text disable-output-escaping="yes">&lt;/ROOT&gt;</xsl:text>
        </xsl:template>
        <!-- 
    The "z:row" template will convert all z:row segements into a normal xml file.
        This will replace any ":" in the attribute name and replace it with a "-".

    EXAMPLE:
    <DIAGNOSIS>
        <Data>
            <z:row xmlns:z="#RowsetSchema" 
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" 
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" 
                xmlns:rs="urn:schemas-microsoft-com:rowset" LEAF_1="100055848" 
                LEAF_2="38494" LEAF_3="32568" 
                LEAF_4="99997016" 
                LEAF_5="100042434" 
                LEAF_6="99996294" 
    ......
                rs:forcenull="CREATETIME CREATEDBY CREATEDON TMIP_STATUS"/>            
        </Data>
    </ELEMENT>

    would be converted into:

    <ELEMENT_1>
        <LEAF_1>100055848</LEAF_1>
        <LEAF_2>38494</LEAF_2>
        <LEAF_3>32568</LEAF_3>
        <LEAF_4>99997016</LEAF_4>
        <LEAF_5>100042434</LEAF_5>
        <LEAF_6>99996294</LEAF_6>    
    ......
        <rs:forcenull>CREATETIME CREATEDBY CREATEDON TMIP_STATUS</rs:forcenull>
    </ELEMENT_1>

    -->
        <xsl:template match="z:row">
            <!-- creating the starting node for the parent node -->
            <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
            <xsl:call-template name="globalReplace">
                <xsl:with-param name="outputString" select="name(../..)"/>
                <xsl:with-param name="target" select="':'"/>
                <xsl:with-param name="replacement" select="''-''"/>
            </xsl:call-template>
            <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
            <!-- dumping all the attributes into nodes -->
            <xsl:for-each select="@*">
                <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
                <xsl:call-template name="globalReplace">
                    <xsl:with-param name="outputString" select="name()"/>
                    <xsl:with-param name="target" select="':'"/>
                    <xsl:with-param name="replacement" select="''-''"/>
                </xsl:call-template>
                <!-- writing the attribute xsi:nil="true" if the element is empty-->
				<xsl:choose>
					<xsl:when test="normalize-space(.)">
						<xsl:text disable-output-escaping="yes">&gt;</xsl:text>
						<xsl:value-of select="."/>
					</xsl:when>
					<xsl:otherwise>
						<xsl:text disable-output-escaping="yes">&gt;</xsl:text>
					</xsl:otherwise>
				</xsl:choose>
                <xsl:text disable-output-escaping="yes">&lt;/</xsl:text>
                <xsl:call-template name="globalReplace">
                    <xsl:with-param name="outputString" select="name()"/>
                    <xsl:with-param name="target" select="':'"/>
                    <xsl:with-param name="replacement" select="''-''"/>
                </xsl:call-template>
                <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
            </xsl:for-each>
            <!-- creating the ending node for the parent node -->
            <xsl:text disable-output-escaping="yes">&lt;/</xsl:text>
            <xsl:call-template name="globalReplace">
                <xsl:with-param name="outputString" select="name(../..)"/>
                <xsl:with-param name="target" select="':'"/>
                <xsl:with-param name="replacement" select="''-''"/>
            </xsl:call-template>
            <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
        </xsl:template>
        <!-- find and replace function call -->
        <xsl:template name="globalReplace">
            <xsl:param name="outputString"/>
            <xsl:param name="target"/>
            <xsl:param name="replacement"/>
            <xsl:choose>
                <xsl:when test="contains($outputString,$target)">
                    <xsl:value-of select="concat(substring-before($outputString,$target),
                   $replacement)"/>
                    <xsl:call-template name="globalReplace">
                        <xsl:with-param name="outputString" select="substring-after($outputString,$target)"/>
                        <xsl:with-param name="target" select="$target"/>
                        <xsl:with-param name="replacement" select="$replacement"/>
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$outputString"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    </xsl:stylesheet>
I didn't write this so, but I don understand the way it works... however going the other way seems much more difficult.

This is the code that I have been trying to work on... but after reading all this information on sibling recursion, I think I have been going about this completely wrong... Help!

Im sorry about the lenghtiness of this post and my lack of XSLT understanding. I am working as an intern and 1 week ago was the first time Ive seen XSLT.

Thanks for anything you can help with!
Code:
<xsl:template match="/">
        <xsl:text disable-output-escaping="yes">&lt;ROOT&gt;</xsl:text>
        
        
        
        
        
        <xsl:for-each select="/child::*/*">
            <xsl:choose>
                <xsl:when
                    test="name(current()) != name(following-sibling::*) and name(following-sibling::*)">
                    <!--<xsl:call-template name="nameNodes"> </xsl:call-template>-->
                </xsl:when>
            </xsl:choose>
        </xsl:for-each>
        <xsl:if test="name() = name(following-sibling::*) or name() = name(preceding-sibling::*[1])">
            <xsl:for-each select="/child::*/*">
                <xsl:variable name="count" select="count(following-sibling::*)"/>
                <xsl:value-of select="$count"/>
                <xsl:if test="$count > 1 and $count &lt; 3">
                <xsl:call-template name="other"/>
                </xsl:if>
            </xsl:for-each>
        </xsl:if>

        <xsl:text disable-output-escaping="yes">&lt;/ROOT&gt;</xsl:text>
    </xsl:template>




    <xsl:template name="nameNodes">
        <xsl:variable name="parentName" select="name(/child::*/*/*)"/>
        <xsl:variable name="tags" select="name(current())"/>
        <xsl:if test="$tags != $parentName">
            <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
            <xsl:value-of select="$tags"/>
            <xsl:text disable-output-escaping="yes">&gt;&lt;Data&gt; &lt;z:row </xsl:text>
            <!--adding attributes after start tags-->
            <xsl:call-template name="addAttributes"/>
            <xsl:text disable-output-escaping="yes">&lt;/</xsl:text>
            <xsl:value-of select="$tags"/>
            <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
        </xsl:if>
    </xsl:template>


    <xsl:template name="other">
        <xsl:call-template name="startTag"/>
        <xsl:for-each select="/child::*/*[name() = name(following-sibling::*)]">
          <!--  <xsl:if
                test="name() = name(following-sibling::*) or name() = name(preceding-sibling::*[1])">
                <xsl:for-each select="self::*">
                    <xsl:if
                        test="name() = name(preceding-sibling::*[1]) or name() = name(following-sibling::*)">
                        <xsl:text disable-output-escaping="yes">&lt;z:row </xsl:text>
                        <xsl:for-each select="child::*[name() = name(following-sibling::*)]">
                            <xsl:call-template name="globalReplace">
                                <xsl:with-param name="outputString" select="name()"/>
                                <xsl:with-param name="target" select="':'"/>
                                <xsl:with-param name="replacement" select="''-''"/>
                            </xsl:call-template>
                            <xsl:choose>
                                <xsl:when test="normalize-space(.)">
                                    <xsl:text disable-output-escaping="yes">="</xsl:text>
                                    <xsl:value-of select="."/>
                                </xsl:when>
                                <xsl:otherwise>
                                    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
                                </xsl:otherwise>
                            </xsl:choose>
                            <xsl:text disable-output-escaping="yes">" </xsl:text>
                        </xsl:for-each>
                        <xsl:text disable-output-escaping="yes">xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema" /&gt;</xsl:text>
                    </xsl:if>
                </xsl:for-each>
            </xsl:if>-->
        </xsl:for-each>
        <xsl:call-template name="endTag"/>
    </xsl:template>


    <xsl:template name="startTag">
        <xsl:for-each select="/child::*/*[name() = name(following-sibling::*[2])]">
            <xsl:value-of select="name(following-sibling::*[2])"/>
            <xsl:if
                test="name() = name(following-sibling::*) and name() = name(following-sibling::*[2])">
                <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
                <xsl:value-of select="name()"/>
                <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;Data&gt; </xsl:text>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>


    <xsl:template name="endTag">
        <xsl:for-each select="/child::*/*">
            <xsl:if
                test="name() = name(preceding-sibling::*[1]) and name() != name(following-sibling::*)">
                <xsl:text disable-output-escaping="yes">&lt;/Data&gt;</xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;/</xsl:text>
                <xsl:value-of select="name()"/>
                <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>



    <xsl:template name="addAttributes">
        <xsl:for-each select="child::*">
            <xsl:call-template name="globalReplace">
                <xsl:with-param name="outputString" select="name()"/>
                <xsl:with-param name="target" select="':'"/>
                <xsl:with-param name="replacement" select="''-''"/>
                <xsl:variable name="count" select="count(child::*/*)"/>
            </xsl:call-template>
            <!--adds the proper syntax for attributes-->
            <xsl:choose>
                <xsl:when test="normalize-space(.)">
                    <xsl:text disable-output-escaping="yes">="</xsl:text>
                    <xsl:value-of select="."/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:text disable-output-escaping="yes">=" </xsl:text>
                </xsl:otherwise>
            </xsl:choose>
            <xsl:text disable-output-escaping="yes">" </xsl:text>
        </xsl:for-each>
        <!--for every child of the root document, adds an end tag, in this case created end tags for z:row-->
        <xsl:for-each select="/child::*">
            <xsl:text disable-output-escaping="yes">xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"</xsl:text>
            <xsl:text disable-output-escaping="yes">/&gt;&lt;/Data&gt;</xsl:text>
        </xsl:for-each>
    </xsl:template>


    <!-- find and replace function call -->
    <xsl:template name="globalReplace">
        <xsl:param name="outputString"/>
        <xsl:param name="target"/>
        <xsl:param name="replacement"/>
        <xsl:choose>
            <xsl:when test="contains($outputString,$target)">
                <xsl:value-of
                    select="concat(substring-before($outputString,$target),
                    $replacement)"/>
                <xsl:call-template name="globalReplace">
                    <xsl:with-param name="outputString"
                        select="substring-after($outputString,$target)"/>
                    <xsl:with-param name="target" select="$target"/>
                    <xsl:with-param name="replacement" select="$replacement"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$outputString"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>


***EDIT: Also, I am not limited to XSLT 1.0, would this be easier in 2.0?

*****EDIT: Simplified Code. The elements can be any values, I need to be able to specify element_B multiples from element_C multiples.

Last edited by FrozenGrapes; April 14th, 2010 at 03:28 PM..
 
Old April 14th, 2010, 07:20 AM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

It looks as if you simply want to transform some elements names and some leaf elements to attributes. The following does that:
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  
  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>
  
  <xsl:template match="altha-t-record">
    <AHLTA-T-RECORD>
      <SYMPTOMS>
        <Data>
          <xsl:apply-templates select="SYMPTOMS"/>
        </Data>
      </SYMPTOMS>
      <ENC_SECTIONS>
        <Data>
          <xsl:apply-templates select="ENC_SECTIONS"/>
        </Data>
      </ENC_SECTIONS>
      <ENCOUNTERS>
        <Data>
          <xsl:apply-templates select="ENCOUNTERS"/>
        </Data>
      </ENCOUNTERS>
    </AHLTA-T-RECORD>
  </xsl:template>
  
  <xsl:template match="SYMPTOMS | ENC_SECTIONS | ENCOUNTERS">
    <z:row xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
      <xsl:apply-templates/>
    </z:row>
  </xsl:template>
  
  <xsl:template match="SYMPTOMS/* | ENC_SECTIONS/* | ENCOUNTERS/*">
    <xsl:attribute name="{local-name()}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>

</xsl:stylesheet>
When applied to the input you posted, the output is as follows:
Code:
<?xml version="1.0" encoding="utf-8"?>
<AHLTA-T-RECORD>
   <SYMPTOMS>
      <Data>
         <z:row xmlns:z="#RowsetSchema" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" EncounterNumber="99990023" MedcinID="247" MedcinDesc="a cough"/>
      </Data>
   </SYMPTOMS>
   <ENC_SECTIONS>
      <Data>
         <z:row xmlns:z="#RowsetSchema" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" DATAID="100000045" FACILITYNCID="175492" ENC_SECTIONSINDEX="77619" ENCOUNTERNUMBER="99990023"/>
         <z:row xmlns:z="#RowsetSchema" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" DATAID="100000052" FACILITYNCID="175492" ENC_SECTIONSINDEX="90948" ENCOUNTERNUMBER="99990023"/>
         <z:row xmlns:z="#RowsetSchema" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" DATAID="100000054" FACILITYNCID="175492" ENC_SECTIONSINDEX="110626" ENCOUNTERNUMBER="99990023"/>
      </Data>
   </ENC_SECTIONS>
   <ENCOUNTERS>
      <Data>
         <z:row xmlns:z="#RowsetSchema" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" PRIMARYPROVIDERNCID="100000011" FACILITYNCID="175492" ENCOUNTERNUMBER="99990023"/>
         <z:row xmlns:z="#RowsetSchema" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" PRIMARYPROVIDERNCID="100000011" FACILITYNCID="175492" ENCOUNTERNUMBER="99990023"/>
         <z:row xmlns:z="#RowsetSchema" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" PRIMARYPROVIDERNCID="100000011" FACILITYNCID="175492" ENCOUNTERNUMBER="99990023"/>
      </Data>
   </ENCOUNTERS>
</AHLTA-T-RECORD>
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
The Following User Says Thank You to Martin Honnen For This Useful Post:
FrozenGrapes (April 14th, 2010)
 
Old April 14th, 2010, 02:12 PM
Registered User
 
Join Date: Apr 2010
Posts: 8
Thanks: 4
Thanked 0 Times in 0 Posts
Default

Well, I look stupid now, I forgot to mention some important information.

That would work really well if i could use the template match, the problem is, that those element names are generic and there could be any number of them. Is there anyway to generally specify the multiple elements?
The thing that makes this problem hard is that I am unable to specify individual elements by name, it has to be done with axes.

Edited original post.

Thanks Martin

Last edited by FrozenGrapes; April 14th, 2010 at 03:30 PM..
 
Old April 14th, 2010, 02:48 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

I must admit I (correctly?) assumed your element names were not fixed. But it shows how difficult it is to specify your problem clearly and unambiguously. We always ask for an example of your source document, but actually what we need is a description of the set of possible source documents.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
 
Old April 14th, 2010, 04:38 PM
Registered User
 
Join Date: Apr 2010
Posts: 8
Thanks: 4
Thanked 0 Times in 0 Posts
Default

Okay, I'll describe my problem.

I get server dumps in XML that look something like this:
Code:
<RECORD>
    <INPUT>
        <Data>
            <z:row LOCATION="5" MEID="247" MEDESC="a cough"
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
        </Data>
    </INPUT>
    <SECTIONS>
        <Data>
            <z:row DATAID="100000045" NFID="175492" INDEX="77619"
                LOCATION="5" 
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
            <z:row DATAID="100000045" NFID="175492" INDEX="77619"
                LOCATION="5" 
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
            <z:row DATAID="100000045" NFID="175492" INDEX="77619"
                LOCATION="5" 
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
        </Data>
    </SECTIONS>
    <OUT_SECTION>
        <Data>
            <z:row NFID="1000" ZDID="175492" PLACEMENT="9999"  LOCATION="5"
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
            <z:row NFID="1001" ZDCID="175492" PLACEMENT="9999"  LOCATION="5"
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
            <z:row NFID="1002" ZDID="175492" PLACEMENT="9999"  LOCATION="5"
                xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"/>
        </Data>
    </OUT_SECTION>
</RECORD>
This can be several times longer and can contain any number of elements each appearing once to many times depending on the dump.

I then use the aforementioned script to convert the files into something like this:
Code:
<?xml version="1.0"?>
<RECORD>
    <INPUT>
        <LOCATION>5</LOCATION>
        <MEID>247</MEID>
        <MEDESC>a cough</MEDESC>
    </INPUT>
    <SECTIONS>
        <DATAID>100000045</DATAID>
        <NFID>175492</NFID>
        <INDEX>77619</INDEX>
        <LOCATION>5</LOCATION>
    </SECTIONS>
    <SECTIONS>
        <DATAID>100000045</DATAID>
        <NFID>175492</NFID>
        <INDEX>77619</INDEX>
        <LOCATION>5</LOCATION>
    </SECTIONS>
    <SECTIONS>
        <DATAID>100000045</DATAID>
        <NFID>175492</NFID>
        <INDEX>77619</INDEX>
        <LOCATION>5</LOCATION>
    </SECTIONS>
    <OUT_SECTION>
        <NFID>1000</NFID>
        <ZDID>175492</ZDID>
        <PLACEMENT>9999</PLACEMENT>
        <LOCATION>5</LOCATION>
    </OUT_SECTION>
    <OUT_SECTION>
        <NFID>1001</NFID>
        <ZDCID>175492</ZDCID>
        <PLACEMENT>9999</PLACEMENT>
        <LOCATION>5</LOCATION>
    </OUT_SECTION>
    <OUT_SECTION>
        <NFID>1002</NFID>
        <ZDID>175492</ZDID>
        <PLACEMENT>9999</PLACEMENT>
        <LOCATION>5</LOCATION>
    </OUT_SECTION>
</RECORD>
That way the data can be clearly edited and displayed, then we need to feed the data back to the server in the original format. Because the original files can contain any number of multiple elements, some of which may not even show up on my XML schema, the script I need has to be able to handle generic elements and if there is an element appearing only once, it just reverts the changes made by the script and all the data gets put in one element with one z:row. However, I am having trouble with the elements that appear multiple times. The script must recognize that an element does occur multiple times and place all leaf data into a z:row under one element, with each z:row in that element corresponding to one of the multiple elements being reverted. (I hope that makes half a bit of sense)

If i can further clarify anything I will try my best.

Last edited by FrozenGrapes; April 14th, 2010 at 04:41 PM..
 
Old April 14th, 2010, 05:48 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

Well, as I think I mentioned before (though I can't now find the posting), by far the easiest way of doing this is using XSLT 2.0:

Code:
<xsl:for-each-group select="*" group-adjacent="node-name()">
Early on in this thread, you asked: "I am not limited to XSLT 1.0, would this be easier in 2.0?", and the answer is yes, definitely.

The solution using sibling recursion isn't actually very long or complex, it just needs a very clear head to write it. From the parent element, you do apply-templates select="*[1]" to process the first child. For the child element, you need two template rules something like this:

Code:
<xsl:template match="RECORD/*"
  <group>
    <xsl:copy-of select="."/>
    <xsl:apply-templates select="following-sibling::*[1][name() = name(current())]" mode="rest-of-group"/>
  </group>
  <xsl:apply-templates select="following-sibling::[name() != name(current())][1]"/>
</xsl:template>

<xsl:template match="RECORD/*" mode="rest-of-group">
  <xsl:copy-of select="."/>
  <xsl:apply-templates select="following-sibling::*[1][name() = name(current())]" mode="rest-of-group"/>
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
The Following User Says Thank You to mhkay For This Useful Post:
FrozenGrapes (April 16th, 2010)
 
Old April 15th, 2010, 06:36 AM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

Just to clarify, do you want to group adjacent elements by their name or simply all siblings by their name? If you have e.g.
Code:
<root>
  <a>...</a>
  <a>...</a>
  <b>...</b>
  <b>...</b>
  <a>...</a>
  <a>...</a>
</root>
do you want to have two groups of 'a' elements in the output e.g.
Code:
<root>
  <a>
    <data>
       <row .../>
       <row .../>
    </data>
  </a>
  <b>
     <data>
        <row .../>
        <row .../>
     </data>
   </b>
  <a>
    <data>
       <row .../>
       <row .../>
    </data>
  </a> 
</root>
or just one group e.g.
Code:
<root>
  <a>
    <data>
       <row .../>
       <row .../>
       <row .../>
        <row .../>
    </data>
  </a>
  <b>
     <data>
        <row .../>
        <row .../>
     </data>
   </b>
</root>
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
 
Old April 15th, 2010, 08:47 PM
Registered User
 
Join Date: Apr 2010
Posts: 8
Thanks: 4
Thanked 0 Times in 0 Posts
Default

The second example you posted is what I'm looking for.
 
Old April 16th, 2010, 07:16 AM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

Then with XSLT 2.0 you can simply use for-each-group select="*" group-by="node-name(.)":
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">
  
  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="RECORD">
    <xsl:copy>
      <xsl:for-each-group select="*" group-by="node-name(.)">
        <xsl:element name="{current-grouping-key()}">
          <Data>
            <xsl:apply-templates select="current-group()"/>
          </Data>
        </xsl:element>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="RECORD/*">
    <z:row xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
      <xsl:apply-templates select="*"/>
    </z:row>
  </xsl:template>
  
  <xsl:template match="RECORD/*/*">
    <xsl:attribute name="{name()}" select="."/>
  </xsl:template>

</xsl:stylesheet>
With XSLT 1.0 you don't need sibling recursion, it suffices to use Muenchian grouping:
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  
  <xsl:output method="xml" indent="yes"/>
  
  <xsl:key name="k1" match="RECORD/*" use="concat('{', namespace-uri(), '}', local-name())"/>
  
  <xsl:template match="RECORD">
    <xsl:copy>
      <xsl:for-each select="*[generate-id() = generate-id(key('k1', concat('{', namespace-uri(), '}', local-name()))[1])]">
        <xsl:element name="{name()}">
          <Data>
            <xsl:apply-templates select="key('k1', concat('{', namespace-uri(), '}', local-name()))"/>
          </Data>
        </xsl:element>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="RECORD/*">
    <z:row xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
                xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
      <xsl:apply-templates select="*"/>
    </z:row>
  </xsl:template>
  
  <xsl:template match="RECORD/*/*">
    <xsl:attribute name="{name()}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>

</xsl:stylesheet>
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
The Following User Says Thank You to Martin Honnen For This Useful Post:
FrozenGrapes (April 16th, 2010)
 
Old April 16th, 2010, 07:23 PM
Registered User
 
Join Date: Apr 2010
Posts: 8
Thanks: 4
Thanked 0 Times in 0 Posts
Default

Wow Martin, and Michael thank you so much. I never would have thought to do it like that, but it makes so much more sense. Thank you guys for your help.

Martin, after looking at the 1.0 code, im still trying to figure it out, could you explain how this line works in conjunction with the Key K1?
Code:
 <xsl:for-each select="*[generate-id() = generate-id(key('k1', concat('{', namespace-uri(), '}', local-name()))[1])]">
Thanks in advance.





Similar Threads
Thread Thread Starter Forum Replies Last Post
Simplifying XML parents [XSLT] FrozenGrapes XSLT 2 April 13th, 2010 06:02 PM
SIBLING RECURSION - REVISITED pallone XSLT 7 December 4th, 2009 11:25 AM
Want to Pull multiple following-sibling data to preceding-sibling element sameer_kadam XSLT 4 May 9th, 2009 08:07 AM
having issue printing child values by comparing parents value eruditionist XSLT 4 January 7th, 2009 02:34 AM
MDI Sibling calling Sibling... Nick.Net VB.NET 2002/2003 Basics 1 December 8th, 2003 09:23 PM





Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
Copyright (c) 2020 John Wiley & Sons, Inc.