using XSL to count elements

Here should go questions about transforming XML with XSLT and FOP.
T_BugReporter
Posts: 3
Joined: Fri Oct 15, 2021 7:08 am

using XSL to count elements

Post by T_BugReporter »

Hello! I've been trying for several weeks to learn XSL and use it to manipulate some data we receive in XML format, but I'm not finding a whole lot of resources for this language on the Internet (so if you've seen this same question posted somewhere else, I apologize for the duplication). Here's an example of the data files we receive (scrubbed of confidential information), the XSL code I came up with to manipulate it, and the resulting output:

Sample-Input.xml

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Entry1</key>
    <dict>
        <key>EntryCollectionA</key>
        <dict>
            <key>ECA Identifier</key>
            <dict>
                <key>ECADetail1</key>
                <string>The quick brown fox</string>
                <key>ECADetail2</key>
                <integer>1112</integer>
            </dict>
        </dict>
        <key>EntryCollectionB</key>
        <dict>
            <key>Identifier for the first ECB</key>
            <dict>
                <key>ECBMinorDetail1</key>
                <false/>
                <key>ECBMinorDetail2</key>
                <dict>
                    <key>EBC2's name</key>
                    <string>jumps over the lazy dog.</string>
                </dict>
                <key>ECBImportantDetail3</key>
                <string>three</string>
                <key>ECBImportantDetail4</key>
                <string>four</string>
                <key>ECBImportantDetail5</key>
                <string>five</string>
                <key>ECBIrrelevantToMe</key>
                <data>
                yadda+yadda+yadda/yadda+yaddayaddayaddayadda
                </data>
                <key>ECBIrrelevantToo</key>
                <integer>987654321</integer>
            </dict>
            <key>second ECB for Entry1 has a different title</key>
            <dict>
                <key>ECBMinorDetail1</key>
                <false/>
                <key>ECBMinorDetail2</key>
                <dict>
                    <key>a name for Ecb2 goes here</key>
                    <string>vaults above the recalcitrant canine.</string>
                </dict>
                <key>ECBImportantDetail3</key>
                <string>is the number of the count,</string>
                <key>ECBImportantDetail4</key>
                <string>and what the number of the count shall be</string>
                <key>ECBImportantDetail5</key>
                <string>is right out.</string>
                <key>ECBIrrelevantToMe</key>
                <data>
                MORE/inde+cipher+able/and+IRReleVaNTinbase64
                </data>
                <key>ECBIrrelevantToo</key>
                <integer>19283746</integer>
            </dict>
            <key>Entry1 third ECB</key>
            <dict>
                <key>ECBMinorDetail1</key>
                <true/>
                <key>ECBMinorDetail2</key>
                <dict>
                    <key>Ecb3 has a name</key>
                    <string>is intimidated by the sleeping dragon.</string>
                </dict>
                <key>ECBImportantDetail3</key>
                <string>is a magic number</string>
                <key>ECBImportantDetail4</key>
                <string>is the legs in a zoo</string>
                <key>ECBImportantDetail5</key>
                <string>ready or not, here I come!</string>
                <key>ECBIrrelevantToMe</key>
                <data>
                WiLlY0USt1lLFe3dMe/WhEnImSiXtYfOuR/60plUsF0R
                </data>
                <key>ECBIrrelevantToo</key>
                <integer>6406406566</integer>
            </dict>
        </dict>
        <key>ImportantDatum6</key>
        <string>what this data pertains to</string>
        <key>ImportantDatum7</key>
        <string>reference code</string>
        <key>ImportantDatum8</key>
        <string>one,two</string>
        <key>whoCares</key>
        <integer>1</integer>
        <key>iDo-aboutThisOne</key>
        <string>999</string>
        <key>ANDagain</key>
        <string>eeny</string>
        <key>moreSillyData</key>
        <data>
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        </data>
        <key>bucketObits</key>
        <string>more characters</string>
        <key>bitObuckets</key>
        <string>textity textext</string>
        <key>amount</key>
        <integer>66645714</integer>
        <key>code</key>
        <integer>765432109</integer>
        <key>subcode</key>
        <integer>828474104</integer>
    </dict>
    <key>second entry</key>
    <dict>
        <key>EntryCollectionA</key>
        <dict>
            <key>5564738291</key>
            <dict>
                <key>wardrobe</key>
                <string>lionWitch</string>
                <key>buuleeun</key>
                <false/>
            </dict>
        </dict>
        <key>EntryCollectionB</key>
        <dict>
            <key>2's ECB name</key>
            <dict>
                <key>tuuleeun</key>
                <false/>
                <key>batchCodes</key>
                <dict>
                    <key>5564738291</key>
                    <data>
                    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                    </data>
                </dict>
                <key>ImportantDatum7</key>
                <string>sequence identifier</string>
                <key>ImportantDatum8</key>
                <string>three,four</string>
                <key>iDo-aboutThisOne</key>
                <string>thuh-ree</string>
                <key>moreSillyData</key>
                <data>
                AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                </data>
                <key>subcode</key>
                <integer>841391812</integer>
            </dict>
        </dict>
        <key>ImportantDatum6</key>
        <string>another collection of data</string>
        <key>ImportantDatum7</key>
        <string>identifier string</string>
        <key>ImportantDatum8</key>
        <string>buckle my shoe</string>
        <key>whoCares</key>
        <integer>1</integer>
        <key>iDo-aboutThisOne</key>
        <string>again</string>
        <key>ANDagain</key>
        <string>meeny</string>
        <key>moreSillyData</key>
        <data>
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        </data>
        <key>bucketObits</key>
        <string>say what you want</string>
        <key>amount</key>
        <integer>51736576</integer>
        <key>code</key>
        <integer>456789012</integer>
        <key>subcode</key>
        <integer>841391812</integer>
    </dict>
    <key>another main entry</key>
    <dict>
        <key>EntryCollectionA</key>
        <dict>
            <key>5564738291</key>
            <dict>
                <key>wardrobe</key>
                <string>lionWitch</string>
                <key>buuleeun</key>
                <false/>
            </dict>
        </dict>
        <key>ImportantDatum6</key>
        <string>my third data set</string>
        <key>ImportantDatum7</key>
        <string>ID code</string>
        <key>ImportantDatum8</key>
        <string>five,six</string>
        <key>whoCares</key>
        <integer>1</integer>
        <key>iDo-aboutThisOne</key>
        <string>tenAgain</string>
        <key>ANDagain</key>
        <string>miney</string>
        <key>moreSillyData</key>
        <data>
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        </data>
        <key>bucketObits</key>
        <string>more to say</string>
        <key>amount</key>
        <integer>1576960</integer>
        <key>code</key>
        <integer>1248163264</integer>
        <key>subcode</key>
        <integer>842184218</integer>
    </dict>
    <key>final sample</key>
    <dict>
        <key>EntryCollectionA</key>
        <dict>
            <key>5564738291</key>
            <dict>
                <key>wardrobe</key>
                <string>lionWitch</string>
                <key>buuleeun</key>
                <false/>
            </dict>
        </dict>
        <key>EntryCollectionB</key>
        <dict>
            <key>ECB (but for #4?)</key>
            <dict>
                <key>tuuleeun</key>
                <false/>
                <key>batchCodes</key>
                <dict>
                    <key>5564738291</key>
                    <data>
                    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                    </data>
                </dict>
                <key>ImportantDatum7</key>
                <string>name, of sorts</string>
                <key>ImportantDatum8</key>
                <string>nine,ten</string>
                <key>iDo-aboutThisOne</key>
                <string>tenAgain</string>
                <key>moreSillyData</key>
                <data>
                AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                </data>
                <key>subcode</key>
                <integer>84218421</integer>
            </dict>
            <key>four's second ECB</key>
            <dict>
                <key>tuuleeun</key>
                <false/>
                <key>batchCodes</key>
                <dict>
                    <key>5564738291</key>
                    <data>
                    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                    </data>
                </dict>
                <key>ImportantDatum7</key>
                <string>three is also 7?</string>
                <key>ImportantDatum8</key>
                <string>eight is also four</string>
                <key>iDo-aboutThisOne</key>
                <string>cra-zee</string>
                <key>moreSillyData</key>
                <data>
                AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                </data>
                <key>subcode</key>
                <integer>474828131</integer>
            </dict>
            <key>369,121518</key>
            <dict>
                <key>tuuleeun</key>
                <false/>
                <key>batchCodes</key>
                <dict>
                    <key>5564738291</key>
                    <data>
                    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                    </data>
                </dict>
                <key>ImportantDatum7</key>
                <string>code name for this item</string>
                <key>ImportantDatum8</key>
                <string>close the door</string>
                <key>iDo-aboutThisOne</key>
                <string>heaven</string>
                <key>moreSillyData</key>
                <data>
                AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                </data>
                <key>subcode</key>
                <integer>963852741</integer>
            </dict>
        </dict>
        <key>ImportantDatum6</key>
        <string>and here's another group of data</string>
        <key>ImportantDatum7</key>
        <string>code name for this item</string>
        <key>ImportantDatum8</key>
        <string>do it again</string>
        <key>whoCares</key>
        <integer>1</integer>
        <key>iDo-aboutThisOne</key>
        <string>heaven</string>
        <key>ANDagain</key>
        <string>moe</string>
        <key>moreSillyData</key>
        <data>
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        </data>
        <key>bucketObits</key>
        <string>piece of text</string>
        <key>amount</key>
        <integer>19189760</integer>
        <key>code</key>
        <integer>567890123</integer>
        <key>supportedDeviceTypes</key>
        <integer>7</integer>
        <key>subcode</key>
        <integer>303030303</integer>
    </dict>
</dict>
</plist>
Sample-Code.xsl

Code: Select all

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
  <html>
  <body>
  <h2>Heading and preliminary stuff</h2>
   <table>
    <tr>
      <th bgcolor="#336699">Main</th>
      <th>count</th>
      <th bgcolor="#3366cc">EntryCollectionB</th>
      <th bgcolor="#339966">EntryCollectionB/<br/>
        ECBImportantDetail3</th>
      <th bgcolor="#3399cc">EntryCollectionB/<br/>
        ECBImportantDetail4</th>
      <th bgcolor="#33cc66">EntryCollectionB/<br/>
        ECBImportantDetail5</th>
      <th bgcolor="#33cc99">ImportantDatum6</th>
      <th bgcolor="#663399">ImportantDatum7</th>
      <th bgcolor="#6633cc">ImportantDatum8</th>
      <th bgcolor="#669933">iDo-aboutThisOne</th>
      <th bgcolor="#6699cc">ANDagain</th>
      <th bgcolor="#66cc33">amount</th>
    </tr>
  <xsl:for-each select="plist/dict/key">
    <tr>
      <td bgcolor="#66cc99"><xsl:value-of select="." /></td>
      <td><xsl:value-of select="count(following-sibling::dict/key[text()='EntryCollectionB']/
                        following-sibling::dict/key)" /></td>
      <td bgcolor="#993366"><xsl:value-of select="following-sibling::dict/key[text()='EntryCollectionB']/
                        following-sibling::dict/key" /></td>
      <td bgcolor="#9933cc"><xsl:value-of select="following-sibling::dict/key[text()='EntryCollectionB']/
                        following-sibling::dict/key/
                        following-sibling::dict/key[text()='ECBImportantDetail3']/
                        following-sibling::string" /></td>
      <td bgcolor="#996633"><xsl:value-of select="following-sibling::dict/key[text()='EntryCollectionB']/
                        following-sibling::dict/key/
                        following-sibling::dict/key[text()='ECBImportantDetail4']/
                        following-sibling::string" /></td>
      <td bgcolor="#9966cc"><xsl:value-of select="following-sibling::dict/key[text()='EntryCollectionB']/
                        following-sibling::dict/key/
                        following-sibling::dict/key[text()='ECBImportantDetail5']/
                        following-sibling::string" /></td>
      <td bgcolor="#99cc33"><xsl:value-of select="following-sibling::dict/key[text()='ImportantDatum6']/
                        following-sibling::string" /></td>
      <td bgcolor="#99cc66"><xsl:value-of select="following-sibling::dict/key[text()='ImportantDatum7']/
                        following-sibling::string" /></td>
      <td bgcolor="#cc3366"><xsl:value-of select="following-sibling::dict/key[text()='ImportantDatum8']/
                        following-sibling::string" /></td>
      <td bgcolor="#cc3399"><xsl:value-of select="following-sibling::dict/key[text()='iDo-aboutThisOne']/
                        following-sibling::string" /></td>
      <td bgcolor="#cc6633"><xsl:value-of select="following-sibling::dict/key[text()='ANDagain']/
                        following-sibling::string" /></td>
      <td bgcolor="#cc6699"><xsl:value-of select="following-sibling::dict/key[text()='amount']/
                        following-sibling::integer" /></td>
    </tr>
    </xsl:for-each>
  </table>
  </body>
  </html>
 </xsl:template>
</xsl:stylesheet>
Sample-Output.html

Code: Select all

<!DOCTYPE HTML><html>
   <body>
      <h2>Heading and preliminary stuff</h2>
      <table>
         <tr>
            <th bgcolor="#336699">Main</th>
            <th>count</th>
            <th bgcolor="#3366cc">EntryCollectionB</th>
            <th bgcolor="#339966">EntryCollectionB/<br>
               ECBImportantDetail3</th>
            <th bgcolor="#3399cc">EntryCollectionB/<br>
               ECBImportantDetail4</th>
            <th bgcolor="#33cc66">EntryCollectionB/<br>
               ECBImportantDetail5</th>
            <th bgcolor="#33cc99">ImportantDatum6</th>
            <th bgcolor="#663399">ImportantDatum7</th>
            <th bgcolor="#6633cc">ImportantDatum8</th>
            <th bgcolor="#669933">iDo-aboutThisOne</th>
            <th bgcolor="#6699cc">ANDagain</th>
            <th bgcolor="#66cc33">amount</th>
         </tr>
         <tr>
            <td bgcolor="#66cc99">Entry1</td>
            <td>7</td>
            <td bgcolor="#993366">Identifier for the first ECB</td>
            <td bgcolor="#9933cc">three</td>
            <td bgcolor="#996633">four</td>
            <td bgcolor="#9966cc">five</td>
            <td bgcolor="#99cc33">what this data pertains to</td>
            <td bgcolor="#99cc66">reference code</td>
            <td bgcolor="#cc3366">one,two</td>
            <td bgcolor="#cc3399">999</td>
            <td bgcolor="#cc6633">eeny</td>
            <td bgcolor="#cc6699">66645714</td>
         </tr>
         <tr>
            <td bgcolor="#66cc99">second entry</td>
            <td>4</td>
            <td bgcolor="#993366">2's ECB name</td>
            <td bgcolor="#9933cc"></td>
            <td bgcolor="#996633"></td>
            <td bgcolor="#9966cc"></td>
            <td bgcolor="#99cc33">another collection of data</td>
            <td bgcolor="#99cc66">identifier string</td>
            <td bgcolor="#cc3366">buckle my shoe</td>
            <td bgcolor="#cc3399">again</td>
            <td bgcolor="#cc6633">meeny</td>
            <td bgcolor="#cc6699">51736576</td>
         </tr>
         <tr>
            <td bgcolor="#66cc99">another main entry</td>
            <td>3</td>
            <td bgcolor="#993366">ECB (but for #4?)</td>
            <td bgcolor="#9933cc"></td>
            <td bgcolor="#996633"></td>
            <td bgcolor="#9966cc"></td>
            <td bgcolor="#99cc33">my third data set</td>
            <td bgcolor="#99cc66">ID code</td>
            <td bgcolor="#cc3366">five,six</td>
            <td bgcolor="#cc3399">tenAgain</td>
            <td bgcolor="#cc6633">miney</td>
            <td bgcolor="#cc6699">1576960</td>
         </tr>
         <tr>
            <td bgcolor="#66cc99">final sample</td>
            <td>3</td>
            <td bgcolor="#993366">ECB (but for #4?)</td>
            <td bgcolor="#9933cc"></td>
            <td bgcolor="#996633"></td>
            <td bgcolor="#9966cc"></td>
            <td bgcolor="#99cc33">and here's another group of data</td>
            <td bgcolor="#99cc66">code name for this item</td>
            <td bgcolor="#cc3366">do it again</td>
            <td bgcolor="#cc3399">heaven</td>
            <td bgcolor="#cc6633">moe</td>
            <td bgcolor="#cc6699">19189760</td>
         </tr>
      </table>
   </body>
</html>
(I know I have more problems with this code than just the one I asked about here, but I have a feeling that once I understand what I'm doing wrong with this problem, the other pieces will fall into place.)

The answers I'm expecting to get in the count column should be 3, 1, 0, and 3; but it seems to be counting not just descendants, but all the matching data points from the current position to the end - and recounting them every time the position changes.

My question is: Why is it that the "following-sibling" key word that I'm using is finding not only siblings (nodes having the same parent node), but also "cousins" (nodes at the same level, regardless of which node is the parent)? And, of course, how do I fix it?
Radu
Posts: 9418
Joined: Fri Jul 09, 2004 5:18 pm

Re: using XSL to count elements

Post by Radu »

Hi,

So inside the for-each:

Code: Select all

 <xsl:for-each select="plist/dict/key">
you are in the context of a "key" element, for example let's say we are in the context of <key>Entry1</key>.
Then you evaluate this Xpath:

Code: Select all

<xsl:value-of select="count(following-sibling::dict/key[text()='EntryCollectionB']/
                                following-sibling::dict/key)" />
"following-sibling::dict/key[text()='EntryCollectionB']" will match three "key" nodes because there are 3 keys "<key>EntryCollectionB</key>" in the same file. And for each of these 3 nodes will return all key elements from the following-sibling "dict" elements "following-sibling::dict/key)".

To test XPath expressions in a certain context in Oxygen you can place for example the caret inside the " <key>Entry1</key>" element and then run the XPath using the XPath toolbar.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
T_BugReporter
Posts: 3
Joined: Fri Oct 15, 2021 7:08 am

Re: using XSL to count elements

Post by T_BugReporter »

After spending too much spare time reading everything I could find on the Internet that seems relevant, plus downloading and playing with several XML tools, I'm still confused - mainly because the XQuery expression that's giving me these bizarre answers is not much different from the other ones that are giving me the results I expect.

I'm going to try to explain my logic in constructing the "count()" expression in my Sample-Code.xsl file; I would appreciate having the flaws in this logic pointed out to me. (Apologies for the long lines below; it was the easiest way I could think of to illustrate my thought process.)

First off, I'm iterating over "plist/dict/key", which should (and, apparently, does) trigger four times for the Sample-Input.xml: on encountering "<key>Entry1</key>", "<key>second entry</key>", "<key>another main entry</key>", and "<key>final sample</key>". Now, for the moment, concentrating on "Entry1" and ignoring what it'll find on subsequent passes*, I output "value-of select='.'" ("Entry1") to the first column of the HTML table, and then ...

Code: Select all

	<!-- attempt to calculate a count, ... -->
<xsl:value-of select="count(                                                                                  )" />
	<!-- (working backwards thru the expression), specifically of "key"s ... -->
<xsl:value-of select="count(                                                                               key)" />
	<!-- that are children of "dict"s, ... -->
<xsl:value-of select="count(                                                                          dict/key)" />
	<!-- that have a "following-sibling" relationship ... -->
<xsl:value-of select="count(                                                       following-sibling::dict/key)" />
	<!-- to some (parent level) "key" ... -->
<xsl:value-of select="count(                                                   key/following-sibling::dict/key)" />
	<!-- having "EntryCollectionB" as its "text()" ... -->
<xsl:value-of select="count(                        key[text()='EntryCollectionB']/following-sibling::dict/key)" />
	<!-- that is a child of a "dict" ... -->
<xsl:value-of select="count(                   dict/key[text()='EntryCollectionB']/following-sibling::dict/key)" />
	<!-- that is a "following-sibling" ... -->
<xsl:value-of select="count(following-sibling::dict/key[text()='EntryCollectionB']/following-sibling::dict/key)" />
	<!-- of the "plist/dict/key" that's currently being worked on - "Entry1". -->
* Or maybe I shouldn't be ignoring that? Or I should write something else into the expression to insure that it does ignore that?

Where else am I going wrong?
Radu
Posts: 9418
Joined: Fri Jul 09, 2004 5:18 pm

Re: using XSL to count elements

Post by Radu »

Hi,

I'm afraid I do not have much time to look into this, maybe you can use our Oxygen XSLT Debugger perspective to step through the XML and maybe evaluate certain XPath expressions in a certain context.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
T_BugReporter
Posts: 3
Joined: Fri Oct 15, 2021 7:08 am

Re: using XSL to count elements

Post by T_BugReporter »

OMG - it took me two months, including countless hours reading, experimenting, and testing - but I've finally constructed a query that gets the results I was looking for:
The answers I'm expecting to get in the count column should be 3, 1, 0, and 3
Excerpt from my original XSL:

Code: Select all

      <td><xsl:value-of select="count(following-sibling::dict/
                        key[text()='EntryCollectionB']/
                        following-sibling::dict/key)" /></td>
My newly working modification:

Code: Select all

      <td><xsl:value-of select="count(following-sibling::dict[1]/
                        key[text()='EntryCollectionB']/
                        following-sibling::dict/key)" /></td>
I still don't understand why this three character addition is necessary, so my second question still stands:
Why is it that the "following-sibling" key word that I'm using is finding not only siblings (nodes having the same parent node), but also "cousins" (nodes at the same level, regardless of which node is the parent)?
However, given how much time and effort I've wasted in trying to understand this, I'm at the point of no longer caring why the code works, as long as it works. You can consider this question closed.
alex_jitianu
Posts: 1016
Joined: Wed Nov 16, 2005 11:11 am

Re: using XSL to count elements

Post by alex_jitianu »

Hello,

The following-sibling:* axis only matches on the brothers of the current node as opposed to following:* axis. Perhaps your behavior is related with the fact that you have a template that fires multiple times so the following-sibling:* expression is executed multiple times. Oxygen's XSLT Debugger should help if you execute the XSLT step by step.

Best regards,
Alex
Post Reply