Finding a Directly Acyclic Graph (DAG) Minimal Elements (Vertices) with XSLT / XPath?

I have an XML file that encodes a directed acyclic graph (DAG) that represents a partial order . Such graphs are useful for things like defining dependencies and finding critical paths . Curiously, my current application is designed to determine the dependencies of components for the build system , so the vertices are the components and edges defining the dependencies of compilation time. Here is a simple example:

<?xml version="1.0"?> <dag> <vertex name="A"> <directed-edge-to vertex="C"/> </vertex> <vertex name="B"> <directed-edge-to vertex="C"/> <directed-edge-to vertex="D"/> </vertex> <vertex name="C"> <directed-edge-to vertex="E"/> </vertex> <vertex name="D"> <directed-edge-to vertex="E"/> </vertex> <vertex name="E"> <directed-edge-to vertex="G"/> </vertex> <vertex name="F"> <directed-edge-to vertex="G"/> </vertex> <vertex name="G"/> </dag> 

This DAG can be done as follows:

http://iparelan.com/dag.png

I would like to apply an XSLT style sheet that creates another XML document file containing only the vertices corresponding to the minimum partial order elements . That is, those vertices that do not have incoming edges. The set of minimal vertices for the sample graph {A, B, F} . For my build dependency application, finding this set is valuable, because I know that if I create members of this set, then everything in my project will be built.

Here is my current style solution (I am running this using Xalan in Java using the Apache Ant xslt task). The key point is that the minimum vertex will not refer to any directed-edge-to elements:

 <?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" exclude-result-prefixes="xalan"> <xsl:output method="xml" indent="yes" xalan:indent-amount="4"/> <xsl:template match="dag"> <minimal-vertices> <xsl:for-each select="//vertex"> <xsl:if test="not(//vertex/directed-edge-to[@vertex=current()/@name])"> <minimal-vertex name="{@name}"/> </xsl:if> </xsl:for-each> </minimal-vertices> </xsl:template> </xsl:stylesheet> 

Applying this style sheet leads to the following conclusion (which I consider correct):

 <?xml version="1.0" encoding="UTF-8"?> <minimal-vertices> <minimal-vertex name="A"/> <minimal-vertex name="B"/> <minimal-vertex name="F"/> </minimal-vertices> 

The fact is that I am not completely satisfied with this decision. I am wondering if there is a way to combine select for-each and test from if with XPath syntax.

I want to write something like:

 <xsl:for-each select="//vertex[not(//vertex/directed-edge-to[@vertex=current()/@name])]"> 

But that does not do what I want, because the current() function does not refer to the nodes selected by the external //vertex expression.

Thus, my solution uses XPath 1.0 and XSLT 1.0 , although I am open to XPath 2.0 and XSLT 2.0 .

Here's Ant build script if you want:

 <?xml version="1.0"?> <project name="minimal-dag" default="default"> <target name="default"> <xslt in="dag.xml" out="minimal-vertices.xml" style="find-minimal-vertices.xsl"/> </target> <target name="dot"> <xslt in="dag.xml" out="dag.dot" style="xml-to-dot.xsl"/> </target> </project> 

The dot target generates Graphviz Dot language code to display the graph. Here is xml-to-dot.xsl :

 <?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" exclude-result-prefixes="xalan"> <xsl:output method="text"/> <xsl:template match="dag"> digraph { rankdir="BT"; node [style="filled", fillcolor="cyan", fontname="Helvetica"]; <xsl:apply-templates select="//directed-edge-to"/> } </xsl:template> <xsl:template match="directed-edge-to"> <xsl:value-of select="concat(ancestor::vertex/@name, '->', @vertex, ';')"/> </xsl:template> </xsl:stylesheet> 
+7
xpath xslt graph-theory directed-acyclic-graphs build-system
source share
2 answers

You can use the implicit existential XPath quantification for the = operator:

 <xsl:for-each select="//vertex[not(@name = //vertex/directed-edge-to/@vertex)]"> 

When you use any of the six comparison operators ( = ,! != , < , <= , > And >= ) to compare a node-set, the expression will return true if any node in node -set satisfies the condition. When comparing one node-set with another, the expression returns true if any node in the first node-set satisfies the condition compared to any node in the second node-set. XPath 2.0 introduces six new operators that do not perform this existential quantification ( eq , ne , lt , le , gt and ge ). But in your case, you will want to use " = " to get this existential quantification.

Note that you still want to use the not() function, as you did. Most of the time, it is best to avoid the != Operator. If you used it here instead of not() , then it will return true if there are any @vertex attributes that are not equal to the @name value, which is not your intention. (And if any node-set is empty, then it will return false, since comparing with empty node-sets always returns false.)

If you want to use eq instead, you will need to do something like this: separate the conditional from the iteration so that you can bind current() . But in XPath 2.0 you can do this in an expression:

 <xsl:for-each select="for $v in //vertex return $v[not(//directed-edge-to[@vertex eq $v/@name])]"> 

This is useful when your condition is not a simple equality comparison (and therefore, it cannot be quantified with " = "). For example: starts-with(@vertex, $v/@name) .

XPath 2.0 also has an explicit way to do existential quantification. Instead of the for statement above, we could write this:

 <xsl:for-each select="//vertex[not(some $e in //directed-edge-to satisfies @name eq $e/@vertex)]"> 

In addition to the " some " syntax, XPath 2.0 also provides the corresponding " every " syntax for performing universal quantification.

Instead of using for-each you can also use template rules that are more modular (and powerful):

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <minimal-vertices> <xsl:apply-templates/> </minimal-vertices> </xsl:template> <!-- Copy vertex elements that have no arrows pointing to them --> <xsl:template match="vertex[not(@name = //directed-edge-to/@vertex)]"> <minimal-vertex name="{@name}"/> </xsl:template> </xsl:stylesheet> 

Again, in this case, we rely on an existential quantification = .

XSLT 1.0 prohibits the use of the current() function in templates, i.e. in the match attribute, but XSLT 2.0 allows this. In this case, current() refers to the node that is currently being mapped. So in XSLT 2.0 we could write this (without using the for statement):

 <xsl:template match="vertex[not(//directed-edge-to[@vertex eq current()/@name])]"> 

Please note: this pattern essentially matches the expression you tried to use in for-each , but while it does not do what you want in for-each , it does what you want in the pattern (because current() binds to another).

Finally, I will add another variation that somehow simplifies the logic (removing not() ). This also applies to using XSLT 1.0:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <minimal-vertices> <xsl:apply-templates/> </minimal-vertices> </xsl:template> <!-- By default, copy vertex elements --> <xsl:template match="vertex"> <minimal-vertex name="{@name}"/> </xsl:template> <!-- But strip out vertices with incoming arrows --> <xsl:template match="vertex[@name = //directed-edge-to/@vertex]"/> </xsl:stylesheet> 

If you do not like the spaces displayed, add an empty rule for text nodes, so they will be deleted (overriding the default rule for text nodes that should copy them):

 <xsl:template match="text()"/> 

Or you can simply be more selective in which nodes you apply the templates to:

 <xsl:apply-templates select="/dag/vertex"/> 

Which approach you take, partly depends on taste, partly depends on the wider context of your stylesheet and the expected data (how much the input structure can be changed, etc.).

I know that I went beyond what you asked for, but I hope you at least find it interesting. :-)

+8
source share

One such XPath 1.0 expression is:

/*/vertex[not(@name = /*/vertex/directed-edge-to/@vertex)]

Then just put it in an XSLT stylesheet, such as :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="/"> <minimal-vertices> <xsl:for-each select= "/*/vertex[not(@name = /*/vertex/directed-edge-to/@vertex)]" > <minimal-vertex name="{@name}"/> </xsl:for-each> </minimal-vertices> </xsl:template> </xsl:stylesheet> 

When this style sheet is applied to the originally provided XML document :

 <dag> <vertex name="A"> <directed-edge-to vertex="C"/> </vertex> <vertex name="B"> <directed-edge-to vertex="C"/> <directed-edge-to vertex="D"/> </vertex> <vertex name="C"> <directed-edge-to vertex="E"/> </vertex> <vertex name="D"> <directed-edge-to vertex="E"/> </vertex> <vertex name="E"> <directed-edge-to vertex="G"/> </vertex> <vertex name="F"> <directed-edge-to vertex="G"/> </vertex> <vertex name="G"/> </dag> 

The result obtained is :

 <minimal-vertices> <minimal-vertex name="A" /> <minimal-vertex name="B" /> <minimal-vertex name="F" /> </minimal-vertices> 

Pay attention . The solution for completing complete (possibly cyclic) charts is available in XSLT here .

+5
source share

All Articles