Solved

Complex XSLT with parameters passed from the URL

Posted on 2004-04-29
17
655 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
  • 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
 
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
Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

 
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

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
asp.net, auto populate 6 49
Quest Defender - XML > HTML POST Data 9 29
Merge XML into DOM 5 27
Viewing XML as a table on a Mac 3 64
The Client Need Led Us to RSS I recently had an investment company ask me how they might notify their constituents about their newsworthy publications.  Probably you would think "Facebook" or "Twitter" but this is an interesting client.  Their cons…
Browsing the questions asked to the Experts of this forum, you will be amazed to see how many times people are headaching about monster regular expressions (regex) to select that specific part of some HTML or XML file they want to extract. The examp…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

758 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

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now