Subject: RE: [xsl] Generate Yahoo-like directory structure From: "Daniel Joshua" <daniel.joshua@xxxxxxxxxxxx> Date: Thu, 20 May 2004 14:50:47 +0800 |
David >http://www.generationXML.com Did you generate this whole site with XML + XSLT? I am trying to the above, so just interested... Regards, Daniel -----Original Message----- From: M. David Peterson [mailto:m.david@xxxxxxxxxx] Sent: Wednesday, 12 May, 2004 10:06 AM To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx Subject: RE: [xsl] Generate Yahoo-like directory structure When I saw your post it reminded me that I needed to create this exact feature for an up and coming site of mine, http://www.generationXML.com . This site is actually a high priority for me but my other project, http://www.aspectXML.org has taken precedence simply because the benefit of having the AspectXML project in a 1.0 state will benefit greatly generationXML.com as well as several of my other projects. I realize this is a lot more information than you needed but I thought I would explain that in this particular case I am not trying to be the good Samaritan who simply wants to help where he can. No my motives were ulterior this time around but fortunately the fact that I needed this code and had planned on writint it this week lends well to all of us. As it turns out the code took less than an hour to develop so it wasn't even as big of a deal as I thought it would be. The resulting code should do for you exactly what you want... output a Yahoo style directory based on a parameter that is set to the current directory structure and then mapping that against an XML tree that we can then parse to output our desired directory... fortunately our needs were exact and we both now have what we want. Well, I hope anyway :D I apologize for not getting this done and out sooner but the events of the day have not lent well to such luxuries so it is now almost 8pm where I'm at and the day isn't even close to coming to an end. Such is the life of a software developer/code junkie. I'll provide some quick documentation to try to explain what I did. If this becomes code that gets used by more than one person I may consider writing heavier docs but these should be suffice to anyone but the novice XSLT developer. Keep in mind that I've only done preliminary testing on this code but so far is seems to output correctly the children of the last element in the directory structure. A great contribution back for developing this code would be to test it until it begs for mercy (try going 20 or 30 (or even 40 or 50 if you feel brave) levels deep and then process as many children as you have the patience to create in your sample XML file) and then post back your results to the list so that everyone can benefit from it. I still like Michael Kay's solution a million times better (he tends to write pretty good code ;) but I, like you, have to use 1.0 code for the time being in production so until the day comes that 2.0 is a choice this will have to do. A quick synopsis of the code: - I first created a param called 'dirPath' and set it to my desired test setting, in this case I set it to 'Main/Computers/WWW'. - I then created my first template and matched it to root. - Within this first template I created a variable called 'dirPathXML' and used call-template to call the 'PathToXML' template passing it the param 'dirPath' set to the value of the global param 'dirPath'. - Within this template I used a named-template and called it recursively to build an XML data set from the directory structure. The code should be pretty straight forward in how this works but in general it parses the string using the '/' and the current string-length to determine if it should keep recursively building the tree. Actually, it first tests to see if '/' is the first character of the string and if so calls the template again but this time setting the value of dirPath to the substring-after '/'. With this we have allowed ourselves the ability to test for the presence of '/' as well as the string-length after the first (and potentially last) occurrence of '/' to determine if we need to call the template again to catch the remaining values and put them into the tree structure. This test statement: string-length(substring-after($dirPath, '/')) > 0 will both test for the presence of '/' as well as check the length of the string contained after '/' if it finds it. Obviously if the substring-after function doesn't find an occurrence of '/' the entire equation will evaluate to false causing the code to fall-through to the next 'when' block if present or 'otherwise' block if not. If this happens the only thing left to test for is if there's a '/' at end of our string (depending on how the directory structure was passed in there may be a '/' appended to the end). If there is we need to get rid of it and then write out the string (the string becomes the value of the 'name' attribute of 'element' btw...) to the output otherwise just write out the string. The next 'when' and 'otherwise' block take care of this for us. At this point we have contained in our variable a data set that looks a lot like XML. However, if we tried to use this variable as the basis of a select attribute of apply-templates or for-each we would find an error thrown. Even though it looks to us like XML it looks like a string or at best a result tree fragment to the processor and as such we need to tell the processor to treat this like regular old XML data. In 1.0 the only way to do this is with an extension function. If your processor doesn't support some sort of nodeset() extension function then you are most likely not using one of the standard processors as all of the standard processor vendors utilize some form of the nodeset() function to allow you to convert transformed data we have stored into a temporary tree or current flow of the processor into something that we can now continue to parse as if it was part of the existing XML data flow. Ive used Xalan in this example but msxml uses the msxml namespace and implements using msxml:node-set() while Saxon uses the EXSLT library to invoke the conversion which uses the exslt namespace and exslt:node-set() to invoke the function (this would be in place of xalan:nodeset() which is currently being used.) Remember to add the correct xmlns: statement to the xsl:stylesheet declaration to ensure things go over smoothly. Just look to the docs for whichever processor you use to show how to do this correctly. Once we have our nodeset we can simply use apply-templates and the elements of the variable we created to continue the processing. By passing as a parameter the value of the current directory structure we can then match each element with the element contained in our variable, recursively parsing the children of our created nodeset until we find there are no more children to process. When this happens we know we have reached the end of our directory structure (and the whole time we have been continuing to water down the directory structure using the name attribute as reference and passing it back to the template as a parameter) and if we have matched the names of the directory structure correctly with element names of our directory we can now apply-templates to the children of the current element in context and process them as we wish to get the desired styled output. Note: Notice the use of mode="..." in both the call to apply-templates as well as the template it matches to. This ensures that the processor knows which template to process the current 'element' element with as otherwise it would just use the first template that matched 'element' which obviously wouldn't work in every case. I have just put some basic formatting in as the way I plan on outputting the directory structure may be different form how you would want to do it. However, I will make available (on generationXML.com if I ever get it finished ;) the implementation that I develop simply because it will be table-less and driven by CSS classes and as such completely customizable via these same classes making it a pretty universal solution. Im not sure when ill get that done but it will be soon and I will post a link to the list when it happens. In the mean time, enjoy the following code and please, if you have the time to pressure cook it, it would be a huge help and will benefit all the members of the list and beyond (if they of course were to choose to use this code for there own project :) Also, keep in mind that I wrote this in less than an hour and although conceptually it seems sound I could be completely missing something I haven't even thought of. So, if you have any suggestions on how to make this better, faster, more robust, etc... feel free to make the changes yourself or let me know your thoughts and Ill see if I can incorporate them. Hope this helps! Best regards, <M:D/> The following XML: <?xml version="1.0"?> <element name="Main"> <element name="Business"> <element name="Finance"/> </element> <element name="Computers"> <element name="Internet"/> <element name="WWW"> <element name="Chat"/> <element name="DNS"/> </element> </element> <element name="Business"/> </element> When processed by this XSLT: <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xalan" version="1.0"> <xsl:param name="dirPath" select="'Main/Computers/WWW'"/> <xsl:output method="html" indent="yes"/> <xsl:template match="/"> <xsl:variable name="dirPathXML"> <xsl:call-template name="PathToXML"> <xsl:with-param name="dirPath" select="$dirPath"/> </xsl:call-template> </xsl:variable> <xsl:variable name="dirXML" select="xalan:nodeset($dirPathXML)"/> <xsl:apply-templates select="$dirXML/*" mode="locDir"> <xsl:with-param name="directory" select="*"/> </xsl:apply-templates> </xsl:template> <xsl:template name="PathToXML"> <xsl:param name="dirPath"/> <xsl:choose> <xsl:when test="starts-with($dirPath, '/')"> <xsl:call-template name="PathToXML"> <xsl:with-param name="dirPath" select="substring-after($dirPath, '/')"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:element name="element"> <xsl:choose> <xsl:when test="string-length(substring-after($dirPath, '/')) > 0"> <xsl:attribute name="name"> <xsl:value-of select="substring-before($dirPath, '/')"/> </xsl:attribute> <xsl:call-template name="PathToXML"> <xsl:with-param name="dirPath" select="substring-after($dirPath, '/')"/> </xsl:call-template> </xsl:when> <xsl:when test="contains($dirPath, '/')"> <xsl:value-of select="substring-before($dirPath, '/')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$dirPath"/> </xsl:otherwise> </xsl:choose> </xsl:element> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="element" mode="locDir"> <xsl:param name="directory"/> <xsl:variable name="name" select="@name"/> <xsl:choose> <xsl:when test="*"> <xsl:apply-templates select="*" mode="locDir"> <xsl:with-param name="directory" select="$directory[@name = $name]/*"/> </xsl:apply-templates> </xsl:when> <xsl:otherwise> <table> <tr> <xsl:apply-templates select="$directory[@name = $name]/*" mode="outputYahoo"/> </tr> </table> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="element[last()]" mode="outputYahoo"> <td> <a href="{@name}"> <xsl:value-of select="@name"/> </a> </td> </xsl:template> <xsl:template match="element" mode="outputYahoo"> <td> <a href="{@name}"> <xsl:value-of select="@name"/> </a> </td> <td>|</td> </xsl:template> </xsl:stylesheet> Will return the following HTML to the output: <table> <tr> <td><a href="Chat"> Chat </a></td><td>|</td><td><a href="DNS"> DNS </a></td> </tr> </table> Again, feel free to modify, enhance, or suggest enhancements... And if you come up with something really sweet please post it back for the rest of us to enjoy :D Thanks! <M:D/> > -----Original Message----- > From: Philipp Burkert [mailto:mailings@xxxxxxxxxx] > Sent: Tuesday, May 11, 2004 11:07 AM > To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx > Subject: RE: [xsl] Generate Yahoo-like directory structure > > Hi, > > Michael, thankx for the quick response. Anyhow I should have made a note > that I can not make use of Xpath2. Can you - or someone else - outline the > way in Version 1 in more detail? > > Thankx > > PHILIPP BURKERT > mailings@xxxxxxxxxx
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
RE: [xsl] Generate Yahoo-like direc, M. David Peterson | Thread | RE: [xsl] Generate Yahoo-like direc, M. David Peterson |
[xsl] how to use the xsl:param in t, Chen Yi | Date | RE: [xsl] how to use the xsl:param , Jarno.Elovirta |
Month |