tags:

views:

47

answers:

3

I am trying to assign a value from an xsl variable to a new node in my xml file. This code works, but adds an empty PROP/PVAL node when the value of "lbi:GetCoordinates(PVAL)" is empty:

<xsl:template match="PROP" mode="Geocode">
<PROP NAME="Geocode">
    <PVAL>
      <xsl:value-of select="lbi:GetCoordinates(PVAL)"/>
    </PVAL>
 </PROP>
 </xsl:template>

As I don't want any empty nodes, I am trying to only add the new node only when the value of "lbi:GetCoordinates(PVAL)" is not empty. The approach I am trying is to assign the value to a variable and test that variable, as below. Unfortunately, when I do this I get no new PROP nodes, even when lbi:GetCoordinates(PVAL) returns a non-empty value.

<xsl:template match="PROP" mode="Geocode">
<xsl:variable name="coords" select="'lbi:GetCoordinates(PVAL)'"/>
<xsl:if test="not(string-length(coords) = 0)">
  <PROP NAME="Geocode">
    <PVAL>
      <xsl:value-of select="coords"/>
    </PVAL>
  </PROP>
</xsl:if>
</xsl:template>

Can anyone point me in the right direction, or suggest a better way of achieving this?

The source xml is like this:

<RECORD>
<PROP name="PostCode">
<PVAL>N11 1NN</PVAL>
</PROP>
</RECORD>

and the template is referenced thus:

<xsl:template match="RECORD">
<xsl:copy>
  <xsl:apply-templates select="PROP[@NAME='PostCode']" mode="Geocode"/>
</xsl:copy>

The lbi:GetCoordinates() method is in an external .Net assembly added as an xml namespace.


Using this approach works:

<xsl:template match="PROP[string-length(lbi:GetCoordinates(PVAL))>0]" mode="Geocode">
  <PROP NAME="Geocode">
    <PVAL>
      <xsl:value-of select="lbi:GetCoordinates(PVAL)"/>
    </PVAL>
  </PROP>

The problem now is that the lbi:GetCoordinates method is called twice when it only needs to be called once, the source xml can have 100,000+ elements that need geocoding so this is non-trivial. This suggests to me that the xsl:variable expression I used earlier is incorrect and the variable always ends up as empty.

A: 

Try to use string-length(coords) > 0 instead of your condition .

Andrew Bezzub
Thanks, but I get the same result with this.
Jibberish
A: 

if your source-xml looks somewhat like this:

<PROP>
  <lbi:GetCoordinates(PVAL)>sometext</lbi:Getcoordinates(PVAL>
</PROP>

this should do the trick:

<xsl:template match="PROP[string-length(lbi:GetCoordinates(PVAL))>0]" mode="Geocode">
    <PROP NAME="Geocode">
        <PVAL>
          <xsl:value-of select="lbi:GetCoordinates(PVAL)"/>
        </PVAL>
     </PROP>
</xsl:template>

I changed the match-clause to filter early, you could also just try to change your if-statement from not(string-length()=0) to string-length>0

I don't have an environment to test it out at the moment, consider including your source-xml, as it is vital to how exactly the xslt should be constructed

alfirin
Thanks for this, I have expanded my original question with information regarding the source xml.
Jibberish
This approach does work, however the requirement to call the lbi:GetCoordinates method twice each time is undesirable.
Jibberish
+1  A: 
<xsl:variable name="coords" select="'lbi:GetCoordinates(PVAL)'"/> 
<xsl:if test="not(string-length(coords) = 0)"> 

This is "almost" correct. The only problem is the quotes around lbi:GetCoordinates(PVAL). These convert the return value from an extension function-- just to the string of the expression that invokes this function. As the length of this string is obviously greater than 0, the test on the second line will allways be true.

From here on I suppose that the lbi:GetCoordinates() function returns a string or an atomic value (not a node or a node-set), because you haven't said anything about the return type of the function, but this is very important!

You want (note that the quotes are missing now!):

<xsl:variable name="coords" select="lbi:GetCoordinates(PVAL)"/> 
<xsl:if test="not(string-length(coords) = 0)"> 

**But even this is a little bit clumsy.

Solution: Use the power of XSLT template match patterns and avoid the conditional logic inside the template altogether:

<xsl:template match="PROP[string-length(lbi:GetCoordinates(PVAL))]"
     mode="Geocode">
  <PROP NAME="Geocode">                     
    <PVAL>                     
      <xsl:value-of select="lbi:GetCoordinates(PVAL)"/>                     
    </PVAL>                     
   </PROP>
 </xsl:template> 

Don't worry that the lbi:GetCoordinates(PVAL) function is called twice because a good optimizing XSLT processor will only call it once. You may always conduct some tests and see if this is the case.

In the worst case, if the XSLT processor is dumb and calls the function twice, then use the clumsier code above.

Dimitre Novatchev
Thanks Dimitre, that work beautifully.
Jibberish