tags:

views:

161

answers:

4

I have the following XML document:

<ReportParameters SP="prRptActivityDetail">
    <Parameter>
        <Name>Period Start Date</Name>
        <Type>Date</Type>
        <Control>DateTextbox</Control>
        <ControlName>dtePeriodStartDate</ControlName>
        <Validators>
            <Validator>Required</Validator>
            <Validator>DataTypeCheck</Validator>
            <Validator>StartBeforeEnd</Validator>
        </Validators>
    </Parameter>
</ReportParameters>

I have written an XSLT file to transform the above:

<xsl:for-each select="ReportParameters/Parameter/Validators">
   <xsl:choose>
       <xsl:when test="Validator='Required'">
           <span>
               <REQUIRED VALIDATOR CONTROL HERE>
           </span>
       </xsl:when>
       <xsl:when test="Validator='DataTypeCheck'">
           <span>
               <DATA TYPE CHECK VALIDATOR CONTROL HERE>
           </span>
       </xsl:when>
   </xsl:choose>

I've left out a lot of the XSLT for clarity.

For each parameter control (Period Start Date in this case) I wish to have all of the validators listed (3 in this case) placed on the page as validator controls, but I only get the first one when using for-each. I know why this is but I'm a complete newbie with xslt and don't know the syntax to get around this.

Any help much appreciated,

Rich.

+2  A: 

I think the issue is that you are looping over the 'Validators' collection. You are wanting to loop over all the instances of 'Validator'.

Try: (example I used in research)

<xsl:for-each select="ReportParameters/Parameter/Validators/Validator">
   <xsl:choose>
       <xsl:when test=".='Required'">
           <span>
               <REQUIRED VALIDATOR CONTROL HERE>
           </span>
       </xsl:when>
       <xsl:when test=".='DataTypeCheck'">
           <span>
               <DATA TYPE CHECK VALIDATOR CONTROL HERE>
           </span>
       </xsl:when>
   </xsl:choose>
kniemczak
A: 

For this sort of task you don't need to explicitly loop over elements. It's not the XSLT way. Think more in terms of matching against the tree structure.

Something like this will work:

<xsl:if test="ReportParameters/Parameter/Validators/Validator='Required'">
    <span>
        <REQUIRED></REQUIRED>
     </span>
</xsl:if>
<xsl:if test="ReportParameters/Parameter/Validators/Validator='DataTypeCheck'">
    <span>
        <DATA></DATA>
    </span>
</xsl:if>

Also look into creating sub <xsl:template>s and calling them with <xsl:apply-templates> to handle smaller portions of the tree. For a large transform it should be a lot more maintainable.

Damien Ayers
Thanks a lot everybody - I've gone with a template solution.
+1  A: 

When learning XSLT it is good to know that one should avoid using <xsl:for-each> unless this is really necessary.

Here is a simple and short "loopless" way to achieve the same in pure push style:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

 <xsl:variable name="vrtfValidatorNames">
  <validator type="Required" name="REQUIRED"/>
  <validator type="DataTypeCheck" name="DATA TYPE CHECK"/>
  <validator type="StartBeforeEnd" name="START BEFORE END"/>
 </xsl:variable>

 <xsl:variable name="vValidatorNames" select=
  "document('')/*/xsl:variable[@name='vrtfValidatorNames']/*"/>

 <xsl:template match="Validator">
   &lt;<xsl:value-of select="$vValidatorNames[@type=current()]/@name"/> VALIDATOR CONTROL HERE>
 </xsl:template>

 <xsl:template match="text()"/>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<ReportParameters SP="prRptActivityDetail">
    <Parameter>
        <Name>Period Start Date</Name>
        <Type>Date</Type>
        <Control>DateTextbox</Control>
        <ControlName>dtePeriodStartDate</ControlName>
        <Validators>
            <Validator>Required</Validator>
            <Validator>DataTypeCheck</Validator>
            <Validator>StartBeforeEnd</Validator>
        </Validators>
    </Parameter>
</ReportParameters>

the wanted result is produced:

   <REQUIRED VALIDATOR CONTROL HERE>

   <DATA TYPE CHECK VALIDATOR CONTROL HERE>

   <START BEFORE END VALIDATOR CONTROL HERE>
Dimitre Novatchev
@Dimitre: +1 for elegant solution. But in order to learn XSLT, it would be good an explanation about why his transformation isn't working. Every else answer spot the context node inside `for-each` wich is a wrong explanation for the provided XSLT fragment.
Alejandro
@Alejandro: I agree, but I find it more important to let beginners know that in XSLT `<xsl:template>` and push-style are the really important things, not `<xsl:for-each>`
Dimitre Novatchev
+1  A: 

I'd suggest replacing your <xsl:for-each tag with:

<xsl:apply-templates select="ReportParameters/Parameter/Validators/Validator" />

and include templates for each:

<xsl:template match="Validator[text()='Required']">
  ...
</xsl:template>

<xsl:template match="Validator[text()='DataTypeCheck']">
  ..
</xsl:template>

 etc.

As @kniemczak said, you're actually only looping through the parent element at the moment.

Flynn1179
I went with this solution as it fitted my needs but all of the answers helped me out.