XSLT to find all nodes in one list that aren't in another
I have xml that essentialy looks like the following (my actual .xml is a build log containing a union of VStudio and DotCover build output):
<buildlog>
<Projects>
<project name="project1.csproj" />
<project name="project2.csproj" />
<project name="project3.csproj" />
<project name="project4.csproj" />
</Projects>
<Root>
<Assembly Name="project1" />
<Assembly Name="project2" />
</Root>
</buildlog>
I need a list of all project nodes that don't have a corresponding @name match on @Name in the set of Root/Assembly nodes. My goal here is to generate a report showing all projects that don't have code coverage.
Here's the beginnings of my xsl stylesheet:
<xsl:template match="/">
<xsl:apply-templates select="buildlog/Projects" />
</xsl:template>
<xsl:template match="Projects">
<table>
<tr>
<td>Assemblies Not Covered:</td>
<td><xsl:value-of select="count(./project)"/></td>
</tr>
</table>
<xsl:apply-templates select="project" />
</xsl:template>
<xsl:template match="project">
<table>
<tr>
<td style="padding-left:30px">
<xsl:value-of select="substring-before(./@name,'.csproj')"/>
</td>
</tr>
</table>
</xsl:template>
Thanks in advance.
INTERIM SOLUTION
For the time being I solved my issue using ms:script extensions. Below is the code. Not the most elegant give it is a Windows only solution, but it solved the problem. I'll the things other folks recommended and answer back in the comment sections.
Here's my solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/TR/xhtml1/strict"
xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns:dsi="urn:dsi-scripts">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:apply-templates select="buildlog/Projects" />
</xsl:template>
<xsl:template match="Projects">
<table>
<tr>
<td>As开发者_如何学运维semblies Not Covered:</td>
</tr>
</table>
<xsl:apply-templates select="project" />
</xsl:template>
<xsl:template match="project">
<xsl:variable name="assemblies" select="//Assembly" />
<xsl:if test="not(dsi:HasCoverage($assemblies/@Name, ./@name))">
<table>
<tr>
<td style="padding-left:30px">
<xsl:value-of select="substring-before(./@name,'.csproj')"/>
</td>
</tr>
</table>
</xsl:if>
</xsl:template>
<xsl:template match="Assembly">
<table>
<tr>
<td style="padding-left:30px">
<xsl:value-of select="@Name"/>
</td>
</tr>
</table>
</xsl:template>
<ms:script language="C#" implements-prefix="dsi">
<![CDATA[
public bool HasCoverage(XPathNodeIterator assemblies, string project)
{
bool result = false;
while(assemblies.MoveNext())
{
if(assemblies.Current.Value == project.Replace(".csproj",string.Empty))
{
result = true;
break;
}
}
return result;
}
]]>
</ms:script>
</xsl:stylesheet>
I need a list of all project nodes that don't have a corresponding @name match on @Name in the set of Root/Assembly nodes
Use:
/*/Projects/project
[not(substring-before(@name, '.csproj') = /*/Root/Assembly/@Name)]
This XPath expression selects exactly the wanted project
elements.
Complete solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match=
"Projects/project
[not(substring-before(@name, '.csproj')
=
/*/Root/Assembly/@Name)] ">
<xsl:value-of select="concat(@name,'
')"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<buildlog>
<Projects>
<project name="project1.csproj" />
<project name="project2.csproj" />
<project name="project3.csproj" />
<project name="project4.csproj" />
</Projects>
<Root>
<Assembly Name="project1" />
<Assembly Name="project2" />
</Root>
</buildlog>
the wanted result is produced:
project3.csproj
project4.csproj
In case more efficiency becomes really necessary, one should use keys.
精彩评论