Solved

Sort C# XML Documentation Output by Class Name

Posted on 2009-04-01
5
981 Views
Last Modified: 2013-11-18
I'm trying to write an XSLT for the XML documentation file that gets output by Visual Studio 2005 when you build a project. The class names are in a seemingly random order and I'd like them and their methods/properties/fields to be sorted.

Below is my current XSL file, but the output is really ugly as I'm not a designer or anything. I've tried using <xsl:sort select="@name"/> throughout the XSL file, but it only sorts the methods/properties/fields and not the classes. The XSL file I'm using came from CodeProject (http://www.codeproject.com/KB/XML/XMLDocStylesheet.aspx).

If someone can provide me with a working XSL file that would be great. Normally, I would try to fix what I've got but as I've said, it's ugly anyways. Besides, I would like to have a complete (good) example to look at for future XSL files I'll have to make.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
<!-- DOCUMENT TEMPLATE -->
<!-- Format the whole document as a valid HTML document -->
<xsl:template match="/">
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="tDocc.css" />
  </head>
  <body>
    <xsl:apply-templates select="//assembly">
      <xsl:sort select="member>@name" data-type="text" order="ascending"/>
    </xsl:apply-templates>
  </body>
</html>
</xsl:template>
 
<!-- ASSEMBLY TEMPLATE -->
<!-- For each Assembly, display its name and then its member types -->
<xsl:template match="assembly">
<h1><xsl:value-of select="name"/></h1>
  <xsl:apply-templates select="//member[contains(@name,'T:')]">
    <xsl:sort select="@name" data-type="text" order="ascending"/>
  </xsl:apply-templates>
</xsl:template>
 
<!-- TYPE TEMPLATE -->
<!-- Loop through member types and display their properties and methods -->
<xsl:template match="//member[contains(@name,'T:')]">
 
  <!-- Two variables to make code easier to read -->
  <!-- A variable for the name of this type -->
  <xsl:variable name="MemberName"
                 select="substring-after(@name, '.')"/>
 
  <!-- Get the type's fully qualified name without the T: prefix -->
  <xsl:variable name="FullMemberName"
                 select="substring-after(@name, ':')"/>
  
  <!-- Display the type's name and information -->
  <h2><xsl:value-of select="$MemberName"/></h2>  
 
  <xsl:apply-templates>
    <xsl:sort select="@name" data-type="text" order="ascending"/>
  </xsl:apply-templates>
 
  <!-- If this type has public fields, display them -->
  <xsl:if test="//member[contains(@name,concat('F:',$FullMemberName))]">
   <h3>Fields</h3>
   <div class="members">
 
      <xsl:for-each select="//member[contains(@name,concat('F:',$FullMemberName))]">
        <xsl:sort select="@name" data-type="text" order="ascending"/>
        <h4><xsl:value-of select="substring-after(@name, concat('F:',$FullMemberName,'.'))"/></h4>
          <xsl:apply-templates>
    	    <xsl:sort select="@name" data-type="text" order="ascending"/>
 		  </xsl:apply-templates>
      </xsl:for-each>
    </div>
  </xsl:if>
 
  <!-- If this type has properties, display them -->
  <xsl:if test="//member[contains(@name,concat('P:',$FullMemberName))]">
  <h3>Properties</h3>
  <div class="members">
 
      <xsl:for-each select="//member[contains(@name,concat('P:',$FullMemberName))]">
        <xsl:sort select="@name" data-type="text" order="ascending"/>
        <h4><xsl:value-of select="substring-after(@name, concat('P:',$FullMemberName,'.'))"/></h4>
          <xsl:apply-templates>
    	    <xsl:sort select="@name" data-type="text" order="ascending"/>
 		  </xsl:apply-templates>
      </xsl:for-each>
  </div>
  </xsl:if>
   
  <!-- If this type has methods, display them -->
  <xsl:if test="//member[contains(@name,concat('M:',$FullMemberName))]">
  <h3>Methods</h3>
  <div class="members">
   
    <xsl:for-each select="//member[contains(@name,concat('M:',$FullMemberName))]">
        <xsl:sort select="@name" data-type="text" order="ascending"/>
        <!-- If this is a constructor, display the type name 
            (instead of "#ctor"), or display the method name -->
        <h4 class="method_name">
        <xsl:choose>
          <xsl:when test="contains(@name, '#ctor')">
            Constructor: 
            <xsl:value-of select="$MemberName"/>
            <xsl:value-of select="substring-after(@name, '#ctor')"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="substring-after(@name, concat('M:',$FullMemberName,'.'))"/>
          </xsl:otherwise>
        </xsl:choose>
        </h4>
        
        <xsl:apply-templates select="summary"/>
        
        <!-- Display parameters if there are any -->
        <xsl:if test="count(param)!=0">
          <h5>Parameters</h5>
          <xsl:apply-templates select="param"/>      
        </xsl:if>
 
        <!-- Display return value if there are any -->
        <xsl:if test="count(returns)!=0">
          <!-- <h5>Return Value</h5> -->
          <xsl:apply-templates select="returns"/>      
        </xsl:if>
 
        <!-- Display exceptions if there are any -->
        <xsl:if test="count(exception)!=0">
          <h5>Exceptions</h5>
          <xsl:apply-templates select="exception"/>      
        </xsl:if>
 
        <!-- Display examples if there are any -->
        <xsl:if test="count(example)!=0">
          <h5>Example</h5>
          <xsl:apply-templates select="example"/>      
        </xsl:if>
 
      </xsl:for-each>
   
  </div>
  </xsl:if>
</xsl:template>
 
<!-- OTHER TEMPLATES -->
<!-- Templates for other tags -->
<xsl:template match="c">
  <code><xsl:apply-templates /></code>
</xsl:template>
 
<xsl:template match="code">
  <pre><xsl:apply-templates /></pre>
</xsl:template>
 
<xsl:template match="example">
  <p class="example"><strong>Example: </strong><xsl:apply-templates /></p>
</xsl:template>
 
<xsl:template match="exception">
  <p class="exception"><strong><xsl:value-of select="substring-after(@cref,'T:')"/>: </strong><xsl:apply-templates /></p>
</xsl:template>
 
<xsl:template match="include">
  <a href="{@file}">External file</a>
</xsl:template>
 
<xsl:template match="para">
  <p class="para"><xsl:apply-templates /></p>
</xsl:template>
 
<xsl:template match="param">
  <p class="param"><strong><xsl:value-of select="@name"/>: </strong><xsl:apply-templates /></p>
</xsl:template>
 
<xsl:template match="paramref">
  <span class="paramref"><xsl:value-of select="@name" /></span>
</xsl:template>
 
<xsl:template match="permission">
  <p class="permission">Permission: <em><xsl:value-of select="@cref" /> </em><xsl:apply-templates /></p>
</xsl:template>
 
<xsl:template match="remarks">
  <p class="remarks"><xsl:apply-templates /></p>
</xsl:template>
 
<xsl:template match="returns">
  <p class="returns"><strong>Return Value: </strong><xsl:apply-templates /></p>
</xsl:template>
 
<xsl:template match="see">
  <span class="see">See: <xsl:value-of select="@cref" /></span>
</xsl:template>
 
<xsl:template match="seealso">
  <span class="seealso">See also: <xsl:value-of select="@cref" /></span>
</xsl:template>
 
<xsl:template match="summary">
  <p class="summary"><xsl:apply-templates /></p>
</xsl:template>
 
<xsl:template match="list">
  <xsl:choose>
    <xsl:when test="@type='bullet'">
      <ul>
      <xsl:for-each select="listheader">
        <li><strong><xsl:value-of select="term"/>: </strong><xsl:value-of select="definition"/></li>
      </xsl:for-each>
      <xsl:for-each select="list">
        <li><strong><xsl:value-of select="term"/>: </strong><xsl:value-of select="definition"/></li>
      </xsl:for-each>
      </ul>
    </xsl:when>
    <xsl:when test="@type='number'">
      <ol>
      <xsl:for-each select="listheader">
        <li><strong><xsl:value-of select="term"/>: </strong><xsl:value-of select="definition"/></li>
      </xsl:for-each>
      <xsl:for-each select="list">
        <li><strong><xsl:value-of select="term"/>: </strong><xsl:value-of select="definition"/></li>
      </xsl:for-each>
      </ol>
    </xsl:when>
    <xsl:when test="@type='table'">
      <table>
      <xsl:for-each select="listheader">
        <th>
          <td><xsl:value-of select="term"/></td>
          <td><xsl:value-of select="definition"/></td>
        </th>
      </xsl:for-each>
      <xsl:for-each select="list">
        <tr>
          <td><strong><xsl:value-of select="term"/>: </strong></td>
          <td><xsl:value-of select="definition"/></td>
        </tr>
      </xsl:for-each>
      </table>
    </xsl:when>
  </xsl:choose>
</xsl:template>
 
</xsl:stylesheet>
 
 
 
 
 
====== CSS FILE =======
 
body {
	font-family: arial,helvetica,sans-serif;
}
 
p {
	padding: 0;
	margin: 3px;
}
 
h1, h2, h3, h4, h5 {
	padding: 0;
	margin: 0.5em 0.25em;
}
 
h1 {
	font-size: 2em;
}
 
h2 {
	font-size: 1.5em;
}
 
h3 {
	font-size: 1.25em;
}
 
h4, .param {
	font-size: 0.9em;
}
 
h5 {
	font-size: 0.75em;
}
 
h3, h5, .summary, .remarks, .param, .returns {
	margin-left: 2em;
}
 
.method_name {
	margin-top: 0.5em;
}
 
.members {
	margin-left: 2em;
	margin-bottom: 1em;
	border: 1px solid black;
	padding: 0.5em;
}
 
.paramref, see, seealso {
	font-style: italic;
}

Open in new window

0
Comment
Question by:BNLIND
  • 3
  • 2
5 Comments
 
LVL 21

Expert Comment

by:MogalManic
ID: 24048355
Your sort should be a valid XPATH expression.  So change the sort to:
     
If you want to sort by multiple keys, just add more  elements.  The order or the elements determine the sort order
0
 
LVL 1

Author Comment

by:BNLIND
ID: 24048992
On line 13 of the code above, I was playing around with the "member>@name" and didn't mean for that to be in there. I made the change suggested by MogalManiac, but the class names are still not sorted.
0
 
LVL 21

Accepted Solution

by:
MogalManic earned 500 total points
ID: 24049446
I couldn't get your XSL to work but I got the CodeProject documentation to sort by putting  on each for-each for each statement like this:
   
       
 

<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- ================================================================================ -->
<!-- Amend, distribute, spindle and mutilate as desired, but don't remove this header -->
<!-- A simple XML Documentation to basic HTML transformation stylesheet -->
<!-- (c)2005 by Emma Burrows -->
<!-- ================================================================================ -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
<!-- DOCUMENT TEMPLATE -->
<!-- Format the whole document as a valid HTML document -->
<xsl:template match="/">
<HTML>
  <BODY>
    <xsl:apply-templates select="//assembly"/>
  </BODY>
</HTML>
</xsl:template>
 
<!-- ASSEMBLY TEMPLATE -->
<!-- For each Assembly, display its name and then its member types -->
<xsl:template match="assembly">
<H1><xsl:value-of select="name"/></H1>
  <xsl:apply-templates select="//member[contains(@name,'T:')]"/>
</xsl:template>
 
<!-- TYPE TEMPLATE -->
<!-- Loop through member types and display their properties and methods -->
<xsl:template match="//member[contains(@name,'T:')]">
 
  <!-- Two variables to make code easier to read -->
  <!-- A variable for the name of this type -->
  <xsl:variable name="MemberName"
                 select="substring-after(@name, '.')"/>
 
  <!-- Get the type's fully qualified name without the T: prefix -->
  <xsl:variable name="FullMemberName"
                 select="substring-after(@name, ':')"/>
 
  <!-- Display the type's name and information -->
  <H2><xsl:value-of select="$MemberName"/></H2>
  <xsl:apply-templates/>
 
  <!-- If this type has public fields, display them -->
  <xsl:if test="//member[contains(@name,concat('F:',$FullMemberName))]">
   <H3>Fields</H3>
 
      <xsl:for-each select="//member[contains(@name,concat('F:',$FullMemberName))]">
        <xsl:sort select="@name" data-type="text" order="ascending"/>
        <H4><xsl:value-of select="substring-after(@name, concat('F:',$FullMemberName,'.'))"/></H4>
        <xsl:apply-templates/>
      </xsl:for-each>
  </xsl:if>
 
  <!-- If this type has properties, display them -->
  <xsl:if test="//member[contains(@name,concat('P:',$FullMemberName))]">
  <H3>Properties</H3>
 
      <xsl:for-each select="//member[contains(@name,concat('P:',$FullMemberName))]">
        <xsl:sort select="@name" data-type="text" order="ascending"/>
        <H4><xsl:value-of select="substring-after(@name, concat('P:',$FullMemberName,'.'))"/></H4>
        <xsl:apply-templates/>
      </xsl:for-each>
  </xsl:if>
   
  <!-- If this type has methods, display them -->
  <xsl:if test="//member[contains(@name,concat('M:',$FullMemberName))]">
  <H3>Methods</H3>
   
    <xsl:for-each select="//member[contains(@name,concat('M:',$FullMemberName))]">
        <xsl:sort select="@name" data-type="text" order="ascending"/>
       
        <!-- If this is a constructor, display the type name 
            (instead of "#ctor"), or display the method name -->
        <H4>
        <xsl:choose>
          <xsl:when test="contains(@name, '#ctor')">
            Constructor: 
            <xsl:value-of select="$MemberName"/>
            <xsl:value-of select="substring-after(@name, '#ctor')"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="substring-after(@name, concat('M:',$FullMemberName,'.'))"/>
          </xsl:otherwise>
        </xsl:choose>
        </H4>
        
        <xsl:apply-templates select="summary"/>
        
        <!-- Display parameters if there are any -->
        <xsl:if test="count(param)!=0">
          <H5>Parameters</H5>
          <xsl:apply-templates select="param"/>      
        </xsl:if>
 
        <!-- Display return value if there are any -->
        <xsl:if test="count(returns)!=0">
          <H5>Return Value</H5>
          <xsl:apply-templates select="returns"/>      
        </xsl:if>
 
        <!-- Display exceptions if there are any -->
        <xsl:if test="count(exception)!=0">
          <H5>Exceptions</H5>
          <xsl:apply-templates select="exception"/>      
        </xsl:if>
 
        <!-- Display examples if there are any -->
        <xsl:if test="count(example)!=0">
          <H5>Example</H5>
          <xsl:apply-templates select="example"/>      
        </xsl:if>
 
      </xsl:for-each>
   
  </xsl:if>
</xsl:template>
 
<!-- OTHER TEMPLATES -->
<!-- Templates for other tags -->
<xsl:template match="c">
  <CODE><xsl:apply-templates /></CODE>
</xsl:template>
 
<xsl:template match="code">
  <PRE><xsl:apply-templates /></PRE>
</xsl:template>
 
<xsl:template match="example">
  <P><STRONG>Example: </STRONG><xsl:apply-templates /></P>
</xsl:template>
 
<xsl:template match="exception">
  <P><STRONG><xsl:value-of select="substring-after(@cref,'T:')"/>: </STRONG><xsl:apply-templates /></P>
</xsl:template>
 
<xsl:template match="include">
  <A HREF="{@file}">External file</A>
</xsl:template>
 
<xsl:template match="para">
  <P><xsl:apply-templates /></P>
</xsl:template>
 
<xsl:template match="param">
  <P><STRONG><xsl:value-of select="@name"/>: </STRONG><xsl:apply-templates /></P>
</xsl:template>
 
<xsl:template match="paramref">
  <EM><xsl:value-of select="@name" /></EM>
</xsl:template>
 
<xsl:template match="permission">
  <P><STRONG>Permission: </STRONG><EM><xsl:value-of select="@cref" /> </EM><xsl:apply-templates /></P>
</xsl:template>
 
<xsl:template match="remarks">
  <P><xsl:apply-templates /></P>
</xsl:template>
 
<xsl:template match="returns">
  <P><STRONG>Return Value: </STRONG><xsl:apply-templates /></P>
</xsl:template>
 
<xsl:template match="see">
  <EM>See: <xsl:value-of select="@cref" /></EM>
</xsl:template>
 
<xsl:template match="seealso">
  <EM>See also: <xsl:value-of select="@cref" /></EM>
</xsl:template>
 
<xsl:template match="summary">
  <P><xsl:apply-templates /></P>
</xsl:template>
 
<xsl:template match="list">
  <xsl:choose>
    <xsl:when test="@type='bullet'">
      <UL>
      <xsl:for-each select="listheader">
        <LI><strong><xsl:value-of select="term"/>: </strong><xsl:value-of select="definition"/></LI>
      </xsl:for-each>
      <xsl:for-each select="list">
        <LI><strong><xsl:value-of select="term"/>: </strong><xsl:value-of select="definition"/></LI>
      </xsl:for-each>
      </UL>
    </xsl:when>
    <xsl:when test="@type='number'">
      <OL>
      <xsl:for-each select="listheader">
        <LI><strong><xsl:value-of select="term"/>: </strong><xsl:value-of select="definition"/></LI>
      </xsl:for-each>
      <xsl:for-each select="list">
        <LI><strong><xsl:value-of select="term"/>: </strong><xsl:value-of select="definition"/></LI>
      </xsl:for-each>
      </OL>
    </xsl:when>
    <xsl:when test="@type='table'">
      <TABLE>
      <xsl:for-each select="listheader">
        <TH>
          <TD><xsl:value-of select="term"/></TD>
          <TD><xsl:value-of select="definition"/></TD>
        </TH>
      </xsl:for-each>
      <xsl:for-each select="list">
        <TR>
          <TD><strong><xsl:value-of select="term"/>: </strong></TD>
          <TD><xsl:value-of select="definition"/></TD>
        </TR>
      </xsl:for-each>
      </TABLE>
    </xsl:when>
  </xsl:choose>
</xsl:template>
 
</xsl:stylesheet>

Open in new window

0
 
LVL 1

Author Comment

by:BNLIND
ID: 24050943
I ended up grabbing a book about XSLT and XPath and wrote my own, using the CodeProject version for reference. Once I knew a little more about XPath and XSLT's in general, I was able to see where I messed up. Even though your sample above is correct and works, I found out that I still need to make several modifications so that I can also use CSS to make it pretty.

There really is no "quick-fix" with this stuff....you really have to know it, but it was actually pretty easy once I stopped trying to fix the problem and started learning the language. It only took about an hour to figure it all out once I started reading the book and now I can write my own XSLT files :)

Thanks for your help with this.
0
 
LVL 21

Expert Comment

by:MogalManic
ID: 24053189
A good book on XSLT is XSLT: Programmer's Reference published by Wrox press.

I use the 2nd addition which looks like it is out of print:
  http://www.wrox.com/WileyCDA/WroxTitle/productCd-0764543814.html

But there is a XSLT 2.0 Programmer's Reference, 3rd Edition that looks like the next version:
  http://www.wrox.com/WileyCDA/WroxTitle/XSLT-2-0-Programmer-s-Reference-3rd-Edition.productCd-0764576496.html

The author Michael Kay is the writer of the SAXON XSLT processor
  http://en.wikipedia.org/wiki/Saxon_XSLT
0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

What is Node.js? Node.js is a server side scripting language much like PHP or ASP but is used to implement the complete package of HTTP webserver and application framework. The difference is that Node.js’s execution engine is asynchronous and event…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
Viewers will learn one way to get user input in Java. Introduce the Scanner object: Declare the variable that stores the user input: An example prompting the user for input: Methods you need to invoke in order to properly get  user input:
The viewer will learn how to count occurrences of each item in an array.

820 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question