Extracting multiple <TAG> values from the tasklist

Questions about writing stylesheets for transforming (Tools > Transform Tasklist) your tasklist

Moderator: abstr

Post Reply
JamesO2
Posts: 6
Joined: Sun Jan 03, 2021 7:18 pm

Extracting multiple <TAG> values from the tasklist

Post by JamesO2 » Tue Jan 19, 2021 3:06 pm

I'm trying to figure out how to get the data out of certain ToDoList columns. I understand the basics of setting up the XSL transformation sheets, and by examining some of the sample XSL files, I figured out how to test various columns like @FLAG, @TITLE, @ID, @DONEDATESTRING, etc. Most of these are just the attribute names in the actual TDL file, so it's both intuitive and obvious.

But I can't seem to figure out how to get the TAGS column, or any sort of custom columns. There has to be a way, right? Is there a documentation for the XSL attributes anywhere that simply lists all of the attributes/columns and what their corresponding XSL variables are?

An example of what I'm trying to do: I have a custom attribute in my TDL file called "hide" that is in "checkbox" mode. I want to be able to HIDE certain tasks from the XSL transformation by checking if this attribute is toggled. In the TDL file, this custom attribute looks like this:

Code: Select all

 <CUSTOMATTRIB ID="CUST_HIDE" VALUE="+"/>
But I have no idea how to test for this in the XSL file.

I would also be ok with using the default TAGS column, and simply adding a "hide" tag... but I can't figured out how test for TAGS in XSL either! I tried @TAG and @TAGS, but neither worked. But testing for @COMMENTS works for comments, and they're structured in the same way tags are in the TDL file... I'm stumped, and some basic documentation would really help.

I couldn't find any example XSL files that rendered the TAGS attribute as an example.

Temporarily, I guess I'll have to resort to using @STATUS and setting it to "hide" or something, since the @STATUS column seems to work pretty reliably in XSL. Or maybe set RISK to 10, and just interpret that as "hide this task" -- but this is just a hacky workaround, and I would then lose the ability to use the STATUS or RISK columns properly in the future if I needed them.

Anyone have any documentation/list on all of the XSL attributes?

User avatar
abstr
Site Admin
Posts: 369
Joined: Sun Jul 28, 2019 12:22 pm

Re: Is there documentation of XSL attributes?

Post by abstr » Tue Jan 19, 2021 11:12 pm

Welcome James.

Way back I bought a thick reference book on XSLT but the subject matter proved no less impenetrable with the book than without, even though I am a programmer by trade. There's something about meta-programming that I struggle with.

However, you may find this CodeProject article helpful...

ps. If you do make progress I would be grateful if you could feed the knowledge back into the software either by way of a 'How To' in the 'Tips and Tricks' forum or by providing an example stylesheet I could ship with the download (or make otherwise available).

JamesO2
Posts: 6
Joined: Sun Jan 03, 2021 7:18 pm

Re: Is there documentation of XSL attributes?

Post by JamesO2 » Thu Jan 21, 2021 11:41 am

I see. So XSLT is a as confusing for you as it is for me! Haha. Alright, I might just start documenting what I figure out and post in the future about it.

Well, I've done some tinkering in the past few hours, and may have finally figured it out.

So in a TDL file, <TAG> are a nested element of the parent <TASK>, the same way <COMMENTS> are stored. Here's a stripped down example:

Code: Select all

<TASK ID="8" TITLE="Sub Task 1 - With TAGS">
    <COMMENTS>A comment goes here.</COMMENTS>
    <TAG>testing</TAG>
    <TAG>tag1</TAG>
    <TAG>tag2</TAG>
    <TAG>tagA</TAG>
    <TAG>tagB</TAG>
</TASK>
So I tried to do the same method to print out a <COMMENTS> element but for <TAG> instead, like this:

Code: Select all

<div class="taskTags">
	<xsl:if test="TAG">
		<xsl:value-of select="TAG"/>
	</xsl:if>
</div>
But this will only print out the very first <TAG> element it finds and ignore the rest. Which is a problem if your task has multiple tags. So i found out you can run a for-each loop to get each of the tags:

Code: Select all

<!-- 43 - ATTEMPT TO RENDER TAGS AS TEXT -->
<div class="taskTags">
	<xsl:for-each select="TAG">
		<xsl:text>FOUND TAG: </xsl:text>
		<xsl:value-of select="."/>
		<br/>
	</xsl:for-each>
</div>
The 'select="."' part apparently will select whatever it finds within the current element, since we're already inside of the <TAG>. This yields the desired result in the HTML output:

Code: Select all

FOUND TAG: testing
FOUND TAG: tag1
FOUND TAG: tag2
FOUND TAG: tagA
FOUND TAG: tagB
It will print out each <TAG> on its own line, inside the <div> I created for my task. You could replace this with commas, or any other separator you choose. This also works for any other task that has multiple nested elements with the same name, like for <FILEREFPATH> (which is the File Link column in TDL).

Now I just need to figure out if I can test the contents of a <TAG> while inside the for-each loop, that way we can filter out tasks by their TAGs.
EDIT: I figured this part out too. This stack-overflow post helped: https://stackoverflow.com/questions/178 ... an-element

You can test if the value of your tag matches specific text like this:

Code: Select all

		<!-- 43 - ATTEMPT TO RENDER TAGS AS TEXT -->
		<td class="taskTags">
			<xsl:for-each select="TAG">
				<xsl:text>TAG: </xsl:text>
				<xsl:value-of select="."/>
				<br/>
				<xsl:if test=".='tag2'">
					<xsl:text>IF TEST: </xsl:text>
					<xsl:value-of select="." />
					<br/>
				</xsl:if>
			</xsl:for-each>
		</td>
What the above does is simply look for a tag value that matches 'tag2' then prints out "IF TEST: tag2" -- but you could replace these two lines with anything, like applying a CSS class, or changing the font color when it matches a specific tag.

I'll probably write up a more proper how-to guide on this later, when I finish my example XSLT sheet. Thanks for the CodeProject link, it helped put some things in perspective.

User avatar
abstr
Site Admin
Posts: 369
Joined: Sun Jul 28, 2019 12:22 pm

Re: Is there documentation of XSL attributes?

Post by abstr » Fri Jan 22, 2021 5:30 am

Many thanks James for the effort you put into explaining this, I found your reasoning very intelligible.

User avatar
abstr
Site Admin
Posts: 369
Joined: Sun Jul 28, 2019 12:22 pm

Re: Extracting multiple <TAG> values from the tasklist

Post by abstr » Fri Jan 22, 2021 5:59 am

One of the things that you might have already figured out (or the sample stylesheets already do it) is how to only output a variable if it exists for the specified task.

eg. Imagine that you want to display each attribute's values on one line:

Code: Select all

Tags: tag1, tag2, tag3
Categories: cat1, cat2, cat3
but you don't want the line to be output at all if the task attribute has no values.

How does one incorporate a test for the first value prior to running the loop to extract all the values?

If I'm not being clear please say so.

JamesO2
Posts: 6
Joined: Sun Jan 03, 2021 7:18 pm

Re: Extracting multiple <TAG> values from the tasklist

Post by JamesO2 » Fri Jan 22, 2021 9:27 am

How does one incorporate a test for the first value prior to running the loop to extract all the values?
This part, I'm not sure yet. I'm kinda just figuring this out as I go. I know a bit about HTML, XML, programming in general, but I'm no expert.

To test for the first value, I tried wrapping the whole for-each loop in an if-test element... but that didn't work. The resulting HTML element just ended up being blank. Couldn't find a way to make that work. So that's why I wrote it how I did above.

If a task doesn't have any <TAG> children, the for-each loop still seems to work on its own without any "test" needed (it doesn't break or cause any errors, at least). So maybe the for-each loop has some sort of error-checking built in? Maybe if the for-each finds the element count = 0, it produces the same result as if you ran a <xsl:if test="*"> that didn't find anything. I don't know if this produces a result of "null" or "NaN" or "0" or "" (empty string) -- I have no idea what is happening internally when the for-each loop doesn't find any matching elements, but it seems to just move forward anyway without errors.

This would make sense, I think. Since the for-each loop runs "for each" element it finds, the loop itself must "know" how many "each" exist. Otherwise it wouldn't be a for-each loop, it'd be a regular for-while loop (which can run infinitely). And I don't think XSL even has for-while loops.

I ran this XSLT on a larger TDL file of mine with over 1000 tasks (some with tags, most have no tags), and the XSLT worked fine, no errors. Any task with TAGS would have them written to the HTML file under the proper <td> elements I assigned it, and any tasks that didn't have TAGS would just leave that area blank and proceed to the next task.

This is as far as I've figured out so far. I'm not sure if my XSL syntax is "correct" or "efficient" or would be considered "best practices" or not -- but it seems to work and produce a working HTML file for my purposes, and that's good enough for me.

I did a little searching on W3C, and it seems like you don't need to "test" a for-each loop. The for-each loop will "crawl the XML tree" and seems to naturally end when it hits the last element in your selection scope. https://www.w3schools.com/xml/xsl_for_each.asp

The page I just linked also shows an example of filtering a list by certain criteria (basically the same thing I did for TDL's <TAG> elements).

I did a bit more tinkering, and apparently you can filter a for-each loop with AND/OR as well.

Go to this link https://www.w3schools.com/xml/tryxslt.a ... log_filter , and copy/paste the bellow code into the right window.

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
  <html>
  <body>
    <table border="1">
      <tr bgcolor="#9acd32">
        <th>Title</th>
        <th>Artist</th>
        <th>Country</th>
      </tr>
      <xsl:for-each select="(catalog/cd[artist='Bob Dylan' or country='UK'])">
      <tr>
        <td><xsl:value-of select="title"/></td>
        <td><xsl:value-of select="artist"/></td>
        <td><xsl:value-of select="country"/></td>
      </tr>
      </xsl:for-each>
    </table>
  </body>
  </html>
</xsl:template>
</xsl:stylesheet>
This will only print out any musician named 'Bob Dylan' OR any musician from the UK. The key line I changed is here:

Code: Select all

      <xsl:for-each select="(catalog/cd[artist='Bob Dylan' or country='UK'])">
That's all that needs to be modified in order to filter multiple criteria. So it's apparently possible to set up some complex filtering with a single for-each loop. Considering I was able to filter by 'artist' or 'country in one loop, you could probably do the same with a TDL file, and use a single for-each loop to check for multiple TAG, CATEGORY, and FILEREFPATH elements all at once.

Post Reply