Search code examples
htmlxsltxslt-1.0xslt-2.0marc

xslt get the immediate following sibling


I have the following:

 <root>
  <html>
   <table
    <tr> 
     <td width="1%" height="20"></td>
     <td width="18%">Book Location </td>
     <td width="81%">Technology (Applied sciences) Circulation</td>
    </tr>

I'm trying below xslt to get the immediate node content in which node content of td is "Book Location ":

<?xml version="1.0" encoding="utf-8"?>
 <xsl:stylesheet version="1.0"
 xmlns:marc="http://www.loc.gov/MARC21/slim"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text" indent="yes" />

 <xsl:template match="root">
  <xsl:for-each select="html">
  <xsl:text>START HERE</xsl:text>
  <xsl:text>&#13;&#10;</xsl:text>
  <xsl:text>=LDR  00000nam  2200000Ia 4500</xsl:text>
  <xsl:text>&#13;&#10;</xsl:text>

  <xsl:if test="//*[text()='Book Location ']">
   <xsl:text>=952  \\$cLocation: </xsl:text>
   <xsl:value-of select="following-sibling" />
  </xsl:if> 

  </xsl:for-each>

  </xsl:template>
 </xsl:stylesheet>

I'm not sure what to put in this part: Or if there's a better way to do it? Thanks in advance and have a nice day!


Solution

  • Your template suggests that you're thinking like a procedural language programmer. XSLT can be written more or less in that idiom, but it is not XSLT's natural idiom. Code written that way tends to be longer and messier than more natural code. In particular, although they have good uses, for-each elements usually carry a bit of code smell.

    This seems more natural to me, and it seems to work (but I had to test on a modified version of your input, since what you presented is not valid XML):

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="text" indent="yes" />
    
      <xsl:template match="/root/html">
        <xsl:text>START HERE</xsl:text>
        <xsl:text>&#13;&#10;</xsl:text>
        <xsl:text>=LDR  00000nam  2200000Ia 4500</xsl:text>
        <xsl:text>&#13;&#10;</xsl:text>
        <xsl:apply-templates select="descendant::td[text() = 'Book Location ']" />
      </xsl:template>
    
      <xsl:template match="td">
       <xsl:text>=952  \\$cLocation: </xsl:text>
       <xsl:value-of select="following-sibling::*[1]" />
      </xsl:template> 
    
    </xsl:stylesheet>
    

    Note:

    • a more specific match expression obviates the need for the for-each in your original
    • instead of trying to put the whole transformation into one template, separate templates are used for transforming separate elements. This is not only cleaner, but also helps simplify expressions because each template is associated with its own context, with respect to which expressions are evaluated
    • To use "following-sibling" as an axis name, as opposed to an element name, you must append a double colon and at least a node test of some sort
    • The "following-sibling" axis can contain more than one node, so if you want only the immediately-following one then you need to specify that (as above)
    • I might have chosen to transform the td containing the location directly, instead of indirectly via the template for its label td. That seems cleaner to me, but I did not do it because it has slightly different semantics.