2

I guess I need your help.

I have an XML file structured this way:

<root>
    <source id="1"/>
    <source id="2"/>
    <source ..... />
    <element id="e1">
        <connection from_id="1">
    </element>
    <element id="e7">
        <connection from_id="1">
    </element>
    <element id="e2">
        <connection from_id="e2">
    </element>
    <element id="e3">
        <connection from_id="e2">
    </element>
    <element id="e4">
        <connection from_id="e3">
    </element>
    <element id="e5">
        <connection from_id="2">
    </element>
    <element id="e6">
        <connection from_id="3">
    </element>
</root>

Now, what I've tried to accomplish is to count() the number of nodes (more specific: element-nodes), that are somehow connected to each source-node, even if they are connected via another element node. So for this example: source-node 1: 5 source-node 2: 1 source-node 3: 1

If tried multiple approaches, including functions and recursion, but I wasn't able to manage this task. From my daily java-programmers perspective I just miss a variable or so to save some intermediate results.

So my question: How can I do this without any intermediate results?

Katarn
  • 23
  • 2
  • You can have [recursive loops](http://stackoverflow.com/q/3709092/11683) and you can [store intermediate results in a variable](http://stackoverflow.com/a/3881931/11683). – GSerg Nov 23 '16 at 08:38

1 Answers1

1

The first question you need to ask yourself is: is there a need to detect cycles in the data, or are you prepared for your program to go into an infinite loop if cycles exist? Detecting cycles makes the problem a bit more difficult, though it can be added easily enough once you've got the basic structure working.

Next, I may have misunderstood the exact requirement because I can't see how the count for source node 1 comes to 5. As I see it, elements e1 and e7 are connected to source node 1 and neither of these has any further connections.

You haven't said whether it's XSLT 1.0 or 2.0 but since there's a "Saxon" tag on the question let's assume 2.0 (which is going to be much easier than 1.0). The right tag here would be "xslt 2.0" rather than "saxon" since the problem isn't Saxon-specific.

You basically want to start with a function

<xsl:function name="f:directConnections" as="element()*">
  <xsl:param name="from" as="element()"/>
  <xsl:sequence select="key('with-from-id', $from/@id, $from/root())"/>
</xsl:function>

having declared

<xsl:key name="with-from-id" match="connection" select="@from_id"/>

Then the transitive closure of this function is:

<xsl:function name="f:transitiveConnections" as="element()*">
  <xsl:param name="from" as="element()"/>
  <xsl:variable name="direct" select="f:directConnections($from)"/>
  <xsl:sequence select="$direct/(. | f:transitiveConnections(.))"/>
</xsl:function>

And then the count of connections for a given node $N is count(f:transitiveConnections($N)).

All that remains is cycle detection. To achieve this, add an extra parameter to the function containing all the nodes 'en route' to the node whose connections you are finding, and use "except" to avoid following links from any node that is already "en route".

<xsl:function name="f:transitiveConnections" as="element()*">
  <xsl:param name="from" as="element()"/>
  <xsl:param name="enRoute" as="element()*"/>
  <xsl:variable name="direct" 
                select="f:directConnections($from) except $enRoute"/>
  <xsl:sequence select="$direct/(. | f:transitiveConnections(., $enRoute | .))"/>
</xsl:function> 
Michael Kay
  • 156,231
  • 11
  • 92
  • 164