Solved

Complex XSLT with parameters passed from the URL

Posted on 2004-04-29
17
682 Views
Last Modified: 2010-08-05
I have an xml file of the following structure

<document type="x">
    <title>Doc 1</title>
    <author>Joe Bloggs</author>
</document>

<document type="y">
    <title>Who Cares</title>
    <author>No one</author>
</document>

<category category="cat1">
    <document type="x">
        <title>Doc 2</title>
        <author>John Doe</author>
    </document>

    <category category="cat2">
         <document type="x">
            <title>More Docs</title>
            <author>A N Other</author>
         </document>
    </category>

    <document type="x">
        <title>Doc 3</title>
        <author>Jane Doe</author>
    </document>

    <document type="y">
        <title>Who Cares Vol 2</title>
        <author>No One</author>
    </document>
</category>

I want to use XSLT to convert this into a page which will display documents of type 'z' where the URL is www.blah.com/documents.xml?type=z&category=.
Furthermore I wish to list the documents in a definition list of the form
<dl>
    <dt>Doc 1</dt>
    <dd>Joe Bloggs</dd>
    <a href="www.blah.com/documents.xml?type=z&category=cat1">cat1</a> &gt;
    <dl>
        <dt>Doc 2</dt>
        <dd>John Doe</dd>
        <dt>Doc 3</dt>
        <dd>Jane Doe</dd>
        <a href="www.blah.com/documents.xml?type=z&category=cat1">cat1</a> &gt; <a href="www.blah.com/documents.xml?type=z&category=cat2">cat2</a> &gt;
        <dl>
            <dt>More docs</dt>
            <dd>A N Other</dd>
        </dl>
    </dl>
</dl>

It is important that categories can have unlimited levels of subcategory and a clickable breadcrumb is displayed as shown below to allow users to drill down to only display the data they require.


Doc 1
Joe Bloggs

cat1 >
     Doc 2
     John Doe

     Doc 3
     Jane Doe
cat1 > cat2 >
          More Docs
          A N Other
0
Comment
Question by:Neil_Simpson
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 9
  • 5
  • 3
17 Comments
 
LVL 5

Expert Comment

by:conorj
ID: 10950923
Neil,

The following stylesheet should get you on your way:

.................
<xsl:param name="type" select="'x'" />
<xsl:param name="category" select="'cat1'" />
<!--      Top Level Variables      -->

<!--      Template Matches      -->
<xsl:template match="/">
    <html>
        <head></head>
        <body>
            <dl>
                <xsl:apply-templates select="root/document[@type = $type]" />
            </dl>
        </body>
    </html>
</xsl:template>

<xsl:template match="document">
    <dt><xsl:value-of select="title" /></dt>
    <dd><xsl:value-of select="author" /></dd>
    <xsl:variable name="isCatParam" select="($category = '') or (($category != '') and (..//category/@category = $category))" />
    <xsl:if test="(position() = last()) and $isCatParam">
        <xsl:apply-templates select="../category[document/@type = $type]" />
    </xsl:if>
</xsl:template>

<xsl:template match="category">
    <xsl:for-each select="ancestor::category">
        <a href="http://www.blah.com/documents.xml?type={$type}&amp;category={@category}"><xsl:value-of select="@category" /></a> >
    </xsl:for-each>
    <a href="http://www.blah.com/documents.xml?type={$type}&amp;category={@category}"><xsl:value-of select="@category" /></a> >
    <xsl:variable name="subdocs" select="document[@type = $type]" />
    <xsl:if test="$subdocs">
        <dl>
            <xsl:apply-templates select="$subdocs" />
        </dl>
    </xsl:if>
</xsl:template>

.................

HTH.

rgds,
Conor.
0
 
LVL 4

Author Comment

by:Neil_Simpson
ID: 10951254
Thanks, this works well to create the list of previous categories. There are two problems which I have found so far.

Firstly, in <xsl:template match="/"> I cannot use <xsl:apply-templates select="root/document[@type = $type]" /> as this prevents any output from being displayed. This was fixed by changing <xsl:template match="document"> to <xsl:template match="document[@type = $type]">.

Secondly, if I change the category variable to a different category it still displays all of the other information. If for example <xsl:param name="category" select="'cat 2'" /> then all of the documents without a category AND the documents in 'cat 1' are still displayed.

If this can be solved then this solution solves the first part. I still need to know how to set the variables $category and $type from those passed in on the URL.
0
 
LVL 10

Expert Comment

by:Yury_Delendik
ID: 10953932
Second part of solution depend on where transformation will occur on server or client and what web server (or client browser) will perform this
0
MIM Survival Guide for Service Desk Managers

Major incidents can send mastered service desk processes into disorder. Systems and tools produce the data needed to resolve these incidents, but your challenge is getting that information to the right people fast. Check out the Survival Guide and begin bringing order to chaos.

 
LVL 4

Author Comment

by:Neil_Simpson
ID: 10957445
This in an in-house issue so the target browser is Mozilla 1.4 and the server (from the returned HTTP header) is "Server: Netscape-Enterprise/6.0". I do not mind whether the transformation occurs server side or client side. I know that I could use javascript and DHTML to display only approopriate <div>s  based upon a full listing of the documents but I would rather use XML/XSLT if/where possible so that I get some exposure to this as the site will make greater use of this as time goes on and new sections/applications are developed.
0
 
LVL 5

Expert Comment

by:conorj
ID: 10957689
Neil,

the reason I put root into the XPath expression is that your XML is not well formed as it does not have a root element. So i wrapped your XML inside an element named "root" for testing purposes.

the following XSLT should do what you want. I have used a key to test if there is a category document with the right document type.
...................
<xsl:key match="category" name="cats" use="@category" />

<xsl:param name="type" select="'x'" />
<xsl:param name="category" select="'cat2'" />
<!--     Top Level Variables     -->

<!--     Template Matches     -->
<xsl:template match="/">
    <html>
        <head></head>
        <body>
            <dl>
                <xsl:choose>
                    <xsl:when test="$category = ''">
                        <xsl:apply-templates select="root/document[@type = $type]" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:if test="key('cats', $category)/document[@type = $type]">
                            <xsl:apply-templates select="root/document[@type = $type]" />
                        </xsl:if>
                    </xsl:otherwise>
                </xsl:choose>
            </dl>
        </body>
    </html>
</xsl:template>

<xsl:template match="document">
    <dt><xsl:value-of select="title" /></dt>
    <dd><xsl:value-of select="author" /></dd>
    <xsl:variable name="isCatParam" select="($category = '') or (($category != '') and (..//category/@category = $category))" />
    <xsl:if test="(position() = last()) and $isCatParam">
        <xsl:apply-templates select="../category[document/@type = $type]" />
    </xsl:if>
</xsl:template>

<xsl:template match="category">
    <xsl:for-each select="ancestor::category">
        <a href="http://www.blah.com/documents.xml?type={$type}&amp;category={@category}"><xsl:value-of select="@category" /></a> >
    </xsl:for-each>
    <a href="http://www.blah.com/documents.xml?type={$type}&amp;category={@category}"><xsl:value-of select="@category" /></a> >
    <xsl:variable name="subdocs" select="document[@type = $type]" />
    <xsl:variable name="selDocs" select="($category = '') or (@category = $category)" />
    <xsl:choose>
        <xsl:when test="$subdocs and $selDocs">
            <dl>
                <xsl:apply-templates select="$subdocs" />
            </dl>
        </xsl:when>
        <xsl:when test="$category != ''">
            <dl>
                <xsl:apply-templates select="category[document/@type = $type]" />
            </dl>
        </xsl:when>
    </xsl:choose>
</xsl:template>
.......................

As Yury said the method of passing parameters to the stylesheet depends upon the transformation environment.

HTH.

rgds,
Conor.
0
 
LVL 4

Author Comment

by:Neil_Simpson
ID: 10958373
Hi Conor,

Thanks for your help. This solution appeared to add some unnnecessary complexity (although I won't pretend to fully understand your use of <xsl:key>). I found that by adding
<xsl:choose>
      <xsl:when test="($category = '')">
            <xsl:apply-templates select="documents/document[@type = $type]" />
      </xsl:when>
      <xsl:otherwise>
            <xsl:apply-templates select="//category[@category = $category]" />
      </xsl:otherwise>
</xsl:choose>
to the <xsl:template match="/"> and
<xsl:apply-templates select="child::category" />
to the <xsl:template match="category"> that it worked correctly.

Can anyone suggest a way to pass the parameters in from the URL based upon the fact that I am just using mozilla to display the XML file so the transformation is taking place within the browser I'd assume? I'm incredibly close to getting this working successfully it's just this that is holding me up. Another problem I can forsee is the inclusion of the SSIs which I use for the site menus and layout. Is it possible to dump the contents of an external file into the output during the transformation?

PS: I did actually have a root node called documents but I forgot to add it in the example, whoops. :o)

Anyway, thanks again, if I can just get this last
0
 
LVL 10

Assisted Solution

by:Yury_Delendik
Yury_Delendik earned 200 total points
ID: 10959326
what programming language you are using?
0
 
LVL 4

Author Comment

by:Neil_Simpson
ID: 10960196
I'm now using an HTML file with a Javascript to apply the variables I require and it all works fantastically well. This avoids the SSI problem I would ave encountered as I can just have this in the HTML wrapper file. I have included the output below. Just before I award the points there is one remaining niggle in the XSLT; it prints out category names even if no documents within the category match the specified type? Any final nuggets of XSLT wizardry to prevent me from using my own brain to think about this?

<script>
var xslStylesheet;
var xsltProcessor = new XSLTProcessor();
var myDOM;

var xmlDoc;

function Init() {
      // load the xslt file, example1.xsl
      var myXMLHTTPRequest = new XMLHttpRequest();
      myXMLHTTPRequest.open("GET", "1pager_styles.xsl", false);
      myXMLHTTPRequest.send(null);

      xslStylesheet = myXMLHTTPRequest.responseXML;
      xsltProcessor.importStylesheet(xslStylesheet);

      // load the xml file, example1.xml
      myXMLHTTPRequest = new XMLHttpRequest();
      myXMLHTTPRequest.open("GET", "documents.xml", false);
      myXMLHTTPRequest.send(null);

      xmlDoc = myXMLHTTPRequest.responseXML;
      //Get URL parameters
      var x = 0
      URLParams = location.search.substr(1).split("&");
      for (x=0;x<=URLParams.length-1;x++) {
            singleParam = URLParams[x].split("=");
            xsltProcessor.setParameter(null, singleParam[0], unescape(singleParam[1]));
      }
      var fragment = xsltProcessor.transformToFragment(xmlDoc, document);      

      document.getElementById("example").innerHTML = "";

      myDOM = fragment;
      document.getElementById("example").appendChild(fragment)        
}

</script>  
0
 
LVL 5

Accepted Solution

by:
conorj earned 300 total points
ID: 10961953
Neil,

have you tried changing

<xsl:apply-templates select="//category[@category = $category]" />

to

<xsl:apply-templates select="//category[@category = $category][document/@type = $type]" />

rgds,
Conor.
0
 
LVL 4

Author Comment

by:Neil_Simpson
ID: 10975996
Well, I think I've almost cracked it. All I need to know is:
1) how to display sub-categories when they have a file of @type=$type but their parent category does not.
2) an elegant way to select all categories or types when a value is not specified, I had wondered if it was possible to use a wildcard as the variable value but this did not appear to work (although it may well have been my implementation of this that didn't work). I know it is possible to have a choose and then 4 options so all possibilities of $category and/or $type = '' but this needs to be repeated frequently throughout the sheet and is obviously ineffiecient. Also, this would not scale well at all if I were to allow any other parameter to be passed on the URL.
0
 
LVL 4

Author Comment

by:Neil_Simpson
ID: 10994174
Anybody?
0
 
LVL 10

Expert Comment

by:Yury_Delendik
ID: 10996782
0
 
LVL 4

Author Comment

by:Neil_Simpson
ID: 10997442
Thanks to a seperate question I've managed to solve problem 2) above. I shall investigate problem 1 now.
0
 
LVL 10

Expert Comment

by:Yury_Delendik
ID: 11000093
<xsl:for-each select="//category[ document[@type = $type] and not(ancestor::category[document[@type = $type]]) ]">
0
 
LVL 10

Expert Comment

by:Yury_Delendik
ID: 11000126
To do both $category and $type and they may be empty (or null, nothing):

"//category[ not(@category != $category) and document[not(@type != $type)] ][not(ancestor::category[ not(@category != $category) and document[not(@type != $type)] ]) ]"
0
 
LVL 4

Author Comment

by:Neil_Simpson
ID: 11007579
I have changed category to refer to an id attribute and based upon your answer to the other question I have the select as:

"//category[ (@id = $id) and document[not($type) or @type = $type] ][not(ancestor::category[ (@id = $id) and document[not($type) or @type = $type] ])]"

The thing is, if I say type=other and do not specify an ID (the default for the id is the ID of the root category) then I don not see files of type other which are in a sub-category when the parent category does not contain a document of type=other.
0
 
LVL 4

Author Comment

by:Neil_Simpson
ID: 11007839
It looks like this does the trick:

"//category[(document[not($type) or @type = $type]) and (@id = $id)]|//category[@id = $id]//category[document[not($type) or @type = $type]]"

Thanks for your help everyone. I'll split the point between Conor and Yury
0

Featured Post

Space-Age Communications Transitions to DevOps

ViaSat, a global provider of satellite and wireless communications, securely connects businesses, governments, and organizations to the Internet. Learn how ViaSat’s Network Solutions Engineer, drove the transition from a traditional network support to a DevOps-centric model.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

I was working on a PowerPoint add-in the other day and a client asked me "can you implement a feature which processes a chart when it's pasted into a slide from another deck?". It got me wondering how to hook into built-in ribbon events in Office.
Many times as a report developer I've been asked to display normalized data such as three rows with values Jack, Joe, and Bob as a single comma-separated string such as 'Jack, Joe, Bob', and vice versa.  Here's how to do it. 
Nobody understands Phishing better than an anti-spam company. That’s why we are providing Phishing Awareness Training to our customers. According to a report by Verizon, only 3% of targeted users report malicious emails to management. With compan…
Attackers love to prey on accounts that have privileges. Reducing privileged accounts and protecting privileged accounts therefore is paramount. Users, groups, and service accounts need to be protected to help protect the entire Active Directory …

749 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question