Reputation: 231
I'm trying to build a commenting system with XSLT. Here's the XML input for comments already submitted:
<in:inputs xmlns:in="http://www.composite.net/ns/transformation/input/1.0">
<!-- Input Parameter, XPath /in:inputs/in:param[@name='story_id'] -->
<in:param name="story_id">182485599</in:param>
<!-- Function Call Result (0 ms), XPath /in:inputs/in:result[@name='LoggedInWebUserID'] -->
<in:result name="LoggedInWebUserID">233459</in:result>
<!-- Function Call Result (9 ms), XPath /in:inputs/in:result[@name='XML_Comment']/root -->
<in:result name="XML_Comment">
<root xmlns="">
<Comments CommentID="1" ResponseCommentID="0" WebUserID="123456" FULL_NAME="Osikhuemhe Abulume" Comment="test comment!!!!" DateSubmitted="Feb 20 2013 1:34PM"/>
<Comments CommentID="2" ResponseCommentID="0" WebUserID="261337" FULL_NAME="Phillip Lowe" Comment="test comment2!!!!" DateSubmitted="Feb 20 2013 5:14PM"/>
<Comments CommentID="3" ResponseCommentID="1" WebUserID="000007" FULL_NAME="Norman Abbott" Comment="my response" DateSubmitted="Feb 20 2013 5:14PM"/>
<Comments CommentID="4" ResponseCommentID="0" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="Not impressed..." DateSubmitted="Feb 20 2013 4:10PM"/>
<Comments CommentID="5" ResponseCommentID="0" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="blah blah blah. " DateSubmitted="Feb 20 2013 4:11PM"/>
<Comments CommentID="6" ResponseCommentID="0" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="dfsfs" DateSubmitted="Feb 20 2013 4:14PM"/>
<Comments CommentID="7" ResponseCommentID="5" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="this is a response to blah blah blah." DateSubmitted="Feb 20 2013 4:52PM"/>
<Comments CommentID="8" ResponseCommentID="3" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="I don't agree with Norman. Terrible response." DateSubmitted="Feb 20 2013 5:39PM"/>
<Comments CommentID="9" ResponseCommentID="4" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="I'm impressed." DateSubmitted="Feb 20 2013 5:43PM"/>
<Comments CommentID="10" ResponseCommentID="1" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="I've got something to say!" DateSubmitted="Feb 20 2013 6:34PM"/>
</root>
</in:result>
This should work like any other commenting system for news stories (see: http://www.npr.org/2013/02/20/172384724/when-a-bad-economy-means-working-forever)
That is - new comments (ResponseCommentID = 0) would always be pushed to the left.
Responses to these comments would sit indented underneath. (i don't care about indentions right now. I would like to get replies to comments to fall underneath each other)
There are two parts where I'm stuck. The first part is how each post should be called:
<xsl:variable name="story_id" select="/in:inputs/in:param[@name='story_id']" />
<xsl:variable name="root" select="/in:inputs/in:result[@name='XML_Comment']/root" />
<xsl:for-each select="$root/Comments[@ResponseCommentID=0]">
<!-- call the template to plot the first comment down -->
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:value-of select="@CommentID" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:value-of select="@ResponseCommentID" /></xsl:with-param>
</xsl:call-template>
<!-- if the comment has any responses, put those underneath the root -->
<xsl:for-each select="$root/Comments[current()/@CommentID = $root/Comments/@ResponseCommentID]">
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:value-of select="@CommentID" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:value-of select="@ResponseCommentID" /></xsl:with-param>
</xsl:call-template>
</xsl:for-each>
The (also very wrong) ending recursion part of the template:
<xsl:if test="@CommentID = $root/Comments/@ResponseCommentID">
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:attribute name="value"><xsl:value-of select="@CommentID[@CommentID = $root/Comments/@ResponseCommentID]" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:attribute name="value"><xsl:value-of select="@ResponseCommentID[@CommentID = $root/Comments/@ResponseCommentID]" /></xsl:with-param>
</xsl:call-template>
</xsl:if>
If anyone could push me in the right direction I'd very much appreciate it. if more info is needed let me know. The actual template "thecomment" is simply taking the comment passed to it and formatting it the way I'd like.
Upvotes: 0
Views: 93
Reputation: 70618
Rather than using an xsl:for-each and then a call to a named template, you could consider combining the two into one xsl:apply-templates call. Firstly, you would select the comments with a ResponseCommentID attribute of 0.
<xsl:apply-templates
select="in:inputs/in:result[@name='XML_Comment']/root/Comments[@ResponseCommentID='0']" />
Then, you would have a template to match the Comments attribute, where you would output the comment details. You could then recursively get the response comments like so:
<xsl:apply-templates select="../Comments[@ResponseCommentID = current()/@CommentID]" />
This would just recursively call the same Comments template until there are no more reponses.
Here is the full XSLT in this case (I am outputting the comments as list items in HTML just as an example)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:in="http://www.composite.net/ns/transformation/input/1.0" exclude-result-prefixes="in">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<ul>
<xsl:apply-templates select="in:inputs/in:result[@name='XML_Comment']/root/Comments[@ResponseCommentID='0']" />
</ul>
</xsl:template>
<xsl:template match="Comments">
<li>
<xsl:value-of select="@Comment" />
<xsl:if test="../Comments[@ResponseCommentID = current()/@CommentID]">
<ul>
<xsl:apply-templates select="../Comments[@ResponseCommentID = current()/@CommentID]" />
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
This gives the following output
<ul>
<li>test comment!!!!
<ul>
<li>my response
<ul>
<li>I don't agree with Norman. Terrible response.</li>
</ul>
</li>
<li>I've got something to say!</li>
</ul>
</li>
<li>test comment2!!!!</li>
<li>Not impressed...
<ul>
<li>I'm impressed.</li>
</ul>
</li>
<li>blah blah blah.
<ul>
<li>this is a response to blah blah blah.</li>
</ul>
</li>
<li>dfsfs</li>
</ul>
However, it would be more efficient to use an xsl:key here to look up the responses to comments:
<xsl:key name="Comments" match="Comments" use="@ResponseCommentID" />
Then to get the top-level comments you would do this:
<xsl:apply-templates select="key('Comments', '0')" />
And to get the responses to a given comment in your matching template, you would do this
<xsl:apply-templates select="key('Comments', @CommentID)" />
The following XSLT also gives the same results
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:in="http://www.composite.net/ns/transformation/input/1.0" exclude-result-prefixes="in">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="Comments" match="Comments" use="@ResponseCommentID" />
<xsl:template match="/">
<ul>
<xsl:apply-templates select="key('Comments', '0')" />
</ul>
</xsl:template>
<xsl:template match="Comments">
<li>
<xsl:value-of select="@Comment" />
<xsl:if test="key('Comments', @CommentID)">
<ul>
<xsl:apply-templates select="key('Comments', @CommentID)" />
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
Upvotes: 2
Reputation: 231
I think I figured it out. Here's what my XLST looks like now. It will go thru all comments, but only if it's the first post (@ResponseCommentID = 0).
<xsl:for-each select="$root/Comments">
<xsl:if test="@ResponseCommentID = 0 and @CommentID != $root/Comments/@ResponseCommentID">
<!-- call the template to first plot the comment down -->
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:value-of select="@CommentID" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:value-of select="@ResponseCommentID" /></xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
This is the recursive part at the end. It looks at all the comments, and calls the template for each @ResponseCommentID attribute that is equal to the current @CommentID.
<xsl:for-each select="$root/Comments[@ResponseCommentID = current()/@CommentID]">
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:value-of select="@CommentID" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:value-of select="@ResponseCommentID" /></xsl:with-param>
</xsl:call-template>
</xsl:for-each>
Still don't understand it fully (I keep having to replay the sequence of events in my head), but I believe this works. :)
Upvotes: 0