using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Doxygen.NET;
using System.Xml;
namespace DoxyTransform
{
class Program
{
/*
Why does this work? How is this possible? The following reasons:
1. Doxygen supports XML output, allowing the API documentation to be represented in a logically structured form
2. .net assemblies use XML files for documentation, meaning it is easy to transform the doxygen XML to .net XML
3. Doxygen.NET supports parsing a subset of this XML form into Object-oriented classes
4. The MapGuide API is fortunately designed and written in a convention-based manner (when you're making bindings
to 3 different languages, you have to!). The convention-based design allows us to make many valid assumptions:
a. All classes will be under a single namespace (OSGeo.MapGuide)
b. Only classes are exported out to the public API, no enums, structs or other fancy C++ features
d. Any word in a summary starting with the letters "Mg" can be assumed to be a class, thus we can replace them with
cref links to said class
e. Get/Set Methods are decorated with assorted metadata for .net. Just like IMake.exe detects __set, __get as hints to generate .net properties, we can use
these same markers as a hint to generate a property entry in our integrated API documentation
*/
static void Main(string[] args)
{
if (args.Length != 2 && args.Length != 3)
{
Usage();
return;
}
if (!Directory.Exists(args[1]))
{
Console.WriteLine("Doxygen XML output path not found: {0}", args[1]);
return;
}
string srcDir = args[1];
string outputPath = (args.Length == 3) ? args[2] : "";
if (args[0].ToLower() == "dotnet")
{
ProcessDotNet(srcDir, outputPath);
}
else if (args[0].ToLower() == "java")
{
Console.WriteLine("java not supported yet");
}
}
const string DOTNET_NAMESPACE = "OSGeo.MapGuide";
const string JAVA_PACKAGE = "org.osgeo.mapguide";
static XmlDocument CreateDotNetDocument(string assemblyName)
{
var doc = new XmlDocument();
doc.LoadXml("" + assemblyName + "");
return doc;
}
static string GetDotNetClassName(string className)
{
return DOTNET_NAMESPACE + className;
}
static string FormatDotNetCtor(Class klass, Constructor ctor)
{
if (ctor.Parameters.Count == 0)
{
return "M:" + DOTNET_NAMESPACE + "." + klass.Name + ".#ctor";
}
else
{
var paramTokens = new List();
foreach (var p in ctor.Parameters)
{
string clrType = MgTypeToDotNetType(p.Type);
if (clrType == p.Type)
paramTokens.Add(DOTNET_NAMESPACE + "." + p.Type);
else
paramTokens.Add(clrType);
}
return "M:" + DOTNET_NAMESPACE + "." + klass.Name + ".#ctor(" + string.Join(", ", paramTokens.ToArray()) + ")";
}
}
static string FormatDotNetCtor(Class klass, Method ctor)
{
if (ctor.Parameters.Count == 0)
{
return "M:" + DOTNET_NAMESPACE + "." + klass.Name + ".#ctor";
}
else
{
var paramTokens = new List();
foreach (var p in ctor.Parameters)
{
string clrType = MgTypeToDotNetType(p.Type);
if (clrType == p.Type)
paramTokens.Add(DOTNET_NAMESPACE + "." + p.Type);
else
paramTokens.Add(clrType);
}
return "M:" + DOTNET_NAMESPACE + "." + klass.Name + ".#ctor(" + string.Join(", ", paramTokens.ToArray()) + ")";
}
}
static string FormatDotNetMethod(Class klass, Method m)
{
if (m.Parameters.Count == 0)
{
return "M:" + DOTNET_NAMESPACE + "." + klass.Name + "." + m.Name;
}
else
{
var paramTokens = new List();
foreach (var p in m.Parameters)
{
string clrType = MgTypeToDotNetType(p.Type);
if (clrType == p.Type)
paramTokens.Add(DOTNET_NAMESPACE + "." + p.Type);
else
paramTokens.Add(clrType);
}
return "M:" + DOTNET_NAMESPACE + "." + klass.Name + "." + m.Name + "(" + string.Join(", ", paramTokens.ToArray()) + ")";
}
}
static string MgTypeToDotNetType(string mgType)
{
switch (mgType)
{
case "bool":
return "System.Boolean";
case "double":
return "System.Double";
case "float":
return "System.Single";
case "INT8": //These are exposed as short in .net
case "INT16":
return "System.Int16";
case "INT32":
return "System.Int32";
case "INT64":
return "System.Int64";
case "STRING":
return "System.String";
case "CREFSTRING":
return "System.String";
case "BYTE_ARRAY_IN":
case "BYTE_ARRAY_OUT":
return "System.Byte[]";
default:
return mgType;
}
}
static void CreateDotNetClassElement(XmlDocument doc, Class klass)
{
Console.WriteLine("Processing class: " + klass);
//Class description
{
var mbr = doc.CreateElement("member");
var sum = doc.CreateElement("summary");
var clsName = doc.CreateAttribute("name");
clsName.Value = "T:" + DOTNET_NAMESPACE + "." + klass.Name;
mbr.Attributes.Append(clsName);
sum.InnerXml = klass.Summary;
mbr.AppendChild(sum);
doc.DocumentElement.AppendChild(mbr);
}
//Class members
foreach (var m in klass.Members)
{
if (m.AccessModifier != "public")
continue;
var method = m as Method;
var ctor = m as Constructor;
var f = m as Field;
if (method != null)
{
if (method.Name == klass.Name) //Sanity check
ProcessDotNetClassConstructor(doc, klass, method);
else
ProcessDotNetClassMethod(doc, klass, method);
}
else if (ctor != null)
{
ProcessDotNetClassConstructor(doc, klass, ctor);
}
else if (f != null)
{
ProcessDotNetField(doc, klass, f);
}
}
}
private static void ProcessDotNetField(XmlDocument doc, Class klass, Field f)
{
Console.WriteLine("Processing constant: " + klass.Name + "::" + f.Name);
var mbr = doc.CreateElement("member");
var sum = doc.CreateElement("summary");
var dotNetName = doc.CreateAttribute("name");
dotNetName.Value = "F:" + DOTNET_NAMESPACE + "." + klass.Name + "." + f.Name;
mbr.Attributes.Append(dotNetName);
sum.InnerText = f.Summary;
mbr.AppendChild(sum);
doc.DocumentElement.AppendChild(mbr);
}
private static void ProcessDotNetClassConstructor(XmlDocument doc, Class klass, Method ctor)
{
Console.WriteLine("Processing ctor: " + klass.Name);
var mbr = doc.CreateElement("member");
var sum = doc.CreateElement("summary");
var dotNetName = doc.CreateAttribute("name");
dotNetName.Value = FormatDotNetCtor(klass, ctor);
mbr.Attributes.Append(dotNetName);
sum.InnerText = ctor.Summary;
if (ctor.Parameters.Count > 0)
{
foreach (var p in ctor.Parameters)
{
var pel = doc.CreateElement("param");
var pName = doc.CreateAttribute("name");
pName.Value = p.Name.Replace("*", ""); //Strip pointer
pel.Attributes.Append(pName);
mbr.AppendChild(pel);
}
}
mbr.AppendChild(sum);
doc.DocumentElement.AppendChild(mbr);
}
private static void ProcessDotNetClassConstructor(XmlDocument doc, Class klass, Constructor ctor)
{
Console.WriteLine("Processing ctor: " + klass.Name);
var mbr = doc.CreateElement("member");
var sum = doc.CreateElement("summary");
var dotNetName = doc.CreateAttribute("name");
dotNetName.Value = FormatDotNetCtor(klass, ctor);
mbr.Attributes.Append(dotNetName);
sum.InnerText = ctor.Summary;
if (ctor.Parameters.Count > 0)
{
foreach (var p in ctor.Parameters)
{
var pel = doc.CreateElement("param");
var pName = doc.CreateAttribute("name");
pName.Value = p.Name.Replace("*", ""); //Strip pointer
pel.Attributes.Append(pName);
mbr.AppendChild(pel);
}
}
mbr.AppendChild(sum);
doc.DocumentElement.AppendChild(mbr);
}
private static void ProcessDotNetClassMethod(XmlDocument doc, Class klass, Method method)
{
//Destructors should not be exposed, but nevertheless if they are leaking out here
//ignore them
if (method.Name.StartsWith("~"))
return;
Console.WriteLine("Processing method: " + klass + "::" + method.Name);
var mbr = doc.CreateElement("member");
var sum = doc.CreateElement("summary");
var dotNetName = doc.CreateAttribute("name");
dotNetName.Value = FormatDotNetMethod(klass, method);
mbr.Attributes.Append(dotNetName);
//This may be decorated with markers for IMake.exe, so strip them
var methodSum = method.Summary.Replace("__inherited", "");
//But before we strip __set or __get we too are interested as this
//means a .net property was made for it
if (method.Summary.Contains("__set") || method.Summary.Contains("__get"))
{
ProcessDotNetProperty(doc, klass, GetDotNetPropertyName(method.Name));
}
//Now strip them
methodSum = methodSum.Replace("__set", "").Replace("__get", "");
//Only set summary if it wasn't just these markers
if (methodSum.Trim() != ",")
sum.InnerXml = methodSum;
if (method.Parameters.Count > 0)
{
foreach (var p in method.Parameters)
{
var pel = doc.CreateElement("param");
var pName = doc.CreateAttribute("name");
pName.Value = p.Name.Replace("*", "") //Strip pointer
.Replace("=0", ""); //Strip default parameter values
pel.Attributes.Append(pName);
mbr.AppendChild(pel);
}
}
mbr.AppendChild(sum);
doc.DocumentElement.AppendChild(mbr);
}
private static string GetDotNetPropertyName(string methodName)
{
if (methodName.Length > 1)
{
//Assuming a camel-cased method name, the property name will be the substring
//starting from the second capitalized letter of this method name
for (int i = 1; i < methodName.Length; i++)
{
if (Char.IsUpper(methodName, i))
return methodName.Substring(i);
}
}
return methodName;
}
private static void ProcessDotNetProperty(XmlDocument doc, Class klass, string propertyName)
{
var mbr = doc.CreateElement("member");
var sum = doc.CreateElement("summary");
var dotNetName = doc.CreateAttribute("name");
dotNetName.Value = "P:" + DOTNET_NAMESPACE + "." + klass.Name + "." + propertyName;
mbr.Attributes.Append(dotNetName);
mbr.AppendChild(sum);
doc.DocumentElement.AppendChild(mbr);
}
static XmlDocument foundationDoc = null;
static XmlDocument geometryDoc = null;
static XmlDocument platformBaseDoc = null;
static XmlDocument mapguideCommonDoc = null;
static XmlDocument webDoc = null;
private static XmlDocument ResolveDocument(string clsHeaderLocation)
{
if (clsHeaderLocation == null)
return null;
if (clsHeaderLocation.Contains("/Common/Foundation/"))
return foundationDoc;
else if (clsHeaderLocation.Contains("/Common/Geometry/"))
return geometryDoc;
else if (clsHeaderLocation.Contains("/Common/PlatformBase"))
return platformBaseDoc;
else if (clsHeaderLocation.Contains("/Common/MapGuideCommon"))
return mapguideCommonDoc;
else if (clsHeaderLocation.Contains("/Web/src/WebApp"))
return webDoc;
return null;
}
private static void ProcessDotNet(string srcDir, string outDir)
{
foundationDoc = CreateDotNetDocument("OSGeo.MapGuide.Foundation");
geometryDoc = CreateDotNetDocument("OSGeo.MapGuide.Geometry");
platformBaseDoc = CreateDotNetDocument("OSGeo.MapGuide.PlatformBase");
mapguideCommonDoc = CreateDotNetDocument("OSGeo.MapGuide.MapGuideCommon");
webDoc = CreateDotNetDocument("OSGeo.MapGuide.Web");
var docs = new Docs(srcDir);
docs.LoadGlobalTypes(true);
var globTypes = docs.GetAllGlobalTypes();
foreach (var type in globTypes)
{
var cls = type as Class;
var doc = ResolveDocument(type.Location);
if (cls != null && doc != null)
CreateDotNetClassElement(doc, cls);
}
if (!string.IsNullOrEmpty(outDir))
{
if (!Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
foundationDoc.Save(Path.Combine(outDir, "OSGeo.MapGuide.Foundation.xml"));
geometryDoc.Save(Path.Combine(outDir, "OSGeo.MapGuide.Geometry.xml"));
platformBaseDoc.Save(Path.Combine(outDir, "OSGeo.MapGuide.PlatformBase.xml"));
mapguideCommonDoc.Save(Path.Combine(outDir, "OSGeo.MapGuide.MapGuideCommon.xml"));
webDoc.Save(Path.Combine(outDir, "OSGeo.MapGuide.Web.xml"));
}
else
{
foundationDoc.Save("OSGeo.MapGuide.Foundation.xml");
geometryDoc.Save("OSGeo.MapGuide.Geometry.xml");
platformBaseDoc.Save("OSGeo.MapGuide.PlatformBase.xml");
mapguideCommonDoc.Save("OSGeo.MapGuide.MapGuideCommon.xml");
webDoc.Save("OSGeo.MapGuide.Web.xml");
}
}
static void Usage()
{
Console.WriteLine("DoxyTransform.exe [output path]");
}
}
}