Template Recursion error
I am in the process of refreshing my XST knowledge, and have decided to have a go at making an XSLT 1.0 stylesheet that convert the XMLHelp files from the C# compiler into a better formatted form.
There are a number of issues to get around, but currently I am simply trying to parse those annoying member name attributes, and create another XML document in which the tokens between ':' and '.' characters are extracted and turned into elements.
To start with, I want to turn something like:
<member name="T:PrimeNumbers.Properties.Resources">
into something like:
<member type="T">
<Properties>
<Resources />
</Properties>
</member>
Now, I have gone as far as writing a stylesheet which looks as if it ought to output another XML document, but sadly when I process the data, XML Notepad 2007 crashes, and both IE7 and Firefox 3.5.5 give me an error saying that there is an infinite recursion going on in there.
I would be grateful if somebody could tell me what I have done wrong.
<xsl:transform version="1.0">
<xsl:output
method="xml" version="4.0" encoding="iso-8859-1"
indent="yes" media-type="text/xml"
/>
<xsl:variable name="AssemblyName" />
<xsl:template match="/">
<xsl:apply-templates select="/doc/assembly" />
</xsl:template>
<xsl:template match="assembly/name">
<xsl:variable name="AssemblyName" select="text()" />
<assembly name="{$AssemblyName}">
<xsl:apply-templates select="/doc" />
</assembly>
</xsl:template>
<xsl:template match="/doc/members/member">
<!-- This gives you a single letter
(T=Type P=Property M=Method F=Field) -->
<member type="{substring-before(@name,':')}">
<xsl:call-template name="RecurseName">
<!-- This gives you the type name from the beginning
of the Namespace to the final local name. -->
<xsl:with-param name="Path" select="
substring-after(substring-after(@name,':'),'.')
" />
</xsl:call-template>
</member>
</xsl:template>
<xsl:template name="RecurseName">
<xsl:param name="Path" select="'default'" />
<xsl:variable name="PathRemainder" select="substring-after($Path,'.')" />
<xsl:value-of select="$PathRemainder" />
<xsl:element name="{substring-before($Path,'.')}">
<xsl:if test="$PathRemainder != ''">
<xsl:call-template name="RecurseName">
<xsl:with-param name="Path" select="$PathRemainder" />
</xsl:call-template>
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:transform>
[Later on, I will transform this document so that if members have matching elements, they are merged together.]
Basically, it crashes XML Notepad 2007 if the RecurseName template even exists - with no elements. Note that <xsl:value-of select="$PathRemainder"/>
is pure debuggery.
Any ideas?
Appendix A: Test XML
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="XmlDocTemplate3.xml" ?>
<doc>
<assembly>
<name>PrimeNumbers</name>
</assembly>
<members>
<member name="T:PrimeNumbers.Properties.Resources">
<summary>
A strongly-typed resource class, for looking up localized strings, etc.
</summary>
</member>
<member name="P:PrimeNumbers.Properties.Resources.ResourceManager">
<summary>
Returns the cached ResourceManager instance used by this class.
</summ开发者_JAVA百科ary>
</member>
<member name="P:PrimeNumbers.Properties.Resources.Culture">
<summary>
Overrides the current thread's CurrentUICulture property for all
resource lookups using this strongly typed resource class.
</summary>
</member>
<member name="M:PrimeNumbers.Program.Main">
<summary>
The main entry point for the application.
</summary>
</member>
<member name="M:PrimeNumbers.PrimeNumberForm.CalculatePrimeNumbers(System.Int32)">
<summary>
Calculates the prime numbers between 1 and the (count)th prime number.
</summary>
<param name="count">The number of prime numbers to return.</param>
<returns>List of integers</returns>
<exception cref="T:System.ArgumentOutOfRangeException">Thrown if <paramref name="count" /> is negative.</exception>
</member>
<member name="F:PrimeNumbers.PrimeNumberForm.components">
<summary>
Required designer variable.
</summary>
</member>
<member name="M:PrimeNumbers.PrimeNumberForm.Dispose(System.Boolean)">
<summary>
Clean up any resources being used.
</summary>
<param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
</member>
<member name="M:PrimeNumbers.PrimeNumberForm.InitializeComponent">
<summary>
Required method for Designer support - do not modify
the contents of this method with the code editor.
</summary>
</member>
</members>
</doc>
You are encountering an error on the line:
<xsl:element name="{substring-before($Path,'.')}">
when the last portion of the namespace is reached which doesn't contain a dot '.'
You can use this snippet for the RecurseName template instead:
<xsl:template name="RecurseName">
<xsl:param name="Path" select="'default'"/>
<xsl:choose>
<xsl:when test="contains($Path, '.')">
<xsl:element name="{substring-before($Path,'.')}">
<xsl:call-template name="RecurseName">
<xsl:with-param name="Path" select="substring-after($Path,'.')"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$Path}" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I have tested it. Output generated:
<member type="T">
<Properties>
<Resources />
</Properties>
</member>
I think the infinite recursion error in question is actually being caused by this template here
<xsl:template match="assembly/name">
<xsl:variable name="AssemblyName" select="text()"/>
<assembly name="{$AssemblyName}">
<xsl:apply-templates select="/doc"/>
</assembly>
</xsl:template>
Where you do <xsl:apply-templates select="/doc" /> within this template, this will match the template itself, because the assembly element is beneath the doc element. Try selecting on /doc/members instead
<xsl:template match="assembly/name">
<xsl:variable name="AssemblyName" select="text()"/>
<assembly name="{$AssemblyName}">
<xsl:apply-templates select="/doc/members"/>
</assembly>
</xsl:template>
This should stop the infinite recursion errors at least.
Tim C got the right answer, however there are a few other mistakes, which I am correcting purely for posterity. Round brackets in the method names will break my XSLT as well. So before we start recursing, I need to preserve everything between '(' and ')' as a element, and send the name string before the first '(' to the recursive template.
<xsl:transform version="1.0">
<xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes" media-type="text/xml"/>
<xsl:variable name="AssemblyName"/>
<xsl:template match="/">
<xsl:apply-templates select="/doc/assembly"/>
</xsl:template>
<xsl:template match="assembly/name">
<xsl:variable name="AssemblyName" select="text()"/>
<assembly name="{$AssemblyName}">
<xsl:apply-templates select="/doc/members"/>
</assembly>
</xsl:template>
<xsl:template match="member">
<member type="{substring-before(@name,':')}">
<xsl:choose>
<xsl:when test="contains(@name,'(')">
<params>
<xsl:value-of select="substring-before(substring-after(@name,'('),')')"/>
</params>
<xsl:call-template name="RecurseName">
<xsl:with-param name="Path" select="substring-after(substring-before(substring-after(@name,':'),'('),'.')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="RecurseName">
<xsl:with-param name="Path" select="substring-after(substring-after(@name,':'),'.')"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</member>
</xsl:template>
<xsl:template name="RecurseName">
<xsl:param name="Path" select="'default'"/>
<xsl:choose>
<xsl:when test="contains($Path,'.')">
<xsl:element name="{substring-before($Path,'.')}">
<xsl:call-template name="RecurseName">
<xsl:with-param name="Path" select="substring-after($Path,'.')"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$Path}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:transform>
精彩评论