Hello everyone! I just had a quick question about XSLT. I have a large xml file with many <DIMENSION_Id> nodes nested inside each other. In each <DIMENSION_Id> node there are two SYN tags: <SYN>String</SYN><SYN>Integer</SYN> What I am trying to do is take the furthest child node of each DIMENSION_Id and connect it with all of its ancestor paths to create a URL.

i.e.

<DIMENSION_Id>
  <SYN>Text</SYN>
  <SYN>Number</SYN
       <DIMENSION_Id>
             <SYN>More Text</SYN>
             <SYN>Another Number</SYN>
       </DIMENSION_Id>
</DIMENSION_Id>

I wrote this XSLT to get all information from the parent nodes first, then the child node last to create a full URL. Unfortunately it only gives me the information of the furthest child node...I do not know how to append any other text to it. (it should read something like: furthest-parent/closer-parent/parent/item_selected)

Unfortunately all it does is give me the value of the current node.... Here is the XSLT that I wrote:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/DIMENSION_NODE">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="@SYN" /><xsl:text>/</xsl:text> <!--<xsl:value-of select="." /> -->
<xsl:value-of select="@SYN" /><xsl:text>/</xsl:text> <!-- <xsl:value-of select="." /> -->
<xsl:text>woot!</xsl:text> -->
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Thanks in advance for your help!

Recommended Answers

All 6 Replies

Here's a solution. This is a situation where a recursive call template works pretty well. The basic idea is to start at the deepest level of "DIMENSION_Id" and work you way up the tree. So the call template is invoked with the deepest context, then it calls itself with the parent context, and so on until it reaches the root document node.

As it's doing this it is continuously building the string that is your URL. Feel free to tweak it as needed. If you don't want to start at the deepest, just call it with whatever context you want to start at. Keep in mind this solution is strictly for a structure of elements that are nested within themselves.

Input Document

<DIMENSION_Id>
    <SYN>1Text</SYN>
    <SYN>1</SYN>
    <DIMENSION_Id>
        <SYN>2Text</SYN>
        <SYN>2</SYN>
        <DIMENSION_Id>
            <SYN>3Text</SYN>
            <SYN>3</SYN>
            <DIMENSION_Id>
                <SYN>4Text</SYN>
                <SYN>4</SYN>
                <DIMENSION_Id>
                    <SYN>5Text</SYN>
                    <SYN>5</SYN>
                </DIMENSION_Id>
            </DIMENSION_Id>
        </DIMENSION_Id>
    </DIMENSION_Id>
</DIMENSION_Id>

Here's the transformation.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
        <xsl:call-template name="getPath">
            <!-- This XPath gets the "deepest" DIMENSION_Id element node in the tree -->
            <xsl:with-param name="currentnode" select="//DIMENSION_Id[count(ancestor::*) = max(count(//DIMENSION_Id/ancestor::*))]"/>
        </xsl:call-template>
    </xsl:template>


    <xsl:template name="getPath">
        <xsl:param name="currentnode"/>
        <xsl:param name="currenttext" select="''" />
        <xsl:choose>
            <xsl:when test="count($currentnode/ancestor::*) = 0">
                <xsl:value-of select="concat($currentnode/SYN[1],$currenttext)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="gettext" >
                    <xsl:text>/</xsl:text>
                    <xsl:value-of select="$currentnode/SYN[1]"/>
                </xsl:variable>                    
                <xsl:call-template name="getPath" >
                    <xsl:with-param name="currentnode"  select="$currentnode/.." />
                    <xsl:with-param name="currenttext" select="concat($gettext,$currenttext)" />
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

This is the output.

1Text/2Text/3Text/4Text/5Text

Thanks so much for this solution. I just had two further questions. 1. for some reason my compiler is telling me that max is not a function and therefore won't compile the XSLT document. I am using .NET, so I don't know if it's possibly the framework that is the problem (as .NET uses XSLT 1.1)

Also I should apologize, as my question was a little poorly worded. My xml document contains many URLs so I need to retrieve several child URLs. I'm looking at a for-each loop at the top but my knowledge and experience with XSLT is rather limited and I'm not sure how to write a loop to select each child of DIMENSION_Id.

Thank you again for the help!

My apologies about the max() function. That's a 2.0 only function and I wrote mine under a 2.0 processor and forgot to test it under 1.0. My fault. That doesn't really matter though. That's just a way to get to the deepest node of DIMENSION_Id. You can call that template with any nodeset and it behave the same. You could use this instead of the XPath with max()

/descendant::DIMENSION_Id[last()]

In my experience, with developers who are new to XSLT, if you're using a for-each, you're probably wrong :P Or at least making it more difficult on yourself. I'd have to see a real sample document, with a real output target in order to better design your solution.

If you can construct a case, input XML doucment and output document, I'll take a look.

Please forgive me; I'm very new to programming but by case do you mean sample xml document which behaves like the one I have? If so I can definitely do that. As well I don't understand what you mean by input XML document and output document.. For output do you want me to send you the output that I've received from the XSLT? Thank you so much for your time and patience!

By input I mean, construct a sample XML document that full captures the essence of what your real input document looks like. The output is the desired output that you're trying to achieve (based on your sample input document). The case itself are any other rules or points that need to be considered that can't be capture by just the input and output itself. Basically construct a simple, yet complete scenario, that captures the rules and mappings that you want to achieve in the transformation you're trying to write. Once I have those, I can write a xslt that does meets those requirements.. You were close to doing that in your first post, but obviously there's something else to this problem or it'd be solved :)

Thanks iceandrews for the solution below! :

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>

<xsl:template match="/">
<URLs>
<xsl:apply-templates select="//DIMENSION_NODE"/>
</URLs>
</xsl:template>

<xsl:template match="DIMENSION_NODE">
<xsl:call-template name="getPath">
<xsl:with-param name="currentnode" select="."/>
</xsl:call-template>
</xsl:template>

<xsl:template name="getPath">
<xsl:param name="currentnode"/>
<xsl:param name="currenttext" select="''"/>
<xsl:param name="firstrun" select="1"/>
<xsl:choose>
<xsl:when test="$currentnode[parent::DIMENSION]">
<URL>
<xsl:value-of select="$currenttext"/>
</URL>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$firstrun = 1">
<xsl:variable name="gettext">
<xsl:text>/</xsl:text>
<xsl:value-of select="concat($currentnode/DVAL/SYN[1],'&#x9;',$currentnode/DVAL/DVAL_ID/@ID)"/>
</xsl:variable>
<xsl:call-template name="getPath">
<xsl:with-param name="currentnode" select="$currentnode/.."/>
<xsl:with-param name="currenttext" select="concat($gettext,$currenttext)"/>
<xsl:with-param name="firstrun" select="0"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="gettext">
<xsl:text>/</xsl:text>
<xsl:value-of select="$currentnode/DVAL/SYN[1]"/>
</xsl:variable>
<xsl:call-template name="getPath">
<xsl:with-param name="currentnode" select="$currentnode/.."/>
<xsl:with-param name="currenttext" select="concat($gettext,$currenttext)"/>
<xsl:with-param name="firstrun" select="0"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet><!-- Stylus Studio meta-information - (c) 2004-2007. Progress Software Corporation. All rights reserved.
<metaInformation>
<scenarios ><scenario default="yes" name="Scenario1" userelativepaths="yes" externalpreview="no" url="input_real.xml" htmlbaseurl="" outputurl="output_urls.xml" processortype="internal" useresolver="yes" profilemode="0" profiledepth="" profilelength="" urlprofilexml="" commandline="" additionalpath="" additionalclasspath="" postprocessortype="none" postprocesscommandline="" postprocessadditionalpath="" postprocessgeneratedext="" validateoutput="no" validator="internal" customvalidator=""/></scenarios><MapperMetaTag><MapperInfo srcSchemaPathIsRelative="yes" srcSchemaInterpretAsXML="no" destSchemaPath="" destSchemaRoot="" destSchemaPathIsRelative="yes" destSchemaInterpretAsXML="no" ><SourceSchema srcSchemaPath="input.xml" srcSchemaRoot="DIMENSION_Id" AssociatedInstance="" loaderFunction="document" loaderFunctionUsesURI="no"/></MapperInfo><MapperBlockPosition><template match="/"></template><template name="getPath"></template></MapperBlockPosition><TemplateContext></TemplateContext><MapperFilter side="source"></MapperFilter></MapperMetaTag>
</metaInformation>
-->

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.