-1

I have an XML that I'd like to process with XSL to count elements. I would like to count the term names that have the same parent concept/ID as the desc records (that are not type concept) with their concept/ID attribute.

XML

<rdf>
    <desc type = "a">
        <concept ID = "#1"/>
    </desc>

    <desc type = "b">
        <concept ID = "#2"/>
    </desc>

    <desc type = "concept">
        <ID>1</ID>
        <term>
            <info>
                <name>apple</name>
                <ID>1</ID>
            </info>
        </term>
        <term>
            <info>
                <name>pear</name>
                <ID>2</ID>
            </info>
        </term>
    </desc>

    <desc type = "concept">
        <ID>2</ID>
        <term>
            <info>
                <name>chocolate</name>
                <ID>1</ID>
            </info>
        </term>
    </desc>
</rdf>

So for this XML the program could count the term names using all desc elements that have an attribute type value "a" for example, and it would use the #1 value to lookup the desc element (type concept) with an ID element with value 1 (would need to delete the '#') and count all term names for each concept that matches this ID.

Sean2148
  • 365
  • 1
  • 3
  • 13
  • 1
    So you want to get output of how many elements a desc/concept has in the following xml? In that case 2 numbers like: id='1' 2 elements, id='2': 1 element? I could show you how to do this in xslt, but I have no clue of python xml libraries. If you want to do it without an xml library, I would suggest making a list of objects with concepts in them which contain a list of elements. The solution of the resulting datastructure would look like concepts.elements.size – Christian Mosz Apr 09 '20 at 08:23
  • I have an XSL stylsheet that uses a key to achieve this selection, but I could not find a simple XPath expression that could reliably increment an XSL variable in a for-loop, unfortunately. – Sean2148 Apr 09 '20 at 09:04
  • 1
    You cannot increment variables in xsl unless you use recursion, which is a small hack. Call your template with a and after one operation call it again with a new updated param. This may get a bit tricky, due to the circumstance, that you have to save what to process in another param or what you have processed already. I would recommend you using the count() function. Use something like `` Simply loop that statement with different ids and you get your desired output with a bit of text formatting. – Christian Mosz Apr 09 '20 at 09:45
  • Okay I found an example of what you are talking about here: https://stackoverflow.com/questions/9608432/incrementing-and-checking-the-counter-variable-in-xslt/13701546 the last answer by "MiMo" shows how to increment a value but uses an if statement to check to stop the recursion when the count variable reaches a certain number, how would I do this but instead checking recurring until there's no more elements left to process? I'll try with the XPath count function – Sean2148 Apr 09 '20 at 11:25
  • 1
    You should provide an xslt and I would modify it for you. But a parameter for keeping track of the progress would be for example the id. If you pass the ID of the current element you can always call the template again with '' until there is no such element. Basically you search for your element and progress further from it. Inside the template you could use as mentioned something like `` – Christian Mosz Apr 09 '20 at 11:36
  • I've provided just the selection I use, but still having a hard time using parameters to get the count – Sean2148 Apr 09 '20 at 11:59
  • At this point I'll create another question I think – Sean2148 Apr 09 '20 at 15:10

2 Answers2

1
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="no"/>

<xsl:template match="/">
    <xsl:apply-templates select="//desc[concept/@ID]"/>
</xsl:template>

<xsl:template match="desc">
    <xsl:variable name="id" select="concept/@ID"/>
    <xsl:variable name="formatId" select="translate(normalize-space(translate($id, '#', ' ')),' ',',')"/>
    <xsl:value-of select="$id"/>
    <xsl:text>: </xsl:text> 
    <xsl:value-of select="count(//desc[@type='concept'][ID = $formatId]/term)"/>
    <xsl:text>, </xsl:text>                  
</xsl:template>
</xsl:stylesheet>

Adjust this to your needs. Output looks like:

#1: 2, #2: 1, 
Christian Mosz
  • 543
  • 2
  • 12
  • 1
    Using a key to resolve the cross-reference is a better idea. – michael.hor257k Apr 09 '20 at 12:46
  • I find your method works very well for finding all types of terms. However can I ask when you call "apply-templates", what is really happening? I had the understanding when it is called it will match all templates that match the XPath expression, but the other template is only matching "desc" elements. The more I use XSLT the more I realise how much I don't understand about it! – Sean2148 Apr 09 '20 at 13:00
  • Normally the is the same as . That meant it will try to find a matching template for every element which is a child of the current element. What I did by modifying the select statement is telling the xslt to only search for specific templates to apply. The XPath explained: '/' means root. A '//' means all elements beneath (children, grandchildren). '//desc' means only elements with 'desc' name. [xyz] are conditions. So only desc elements with a child::concept which has an @ID attribute. Basically I did only apply your first few nodes. – Christian Mosz Apr 09 '20 at 13:13
  • And because I only applied specific nodes (one type of node), I did just need that one template. If it would match other nodes, where a template is missing, it would takeen a 'default template'. That consists of a simple copy of the current element. So if there is no matching template, your input xml gets copied and printed. – Christian Mosz Apr 09 '20 at 13:16
  • So if the second template's match value was also "//desc[concept/@ID]", there would be no difference? Or I'm understanding wrong – Sean2148 Apr 09 '20 at 13:25
  • template match="desc[concept/@ID]" would hit the same elements I chose to select. The '//' was just to get EVERY desc in the document, since my current scope could be not the root (it is not the root in the count case. the current scorpe is the node you are in and every expression is evaluated from that path). 'descendants::desc' would also match every desc beneath the current element. I would suggest you to look up how Xpaths work ;) What you could do to specify a parent for example in a template match is desc[ancestor::nodename] or desc[parent::nodename]. – Christian Mosz Apr 09 '20 at 13:46
  • Ahh thanks for your explanation. I had to test it myself, for some reason I just assumed the apply-templates match had to be the exact same path of an existing template. I will have to do a little more research with XPath haha. Is there a way to easily adjust your code so that the terms are selected based on the type of desc elements which have the concept child? Like if I only want to see the count of terms that desc/concept/@ID = "a" which links to the desc record with attr "concept" which has an equal ID – Sean2148 Apr 09 '20 at 14:57
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/211296/discussion-between-sean2148-and-christian-mosz). – Sean2148 Apr 09 '20 at 15:23
0

Not a Python solution, but will select all terms that correspond to the desc element of type 'b'. (no count)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<xsl:output method="xml"/>
<xsl:key name = "terms" match = "desc[@type = 'concept']" use = "ID"/>


<xsl:template match="/">
    <xsl:apply-templates select = "rdf/desc[@type != 'concept' and @type != 'a']"/>
</xsl:template>

<xsl:template match = "desc[@type != 'concept' and @type != 'a']">

    <xsl:variable name = "test" select = "key('terms', substring(concept/@ID, 2))"/>
        <xsl:for-each select = "$test/term/info">
            <xsl:value-of select = "name/text()"/>
            <xsl:text>&#xA;</xsl:text>
        </xsl:for-each>
</xsl:template>
Sean2148
  • 365
  • 1
  • 3
  • 13