2 // The assembler: Help compiler.
5 // Miguel de Icaza (miguel@gnome.org)
7 // (C) 2003 Ximian, Inc.
10 using System.Collections.Generic;
14 using Monodoc.Providers;
17 using System.Xml.Linq;
18 using System.Xml.XPath;
21 namespace Mono.Documentation {
23 public class MDocAssembler : MDocCommand {
24 static readonly string[] ValidFormats = {
34 string droppedNamespace = null;
36 public static Option[] CreateFormatOptions (MDocCommand self, Dictionary<string, List<string>> formats)
38 string cur_format = "ecma";
39 var options = new OptionSet () {
41 "The documentation {FORMAT} used in DIRECTORIES. " +
42 "Valid formats include:\n " +
43 string.Join ("\n ", ValidFormats) + "\n" +
44 "If not specified, the default format is `ecma'.",
46 if (Array.IndexOf (ValidFormats, v) < 0)
47 self.Error ("Invalid documentation format: {0}.", v);
50 { "<>", v => AddFormat (self, formats, cur_format, v) },
52 return new Option[]{options[0], options[1]};
55 public override void Run (IEnumerable<string> args)
57 bool replaceNTypes = false;
58 var formats = new Dictionary<string, List<string>> ();
59 string prefix = "tree";
60 var formatOptions = CreateFormatOptions (this, formats);
61 var options = new OptionSet () {
64 "Provides the output file prefix; the files {PREFIX}.zip and " +
65 "{PREFIX}.tree will be created.\n" +
66 "If not specified, `tree' is the default PREFIX.",
69 {"dropns=","The namespace that has been dropped from this version of the assembly.", v => droppedNamespace = v },
70 {"ntypes","Replace references to native types with their original types.", v => replaceNTypes=true },
72 List<string> extra = Parse (options, args, "assemble",
73 "[OPTIONS]+ DIRECTORIES",
74 "Assemble documentation within DIRECTORIES for use within the monodoc browser.");
78 List<Provider> list = new List<Provider> ();
79 EcmaProvider ecma = null;
82 foreach (string format in formats.Keys) {
86 ecma = new EcmaProvider ();
90 ecma.FileSource = new MDocFileSource(droppedNamespace, string.IsNullOrWhiteSpace(droppedNamespace) ? ApiStyle.Unified : ApiStyle.Classic) {
91 ReplaceNativeTypes = replaceNTypes
93 foreach (string dir in formats [format])
94 ecma.AddDirectory (dir);
99 list.AddRange (formats [format].Select (d => (Provider) new XhtmlProvider (d)));
103 list.Add (new ManProvider (formats [format].ToArray ()));
107 list.AddRange (formats [format].Select (d => (Provider) new ErrorProvider (d)));
111 list.AddRange (formats [format].Select (d => (Provider) new EcmaSpecProvider (d)));
115 list.AddRange (formats [format].Select (d => (Provider) new AddinsProvider (d)));
120 HelpSource hs = new HelpSource (prefix, true);
121 hs.TraceLevel = TraceLevel;
123 foreach (Provider p in list) {
124 p.PopulateTree (hs.Tree);
127 if (sort && hs.Tree != null)
128 hs.Tree.RootNode.Sort ();
131 // Flushes the EcmaProvider
133 foreach (Provider p in list)
134 p.CloseTree (hs, hs.Tree);
139 private static void AddFormat (MDocCommand self, Dictionary<string, List<string>> d, string format, string file)
142 self.Error ("No format specified.");
144 if (!d.TryGetValue (format, out l)) {
145 l = new List<string> ();
153 /// A custom provider file source that lets us modify the source files before they are processed by monodoc.
155 internal class MDocFileSource : IEcmaProviderFileSource {
156 private string droppedNamespace;
157 private bool shouldDropNamespace = false;
158 private ApiStyle styleToDrop;
160 public bool ReplaceNativeTypes { get; set; }
162 /// <param name="ns">The namespace that is being dropped.</param>
163 /// <param name="style">The style that is being dropped.</param>
164 public MDocFileSource(string ns, ApiStyle style)
166 droppedNamespace = ns;
167 shouldDropNamespace = !string.IsNullOrWhiteSpace (ns);
171 public XmlReader GetIndexReader(string path)
173 XDocument doc = XDocument.Load (path);
175 DropApiStyle (doc, path);
176 DropNSFromDocument (doc);
178 // now put the modified contents into a stream for the XmlReader that monodoc will use.
179 MemoryStream io = new MemoryStream ();
180 using (var writer = XmlWriter.Create (io)) {
181 doc.WriteTo (writer);
183 io.Seek (0, SeekOrigin.Begin);
185 return XmlReader.Create (io);
188 public XElement GetNamespaceElement(string path)
190 var element = XElement.Load (path);
192 var attributes = element.Descendants ().Concat(new XElement[] { element }).SelectMany (n => n.Attributes ());
193 var textNodes = element.Nodes ().OfType<XText> ();
195 DropNS (attributes, textNodes);
200 void DropApiStyle(XDocument doc, string path)
202 string styleString = styleToDrop.ToString ().ToLower ();
205 .Where (n => n.Attributes ()
206 .Any (a => a.Name.LocalName == "apistyle" && a.Value == styleString))
209 foreach (var element in items) {
213 if (styleToDrop == ApiStyle.Classic && ReplaceNativeTypes) {
214 RewriteCrefsIfNecessary (doc, path);
218 void RewriteCrefsIfNecessary (XDocument doc, string path)
220 // we also have to rewrite crefs
221 var sees = doc.Descendants ().Where (d => d.Name.LocalName == "see").ToArray ();
222 foreach (var see in sees) {
223 var cref = see.Attribute ("cref");
227 EcmaUrlParser parser = new EcmaUrlParser ();
229 if (!parser.TryParse (cref.Value, out reference)) {
232 if ((new EcmaDesc.Kind[] {
233 EcmaDesc.Kind.Constructor,
235 }).Any (k => k == reference.DescKind)) {
236 string ns = reference.Namespace;
237 string type = reference.TypeName;
238 string memberName = reference.MemberName;
239 if (reference.MemberArguments != null) {
240 XDocument refDoc = FindReferenceDoc (path, doc, ns, type);
241 if (refDoc == null) {
244 // look in the refDoc for the memberName, and match on parameters and # of type parameters
245 var overloads = refDoc.XPathSelectElements ("//Member[@MemberName='" + memberName + "']").ToArray ();
246 // Do some initial filtering to find members that could potentially match (based on parameter and typeparam counts)
247 var members = overloads.Where (e => reference.MemberArgumentsCount == e.XPathSelectElements ("Parameters/Parameter[not(@apistyle) or @apistyle='classic']").Count () && reference.GenericMemberArgumentsCount == e.XPathSelectElements ("TypeParameters/TypeParameter[not(@apistyle) or @apistyle='classic']").Count ()).Select (m => new {
249 AllParameters = m.XPathSelectElements ("Parameters/Parameter").ToArray (),
250 Parameters = m.XPathSelectElements ("Parameters/Parameter[not(@apistyle) or @apistyle='classic']").ToArray (),
251 NewParameters = m.XPathSelectElements ("Parameters/Parameter[@apistyle='unified']").ToArray ()
253 // now find the member that matches on types
254 var member = members.FirstOrDefault (m => reference.MemberArguments.All (r => m.Parameters.Any (mp => mp.Attribute ("Type").Value.Contains (r.TypeName))));
255 if (member == null || member.NewParameters.Length == 0)
257 foreach (var arg in reference.MemberArguments) {
258 // find the "classic" parameter
259 var oldParam = member.Parameters.First (p => p.Attribute ("Type").Value.Contains (arg.TypeName));
260 var newParam = member.NewParameters.FirstOrDefault (p => oldParam.Attribute ("Name").Value == p.Attribute ("Name").Value);
261 if (newParam != null) {
262 // this means there was a change made, and we should try to convert this cref
263 arg.TypeName = NativeTypeManager.ConvertToNativeType (arg.TypeName);
266 var rewrittenReference = reference.ToEcmaCref ();
267 Console.WriteLine ("From {0} to {1}", cref.Value, rewrittenReference);
268 cref.Value = rewrittenReference;
274 XDocument FindReferenceDoc (string currentPath, XDocument currentDoc, string ns, string type)
276 if (currentPath.Length <= 1) {
279 // build up the supposed path to the doc
280 string dir = Path.GetDirectoryName (currentPath);
281 if (dir.Equals (currentPath)) {
285 string supposedPath = Path.Combine (dir, ns, type + ".xml");
287 // if it's the current path, return currentDoc
288 if (supposedPath == currentPath) {
292 if (!File.Exists (supposedPath)) {
293 // hmm, file not there, look one directory up
294 return FindReferenceDoc (dir, currentDoc, ns, type);
297 // otherwise, load the XDoc and return
298 return XDocument.Load (supposedPath);
301 void DropNSFromDocument (XDocument doc)
303 var attributes = doc.Descendants ().SelectMany (n => n.Attributes ());
304 var textNodes = doc.DescendantNodes().OfType<XText> ().ToArray();
306 DropNS (attributes, textNodes);
309 void DropNS(IEnumerable<XAttribute> attributes, IEnumerable<XText> textNodes)
311 if (!shouldDropNamespace) {
315 string nsString = string.Format ("{0}.", droppedNamespace);
316 foreach (var attr in attributes) {
317 if (attr.Value.Contains (nsString)) {
318 attr.Value = attr.Value.Replace (nsString, string.Empty);
322 foreach (var textNode in textNodes) {
323 if (textNode.Value.Contains (nsString)) {
324 textNode.Value = textNode.Value.Replace (nsString, string.Empty);
330 /// <param name="nsName">This is the type's name in the processed XML content.
331 /// If dropping the namespace, we'll need to append it so that it's found in the source.</param>
332 /// <param name="typeName">Type name.</param>
333 public string GetTypeXmlPath(string basePath, string nsName, string typeName)
335 string nsNameToUse = nsName;
336 if (shouldDropNamespace) {
337 nsNameToUse = string.Format ("{0}.{1}", droppedNamespace, nsName);
339 var droppedPath = BuildTypeXmlPath (basePath, typeName, nsNameToUse);
340 var origPath = BuildTypeXmlPath (basePath, typeName, nsName);
342 if (!File.Exists (droppedPath)) {
343 if (File.Exists (origPath)) {
351 var finalPath = BuildTypeXmlPath (basePath, typeName, nsNameToUse);
357 static string BuildTypeXmlPath (string basePath, string typeName, string nsNameToUse)
359 string finalPath = Path.Combine (basePath, nsNameToUse, Path.ChangeExtension (typeName, ".xml"));
363 static string BuildNamespaceXmlPath (string basePath, string ns)
365 var nsFileName = Path.Combine (basePath, String.Format ("ns-{0}.xml", ns));
369 /// <returns>The namespace for path, with the dropped namespace so it can be used to pick the right file if we're dropping it.</returns>
370 /// <param name="ns">This namespace will already have "dropped" the namespace.</param>
371 public string GetNamespaceXmlPath(string basePath, string ns)
373 string nsNameToUse = ns;
374 if (shouldDropNamespace) {
375 nsNameToUse = string.Format ("{0}.{1}", droppedNamespace, ns);
377 var droppedPath = BuildNamespaceXmlPath (basePath, nsNameToUse);
378 var origPath = BuildNamespaceXmlPath (basePath, ns);
380 if (!File.Exists (droppedPath) && File.Exists(origPath)) {
386 var path = BuildNamespaceXmlPath (basePath, ns);
391 public XDocument GetTypeDocument(string path)
393 var doc = XDocument.Load (path);
394 DropApiStyle (doc, path);
395 DropNSFromDocument (doc);
400 public XElement ExtractNamespaceSummary (string path)
402 using (var reader = GetIndexReader (path)) {
403 reader.ReadToFollowing ("Namespace");
404 var name = reader.GetAttribute ("Name");
405 var summary = reader.ReadToFollowing ("summary") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("summary");
406 var remarks = reader.ReadToFollowing ("remarks") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("remarks");
408 return new XElement ("namespace",
409 new XAttribute ("ns", name ?? string.Empty),