Sandcastle

From Axaptapedia
Jump to: navigation, search

Introduction[edit]

Sandcastle documentation compilers enable managed class library developers throughout the world to easily create accurate, informative documentation with a common look and feel. With Dynamics AX, one can create XML documentation files that can be compiled with Sandcastle to create CHM help files.


An example of Dynamics AX XML documentation compiled with Sandcastle:

ShellExecute.png


Required Software[edit]

Microsoft .NET Framework Version 2.0, available here

HTML Help Workshop available here

Sandcastle available here or here


Steps[edit]

  1. Creating the XML documentation files
  2. Modifying the generated reflection file (reflection.org)
  3. Optional additions
  4. Compiling Sandcastle

Creating the XML documentation files[edit]

Follow the steps described in the Dynamics AX 2009 SDK to generate the XML documentation files. Be sure to store both the documentation and reflection file in the same folder and name them as follows:

comments.xml (documentation file)

reflection.org (reflection file)


For XML documentation tags compatible with AX and Sandcastle, see: XML Documentation Tags

Modifying the generated reflection file (reflection.org)[edit]

In order for Sandcastle to create the table of contents and generate the html files, the following changes need to be made to the reflection file:

  • Replace the first occurance of <apidata group="root" with <apidata group="namespace"
  • Replace every occurance of <api api="R:Project"> with <api id="N:" api="R:Project">
  • Replace every occurance of </containers> with <namespace api="N:" /></containers>

This could be done directly in AX with the following changes:

  • In SysDictionary.xmlReflection, add _xmlWriter.writeAttributeString('id', 'N:'); after _xmlWriter.writeStartElement(#XmlApi);
  • In SysDictionary.xmlReflectionApiData, replace root with namespace
  • In SysDictClass.xmlReflectionContainers and SysDictMethod.xmlReflectionContainers, insert above the last occurance of _xmlWriter.writeEndElement(); the following: _xmlWriter.writeStartElement('namespace'); _xmlWriter.writeAttributeString('api', 'N:'); _xmlWriter.writeEndElement();

This transformation could also be done with the following xsl file and XmlTransform::execute():

<xpp> <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

   xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
 <xsl:output method="xml"/>
 <xsl:template match="*">
   <xsl:copy>
     <xsl:if test="self::api[1]">
       <xsl:attribute name="id">N:</xsl:attribute>
     </xsl:if>
     <xsl:for-each select="@*">
       <xsl:copy/>
     </xsl:for-each>
     <xsl:apply-templates/>
     <xsl:if test="self::apidata and @pseudo">
       <xsl:attribute name="group">namespace</xsl:attribute>
     </xsl:if>
     <xsl:if test="self::containers">
       <xsl:element name="namespace">
         <xsl:attribute name="api">N:</xsl:attribute>
       </xsl:element>
     </xsl:if>
   </xsl:copy>
 </xsl:template>

</xsl:stylesheet> </xpp>


Here is an example of transforming an XML file with the XmlTransform class: <xpp> void transform(FilePath _xsl, FilePath _old, FilePath _new) {

   #XmlDocumentation
   XmlTextWriter writer = XmlTextWriter::newFile(_new);
   ;
   writer.writeProcessingInstruction(#XmlXml, #XmlVersion);
   writer.writeRaw(XmlTransform::execute(XmlReader::newFile(
       _xsl), XmlReader::newFile(_old), true));
   writer.flush();

} </xpp>

Optional additions[edit]

Assembly information[edit]

To display assembly information, add a library element to each containers element with assembly and module attributes.

  • Replace every occurance of </containers> with <library assembly="Microsoft Dynamics AX" module="Ax32" /></containers>

This does the same with xsl:

<xpp> <xsl:template match="node()|@*">

 <xsl:copy>
   <xsl:if test="self::containers">
     <xsl:element name="library" use-attribute-sets="library">
       <xsl:attribute name="assembly">Microsoft Dynamics AX</xsl:attribute>
       <xsl:attribute name="module">Ax32</xsl:attribute>
     </xsl:element>
   </xsl:if>
   <xsl:apply-templates select="node()|@*"/>
 </xsl:copy>

</xsl:template> </xpp>

The result might look like this:

<xpp> <containers>

 <type api="R:Project" />
 <library assembly="Microsoft Dynamics AX" module="Ax32" />
 <namespace api="N:" />

</containers> </xpp>

Base enums[edit]

When base enums are added to a project, Sandcastle crashes with the follow error:


Error: BuildAssembler: An error occured while initializing the build component 'Microsoft.Ddue.Tools.ResolveReferenceLinksComponent2' in the component assembly 'C:\Program Files\Sandcastle\ProductionTools\BuildComponents.dll'. The error message and stack trace follows: System.ArgumentNullException: Value cannot be null.


Solution:

  • Replace every occurance of "enumerationentry" /> with "enumerationentry" /><containers><type api="R:Project" /></containers>

This does the same with xsl: <xpp> <xsl:template match="node()|@*">

 <xsl:copy>
   <xsl:if test="child::apidata[@subgroup='enumerationentry']">
     <xsl:element name="containers">
       <xsl:element name="type">
         <xsl:attribute name="api">R:Project</xsl:attribute>
       </xsl:element>
     </xsl:element>
   </xsl:if>
   <xsl:apply-templates select="node()|@*"/>
 </xsl:copy>

</xsl:template> </xpp>

The result should look similar to this:

<xpp> <api id="F:Ax::IsCool">

 <apidata name="IsCool" group="member" subgroup="enumerationentry" />
 <typedata value="0" label="IsCool" configurationkey="" />
 <containers>
   <type api="R:Project" />
 </containers>

</api> </xpp>

Extended data types[edit]

Extended data types are not supported, so define them as a class.

  • Replace every occurance of subgroup="extendeddatatype" with subgroup="[class|structure|interface|enumeration|delegate]"

Or, doing the same with xsl (this example replaces table and relation or index with class and field):

<xpp><xsl:template match="node()|@*">

 <xsl:copy>
     <xsl:if test="self::apidata[@subgroup='extendeddatatype']">
       <xsl:attribute name="subgroup">class</xsl:attribute>
     </xsl:if>
   <xsl:apply-templates select="node()|@*"/>
 </xsl:copy>

</xsl:template></xpp>

The result should look similar to this:

Hiding other languages[edit]

To prevent other languages from being displayed, such as VB or C++, edit the presentation sandcastle.config file and comment out all of the undesired <generator> elements.

Example: <xpp> <generators>

 <generator type="Microsoft.Ddue.Tools.CSharpDeclarationSyntaxGenerator" assembly="%DXROOT%\ProductionTools\SyntaxComponents.dll" />

</generators> </xpp>

Tables[edit]

Tables, relations and indexes are not supported with Sandcastle, so if they are included in an AX project, then they need to be changed to one of the supported types, such as class and field.

  • Replace every occurance of subgroup="table" with subgroup="[class|structure|interface|enumeration|delegate]"
  • Replace every occurance of subgroup="relation" with subgroup="[constructor|method|property|field|event]"
  • Replace every occurance of subgroup="index" with subgroup="[constructor|method|property|field|event]"

Or, doing the same with xsl (this example replaces table and relation or index with class and field):

<xpp> <xsl:template match="node()|@*">

 <xsl:copy>
     <xsl:if test="self::apidata[@subgroup='table']">
       <xsl:attribute name="subgroup">class</xsl:attribute>
     </xsl:if>
     <xsl:if test="self::apidata[@subgroup='relation']
         or self::apidata[@subgroup='index']
         or self::apidata[@subgroup='fieldgroup']">
       <xsl:attribute name="subgroup">field</xsl:attribute>
     </xsl:if>
   <xsl:apply-templates select="node()|@*"/>
 </xsl:copy>

</xsl:template> </xpp>

The result should look similar to this:

<xpp> <api id="M:MyTable.fields.Name">

 <apidata name="Name" group="member" subgroup="field" />
 <memberdata id="50001" configurationkey="" mandatory="false" persisted="true" />
 <containers>
   <type api="T:MyTable" />
   <library assembly="Microsoft Dynamics AX" module="Ax32" />
   <namespace api="N:" />
 </containers>
 <datatype>
   <type api="T:Name" />
 </datatype>
 <label>Name</label>
 <helptext>Name.</helptext>

</api> </xpp>

Namespace[edit]

To give the namespace a name, add the "name" attribute to the first "apidata" element and give it a value, such as "AOT":

<xpp> <xsl:if test="self::apidata and @pseudo">

 <xsl:attribute name="group">namespace</xsl:attribute>
 <xsl:attribute name="name">AOT</xsl:attribute>

</xsl:if></xpp>

The result should look similar to this:

<xpp> <?xml version="1.0" encoding="utf-8"?> <reflection build="5.0.1500.1313" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:ddue="urn:ddue-extensions"> <apis> <api id="N:" api="R:Project"> <apidata pseudo="true" group="namespace" name="AOT" /> </xpp>

X++ language syntax title[edit]

For X++ one can use the C# generator since the syntax is similar. However, for the documentation to be titled as X++ rather than C#, modify shared_content.xml in the Sandcastle presentation content folder and replace <item id="CSharpLabel">C#</item> with <item id="CSharpLabel">X++</item>.

Example: <xpp> <item id="CSharpLabel">X++</item> <item id="VisualBasicLabel">Visual Basic</item> <item id="VisualBasicUsageLabel">Visual Basic Usage</item> <item id="ManagedCPlusPlusLabel">Visual C++</item> </xpp>


Compiling Sandcastle[edit]

"Creating a Chm build using Sandcastle" walks one through the process of manually creating a chm file using Sandcastle. For AX, jump to step 5. More examples can be found in the Sandcastle examples folder under %programfiles%\Sandcastle\Examples. To simplify the process, I created RunSandcastle.bat. Download and extract it to the XML documentation folder and then run the following command from dos to generate help in the "chm" subfolder:


RunSandcastle.bat [Presentation] [Output file title]


Example:

C:\Temp\RunSandcastle.bat Hana ShellExecute