tags:

views:

136

answers:

3

I have an XML document with companies listed in it. I want to create a link with XSLT that contains the <link> child of the next node. Sorry if this is confusing..here is some sample XML of what i'm trying to obtain:

<portfolio>

<company>
<name>Dano Industries</name>
<link>dano.xml</link>
</company>

<company>
<name>Mike and Co.</name>
<link>mike.xml</link>
</company>

<company>
<name>Steve Inc.</name>
<link>steve.xml</link>
</company>

</portfolio>

I want two links, "BACK" and "NEXT". While currently on mike.xml, I want BACK to link to "dano.xml" and NEXT linked to "steve.xml"...etc..and have it dynamically change when on a different page based on the nodes around it. I want to do this because I may add and change the list as I go along, so I don't want to have to manually re-link everything.

How can I obtain this? Sorry I am new to XSLT, so please explain with solution if possible! Thanks in advance!

A: 

You can use preceding-sibling and following-sibling for the parent company.

I'm not sure exactly what you're looking for, but the following stylesheet adds a back element and a next element to represent the links you're looking for. You should be able to modify it to fit your needs.

You can also test to see if there is a preceding/following sibling and only output a link if one exists (so you don't get empty <back/> or <next/> elements.

Input XML:

<?xml version="1.0" encoding="UTF-8"?>
<portfolio>

   <company>
      <name>Dano Industries</name>
      <link>dano.xml</link>
   </company>

   <company>
      <name>Mike and Co.</name>
      <link>mike.xml</link>
   </company>

   <company>
      <name>Steve Inc.</name>
      <link>steve.xml</link>
   </company>

</portfolio>

Stylesheet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
   <xsl:output indent="yes"/>

   <xsl:template match="node()|@*">
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="link">
      <back><xsl:value-of select="parent::company/preceding-sibling::company[1]/link"/></back>
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
      <next><xsl:value-of select="parent::company/following-sibling::company[1]/link"/></next>
   </xsl:template>

</xsl:stylesheet>

Resulting XML:

<?xml version="1.0" encoding="UTF-8"?>
<portfolio>

   <company>
      <name>Dano Industries</name>
      <back/>
      <link>dano.xml</link>
      <next>mike.xml</next>
   </company>

   <company>
      <name>Mike and Co.</name>
      <back>dano.xml</back>
      <link>mike.xml</link>
      <next>steve.xml</next>
   </company>

   <company>
      <name>Steve Inc.</name>
      <back>mike.xml</back>
      <link>steve.xml</link>
      <next/>
   </company>

</portfolio>
DevNull
+1  A: 

This transformation:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
  <html>
    <table border="1">
      <xsl:apply-templates/>
    </table>
  </html>
 </xsl:template>

 <xsl:template match="company">
   <xsl:variable name="vPrevious"
     select="preceding-sibling::company[1]/link"/>

   <xsl:variable name="vNext"
     select="following-sibling::company[1]/link"/>
   <tr>
     <td>
       <a href="{link}"><xsl:value-of select="name"/></a>
     </td>
     <td>
      &#xA0;
      <xsl:if test="$vPrevious">
       <a href="{$vPrevious}">Back</a>
      </xsl:if>
     </td>
     <td>
      &#xA0;
      <xsl:if test="$vNext">
       <a href="{$vNext}">Next</a>
      </xsl:if>
     </td>
   </tr>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<portfolio>
    <company>
        <name>Dano Industries</name>
        <link>dano.xml</link>
    </company>
    <company>
        <name>Mike and Co.</name>
        <link>mike.xml</link>
    </company>
    <company>
        <name>Steve Inc.</name>
        <link>steve.xml</link>
    </company>
</portfolio>

produces the desired HTML table with "Back" and "Next" links:

<html>
    <table border="1">
        <tr>
            <td>
                <a href="dano.xml">Dano Industries</a>
            </td>
            <td>      </td>
            <td>
                <a href="mike.xml">Next</a>
            </td>
        </tr>
        <tr>
            <td>
                <a href="mike.xml">Mike and Co.</a>
            </td>
            <td>
                <a href="dano.xml">Back</a>
            </td>
            <td>
                <a href="steve.xml">Next</a>
            </td>
        </tr>
        <tr>
            <td>
                <a href="steve.xml">Steve Inc.</a>
            </td>
            <td>
                <a href="mike.xml">Back</a>
            </td>
            <td>      </td>
        </tr>
    </table>
</html>
Dimitre Novatchev
Thanks for your help guys. Dimitre, your solution certainly works, but what i'm looking for is slightly different (sorry it's hard to be specific when i'm so new to this.) I have these companies populating on a list currently, and each company has their own XML file (mike.xml, dano.xml) containing details. THe back and next pages are going on each respective personal page, not the page with the master list.How can i accomplish this? Would i have to place the master list in each company's xml file? or could i use some sort of variable to refer to? Thanks in advance.
Andrew Parisi
@Andrew-Parisi: You have to decide on some certain URL scheme, so that it would be possible to compose the URL for the Back and Next page from the data available in their `company` element. This scheme will be used to generate the Back and Next links. If you define this URL scheme in your question, it probably would be possible to provide a solution that generates exactly the required URLs.
Dimitre Novatchev
+1  A: 

Based on your comments to Dimitre, I think what you're going to want to do is use the document() function to access your "master list" XML file.

What you are actually running the stylesheet on is the individual fragments (dano.xml, mike.xml, steve.xml), right?

I'll use "mike.xml" for an example. I don't know what the fragments look like so I had to make one up. You will need to be able to identify the correct <company> in the master list based on something in the fragment. In my example, the fragment has a <compName> element with the same value as the <name> element in the corresponding company in the master list XML.

Here is what the "master list" XML, "dano/mike/steve" XML, the stylesheet, and the resulting HTML look like:

master_list.xml:

<?xml version="1.0" encoding="UTF-8"?>
<portfolio>

   <company>
      <name>Dano Industries</name>
      <link>dano.xml</link>
   </company>

   <company>
      <name>Mike and Co.</name>
      <link>mike.xml</link>
   </company>

   <company>
      <name>Steve Inc.</name>
      <link>steve.xml</link>
   </company>

</portfolio>

dano.xml

<?xml version="1.0" encoding="UTF-8"?>
<fragment>
   <compName>Dano Industries</compName>
   <compInfo>Some info about Dano Industries</compInfo>
</fragment>

mike.xml:

<?xml version="1.0" encoding="UTF-8"?>
<fragment>
   <compName>Mike and Co.</compName>
   <compInfo>Some info about Mike and Co.</compInfo>
</fragment>

steve.xml

<?xml version="1.0" encoding="UTF-8"?>
<fragment>
   <compName>Steve Inc.</compName>
   <compInfo>Some info about Steve Inc.</compInfo>
</fragment>

stylesheet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
   <xsl:output method="html" indent="yes"/>
   <xsl:strip-space elements="*"/>

   <xsl:template match="fragment">
      <xsl:variable name="name" select="compName"/>
      <xsl:variable name="previous-file">
         <xsl:value-of select="document('master_list.xml')/portfolio/company[name=$name]/preceding-sibling::company[1]/link"/>
      </xsl:variable>
      <xsl:variable name="next-file">
         <xsl:value-of select="document('master_list.xml')/portfolio/company[name=$name]/following-sibling::company[1]/link"/>
      </xsl:variable>
      <html>
         <xsl:apply-templates/>
         <p>
            <xsl:if test="not($previous-file='')">
               <a href="{$previous-file}">Back</a>
            </xsl:if>
            <xsl:if test="not($previous-file='') and not($next-file='')">
               <xsl:text>&#xA0;|&#xA0;</xsl:text>
            </xsl:if>
            <xsl:if test="not($next-file='')">
               <a href="{$next-file}">Next</a>  
            </xsl:if>
         </p>
      </html>
   </xsl:template>

   <xsl:template match="compName">
      <h1><xsl:apply-templates/></h1>
   </xsl:template>

   <xsl:template match="compInfo">
      <p><xsl:apply-templates/></p>
   </xsl:template>

</xsl:stylesheet>

HTML for Dano (dano.htm:)

<html>
   <h1>Dano Industries</h1>
   <p>Some info about Dano Industries</p>
   <p><a href="mike.xml">Next</a></p>
</html>

HTML for Mike (mike.htm:)

<html>
   <h1>Mike and Co.</h1>
   <p>Some info about Mike and Co.</p>
   <p><a href="dano.xml">Back</a>&nbsp;|&nbsp;<a href="steve.xml">Next</a></p>
</html>

HTML for Steve (steve.htm:)

<html>
   <h1>Steve Inc.</h1>
   <p>Some info about Steve Inc.</p>
   <p><a href="mike.xml">Back</a></p>
</html>
DevNull
DevNull, It looks like that's what i'm looking for, but I'm getting this error when i try to run it: Error loading stylesheet: Invalid XSLT/XPath function.
Andrew Parisi
What processor are you using? I'm using Saxon 9 and everything works ok. If you're using Xalan, try removing all of the `data()` functions. Example: `data(compName)` -> `compName` (there are 4 `data()` functions total)
DevNull
Sorry, I'm not using an XSLT processor such as Saxon or Xalan. I don't even know where to begin with working with those. I just code it in and preview in my browser
Andrew Parisi
Did you try removing the `data()` functions?
DevNull
I removed the variable calling data(), and changed the value-of to match this format:<xsl:value-of select="document('master_list.xml')/portfolio/company/preceding-sibling::company[1]/link"/>...so now it's not giving the error anymore. But it still doesn't seem to display correctly. No matter which of the 3 pages i'm on (mike, dano, steve), Back always displays "dano.xml" and next is always "mike.xml".
Andrew Parisi
If you removed the whole variable, it doesn't know which `company` is the correct one. I've modified my answer to remove the `data()` functions and leave all the variables. You shouldn't have to change the value-of select either. Give it one more shot. :-)
DevNull
I added examples of what dano.xml and steve.xml would look like after transformation.
DevNull
Thanks for all your help! I used your updated code, and i'm having a strange issue with it. It works correctly for the middle element, (mike.xml), but when on the end elements (dano or steve), both next and back links are linked to the current page they're on. (dano to dano.xml, steve to steve.xml) strange? But getting closer! !--EDIT--! Nevermind. that was my fault, the names in each file weren't matching. it works!! Thank you so much Dev! One more thing. Is there an easy way to know if its on the first node to hide the back button, and the end node hide the next button? Thanks!
Andrew Parisi
You're welcome!You can do that a couple of ways. One is testing the `position()`. Another much easier way would be to check to see if the `$previous-file` or `$next-file` variables are not empty. If they aren't empty, output a link. I updated my answer with the `xsl:if` statements that I would use. I also added updated HTML result files.
DevNull
After some testing in my live implementation--it seems I've encountered a strange issue. the "Next" button seems to display the correct following element, but the BACK button--instead of showing the PREVIOUS sibling, it shows the FIRST sibling in the bunch that matches my criteria. This may because I changed "company[1]/link" to "company[@ind='Communications']/link", to only display the next/previous company within a particular industry. Syntax-wise, how could you combine the two? (company[1] and company[@ind='...']).. Thanks!
Andrew Parisi
Sorry Andrew...just saw your comment. You can use more than one predicate (the `[]`). Something like `company[@ind='...'][1]/link`
DevNull
Seems to work perfectly...thanks Dev!
Andrew Parisi