Merge pull request #560 from duboisj/master
[mono.git] / mcs / class / monodoc / Monodoc / providers / EcmaDoc.cs
1 using System;
2 using System.Linq;
3 using System.IO;
4 using System.Text;
5 using System.Xml;
6 using System.Xml.Linq;
7 using System.Collections.Generic;
8
9 using Monodoc.Ecma;
10
11 namespace Monodoc.Providers
12 {
13         public enum EcmaNodeType {
14                 Invalid,
15                 Namespace,
16                 Type,
17                 Member,
18                 Meta, // A node that's here to serve as a header for other node
19         }
20
21         // Common functionality between ecma-provider and ecmauncompiled-provider
22         internal class EcmaDoc
23         {
24                 static EcmaUrlParser parser = new EcmaUrlParser ();
25
26                 public static void PopulateTreeFromIndexFile (string indexFilePath,
27                                                               string idPrefix,
28                                                               Tree tree,
29                                                               IDocStorage storage,
30                                                               Dictionary<string, XElement> nsSummaries,
31                                                               Func<XElement, string> indexGenerator = null)
32                 {
33                         var root = tree.RootNode;
34                         int resID = 0;
35                         var asm = Path.GetDirectoryName (indexFilePath);
36
37                         storage = storage ?? new Storage.NullStorage ();
38                         // nsSummaries is allowed to be null if the user doesn't care about it
39                         nsSummaries = nsSummaries ?? new Dictionary<string, XElement> ();
40                         // default index generator uses a counter
41                         indexGenerator = indexGenerator ?? (_ => resID++.ToString ());
42
43                         using (var reader = XmlReader.Create (File.OpenRead (indexFilePath))) {
44                                 reader.ReadToFollowing ("Types");
45                                 var types = XElement.Load (reader.ReadSubtree ());
46
47                                 foreach (var ns in types.Elements ("Namespace")) {
48                                         var nsName = (string)ns.Attribute ("Name");
49                                         nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
50                                         var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
51
52                                         XElement nsElements;
53                                         if (!nsSummaries.TryGetValue (nsName, out nsElements))
54                                                 nsSummaries[nsName] = nsElements = new XElement ("elements",
55                                                                                                  new XElement ("summary"),
56                                                                                                  new XElement ("remarks"));
57
58                                         foreach (var type in ns.Elements ("Type")) {
59                                                 // Add the XML file corresponding to the type to our storage
60                                                 var id = indexGenerator (type);
61                                                 string typeFilePath;
62                                                 var typeDocument = EcmaDoc.LoadTypeDocument (asm, nsName, type.Attribute ("Name").Value, out typeFilePath);
63                                                 if (typeDocument == null)
64                                                         continue;
65                                                 using (var file = File.OpenRead (typeFilePath))
66                                                         storage.Store (id, file);
67                                                 nsElements.Add (ExtractClassSummary (typeFilePath));
68
69                                                 var typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type);
70                                                 var url = idPrefix + id + '#' + typeCaption + '/';
71                                                 typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type, true);
72                                                 var typeNode = nsNode.CreateNode (typeCaption, url);
73
74                                                 // Add meta "Members" node
75                                                 typeNode.CreateNode ("Members", "*");
76                                                 var membersNode = typeDocument.Root.Element ("Members");
77                                                 if (membersNode == null || !membersNode.Elements ().Any ())
78                                                         continue;
79                                                 var members = membersNode
80                                                         .Elements ("Member")
81                                                         .ToLookup (EcmaDoc.GetMemberType);
82
83                                                 foreach (var memberType in members) {
84                                                         // We pluralize the member type to get the caption and take the first letter as URL
85                                                         var node = typeNode.CreateNode (EcmaDoc.PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
86                                                         var memberIndex = 0;
87
88                                                         var isCtors = memberType.Key[0] == 'C';
89
90                                                         // We do not escape much member name here
91                                                         foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
92                                                                 if (memberGroup.Count () > 1) {
93                                                                         // Generate overload
94                                                                         var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
95                                                                         var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
96                                                                         foreach (var member in memberGroup)
97                                                                                 overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
98                                                                         overloadNode.Sort ();
99                                                                 } else {
100                                                                         // We treat constructor differently by showing their argument list in all cases
101                                                                         node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
102                                                                 }
103                                                         }
104                                                         node.Sort ();
105                                                 }
106                                         }
107
108                                         nsNode.Sort ();
109                                 }
110                                 root.Sort ();
111                         }
112                 }
113
114                 // Utility methods
115
116                 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName)
117                 {
118                         string dummy;
119                         return LoadTypeDocument (basePath, nsName, typeName, out dummy);
120                 }
121
122                 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, out string finalPath)
123                 {
124                         finalPath = Path.Combine (basePath, nsName, Path.ChangeExtension (typeName, ".xml"));
125                         if (!File.Exists (finalPath)) {
126                                 Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", finalPath);
127                                 return null;
128                         }
129
130                         XDocument doc = null;
131                         try {
132                                 doc = XDocument.Load (finalPath);
133                         } catch (Exception e) {
134                                 Console.WriteLine ("Document `{0}' is unparsable, {1}", finalPath, e.ToString ());
135                         }
136
137                         return doc;
138                 }
139
140                 public static string GetTypeCaptionFromIndex (XElement typeNodeFromIndex, bool full = false)
141                 {
142                         var t = typeNodeFromIndex;
143                         var c = ((string)(t.Attribute ("DisplayName") ?? t.Attribute ("Name"))).Replace ('+', '.');
144                         if (full)
145                                 c += " " + (string)t.Attribute ("Kind");
146                         return c;
147                 }
148
149                 public static string PluralizeMemberType (string memberType)
150                 {
151                         switch (memberType) {
152                         case "Property":
153                                 return "Properties";
154                         default:
155                                 return memberType + "s";
156                         }
157                 }
158
159                 public static string GetMemberType (XElement m)
160                 {
161                         return m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value;
162                 }
163
164                 public static string MakeMemberCaption (XElement member, bool withArguments)
165                 {
166                         var caption = (string)member.Attribute ("MemberName");
167                         // Use type name instead of .ctor for cosmetic sake
168                         if (caption == ".ctor") {
169                                 caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
170                                 // If this is an inner type ctor, strip the parent type reference
171                                 var plusIndex = caption.LastIndexOf ('+');
172                                 if (plusIndex != -1)
173                                         caption = caption.Substring (plusIndex + 1);
174                         }
175                         if (caption.StartsWith ("op_")) {
176                                 string sig;
177                                 caption = MakeOperatorSignature (member, out sig);
178                                 caption = withArguments ? sig : caption;
179                                 return caption;
180                         }
181                         if (withArguments) {
182                                 var args = member.Element ("Parameters");
183                                 caption += '(';
184                                 if (args != null && args.Elements ("Parameter").Any ()) {
185                                         caption += args.Elements ("Parameter")
186                                                 .Select (p => (string)p.Attribute ("Type"))
187                                                 .Aggregate ((p1, p2) => p1 + "," + p2);
188                                 }
189                                 caption += ')';
190                         }
191                         
192                         return caption;
193                 }
194
195                 public static Node MatchNodeWithEcmaUrl (string url, Tree tree)
196                 {
197                         Node result = null;
198                         EcmaDesc desc;
199                         if (!parser.TryParse (url, out desc))
200                                 return null;
201
202                         // Namespace search
203                         Node currentNode = tree.RootNode;
204                         Node searchNode = new Node () { Caption = desc.Namespace };
205                         int index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
206                         if (index >= 0)
207                                 result = currentNode.ChildNodes[index];
208                         if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
209                                 return result;
210
211                         // Type search
212                         currentNode = result;
213                         result = null;
214                         searchNode.Caption = desc.ToCompleteTypeName ();
215                         index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
216                         if (index >= 0)
217                                 result = currentNode.ChildNodes[index];
218                         if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
219                                 return result;
220
221                         // Member selection
222                         currentNode = result;
223                         result = null;
224                         var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
225                         currentNode = FindNodeForCaption (currentNode.ChildNodes, caption);
226                         if (currentNode == null
227                             || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
228                                 return currentNode;
229
230                         // Member search
231                         result = null;
232                         var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
233                         searchNode.Caption = desc.ToCompleteMemberName (format);
234                         index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
235                         if (index < 0)
236                                 return null;
237                         result = currentNode.ChildNodes[index];
238                         if (result.ChildNodes.Count == 0 || desc.IsEtc)
239                                 return result;
240
241                         // Overloads search
242                         currentNode = result;
243                         searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
244                         index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
245                         if (index < 0)
246                                 return result;
247                         result = result.ChildNodes[index];
248
249                         return result;
250                 }
251
252                 // This comparer returns the answer straight from caption comparison
253                 class EcmaGenericNodeComparer : IComparer<Node>
254                 {
255                         public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
256
257                         public int Compare (Node n1, Node n2)
258                         {
259                                 return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
260                         }
261                 }
262
263                 // This comparer take into account the space in the caption
264                 class EcmaTypeNodeComparer : IComparer<Node>
265                 {
266                         public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
267
268                         public int Compare (Node n1, Node n2)
269                         {
270                                 int length1 = CaptionLength (n1.Caption);
271                                 int length2 = CaptionLength (n2.Caption);
272
273                                 return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
274                         }
275
276                         int CaptionLength (string caption)
277                         {
278                                 var length = caption.LastIndexOf (' ');
279                                 return length == -1 ? caption.Length : length;
280                         }
281                 }
282
283                 public static Dictionary<string, string> GetContextForEcmaNode (string hash, string sourceID, Node node)
284                 {
285                         var args = new Dictionary<string, string> ();
286
287                         args["source-id"] = sourceID;
288
289                         if (node != null) {
290                                 var nodeType = GetNodeType (node);
291                                 switch (nodeType) {
292                                 case EcmaNodeType.Namespace:
293                                         args["show"] = "namespace";
294                                         args["namespace"] = node.Element.Substring ("N:".Length);
295                                         break;
296                                 case EcmaNodeType.Type:
297                                         args["show"] = "typeoverview";
298                                         break;
299                                 case EcmaNodeType.Member:
300                                 case EcmaNodeType.Meta:
301                                         switch (GetNodeMemberTypeChar (node)){
302                                         case 'C':
303                                                 args["membertype"] = "Constructor";
304                                                 break;
305                                         case 'M':
306                                                 args["membertype"] = "Method";
307                                                 break;
308                                         case 'P':
309                                                 args["membertype"] = "Property";
310                                                 break;
311                                         case 'F':
312                                                 args["membertype"] = "Field";
313                                                 break;
314                                         case 'E':
315                                                 args["membertype"] = "Event";
316                                                 break;
317                                         case 'O':
318                                                 args["membertype"] = "Operator";
319                                                 break;
320                                         case 'X':
321                                                 args["membertype"] = "ExtensionMethod";
322                                                 break;
323                                         case '*':
324                                                 args["membertype"] = "All";
325                                                 break;
326                                         }
327
328                                         if (nodeType == EcmaNodeType.Meta) {
329                                                 args["show"] = "members";
330                                                 args["index"] = "all";
331                                         } else {
332                                                 args["show"] = "member";
333                                                 args["index"] = node.Element;
334                                         }
335                                         break;
336                                 }
337                         }
338
339                         if (!string.IsNullOrEmpty (hash))
340                                 args["hash"] = hash;
341
342                         return args;
343                 }
344
345                 public static EcmaNodeType GetNodeType (Node node)
346                 {
347                         // We guess the node type by checking the depth level it's at in the tree
348                         int level = GetNodeLevel (node);
349                         switch (level) {
350                         case 0:
351                                 return EcmaNodeType.Namespace;
352                         case 1:
353                                 return EcmaNodeType.Type;
354                         case 2:
355                                 return EcmaNodeType.Meta;
356                         case 3: // Here it's either a member or, in case of overload, a meta
357                                 return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
358                         case 4: // At this level, everything is necessarily a member
359                                 return EcmaNodeType.Member;
360                         default:
361                                 return EcmaNodeType.Invalid;
362                         }
363                 }
364
365                 public static char GetNodeMemberTypeChar (Node node)
366                 {
367                         int level = GetNodeLevel (node);
368                         // We try to reach the member group node depending on node nested level
369                         switch (level) {
370                         case 2:
371                                 return node.Element[0];
372                         case 3:
373                                 return node.Parent.Element[0];
374                         case 4:
375                                 return node.Parent.Parent.Element[0];
376                         default:
377                                 throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
378                         }
379                 }
380
381                 public static int GetNodeLevel (Node node)
382                 {
383                         int i = 0;
384                         for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++)
385                                 node = node.Parent;
386                         return i - 1;
387                 }
388
389                 public static string EtcKindToCaption (char etc)
390                 {
391                         switch (etc) {
392                         case 'M':
393                                 return "Methods";
394                         case 'P':
395                                 return "Properties";
396                         case 'C':
397                                 return "Constructors";
398                         case 'F':
399                                 return "Fields";
400                         case 'E':
401                                 return "Events";
402                         case 'O':
403                                 return "Operators";
404                         case '*':
405                                 return "Members";
406                         default:
407                                 return null;
408                         }
409                 }
410
411                 public static string MemberKindToCaption (EcmaDesc.Kind kind)
412                 {
413                         switch (kind) {
414                         case EcmaDesc.Kind.Method:
415                                 return "Methods";
416                         case EcmaDesc.Kind.Property:
417                                 return "Properties";
418                         case EcmaDesc.Kind.Constructor:
419                                 return "Constructors";
420                         case EcmaDesc.Kind.Field:
421                                 return "Fields";
422                         case EcmaDesc.Kind.Event:
423                                 return "Events";
424                         case EcmaDesc.Kind.Operator:
425                                 return "Operators";
426                         default:
427                                 return null;
428                         }
429                 }
430
431                 public static Node FindNodeForCaption (List<Node> nodes, string caption)
432                 {
433                         foreach (var node in nodes)
434                                 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
435                                         return node;
436                         return null;
437                 }
438
439                 internal static string MakeOperatorSignature (XElement member, out string memberSignature)
440                 {
441                         string name = (string)member.Attribute ("MemberName");
442                         var nicename = name.Substring(3);
443                         memberSignature = null;
444
445                         switch (name) {
446                         // unary operators: no overloading possible     [ECMA-335 §10.3.1]
447                         case "op_UnaryPlus":                    // static     R operator+       (T)
448                         case "op_UnaryNegation":                // static     R operator-       (T)
449                         case "op_LogicalNot":                   // static     R operator!       (T)
450                         case "op_OnesComplement":               // static     R operator~       (T)
451                         case "op_Increment":                    // static     R operator++      (T)
452                         case "op_Decrement":                    // static     R operator--      (T)
453                         case "op_True":                         // static  bool operator true   (T)
454                         case "op_False":                        // static  bool operator false  (T)
455                         case "op_AddressOf":                    // static     R operator&       (T)
456                         case "op_PointerDereference":           // static     R operator*       (T)
457                                 memberSignature = nicename;
458                                 break;
459                         // conversion operators: overloading based on parameter and return type [ECMA-335 §10.3.3]
460                         case "op_Implicit":                    // static implicit operator R (T)
461                         case "op_Explicit":                    // static explicit operator R (T)
462                                 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
463                                 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
464                                 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
465                                 memberSignature = arg + " to " + ret;
466                                 break;
467                         // binary operators: overloading is possible [ECMA-335 §10.3.2]
468                         default:
469                                 memberSignature =
470                                         nicename + "("
471                                         + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
472                                         + ")";
473                                 break;
474                         }
475
476                         return nicename;
477                 }
478
479                 static XElement ExtractClassSummary (string typeFilePath)
480                 {
481                         using (var reader = XmlReader.Create (typeFilePath)) {
482                                 reader.ReadToFollowing ("Type");
483                                 var name = reader.GetAttribute ("Name");
484                                 var fullName = reader.GetAttribute ("FullName");
485                                 reader.ReadToFollowing ("AssemblyName");
486                                 var assemblyName = reader.ReadElementString ();
487                                 reader.ReadToFollowing ("summary");
488                                 var summary = reader.ReadInnerXml ();
489                                 reader.ReadToFollowing ("remarks");
490                                 var remarks = reader.ReadInnerXml ();
491
492                                 return new XElement ("class",
493                                                      new XAttribute ("name", name ?? string.Empty),
494                                                      new XAttribute ("fullname", fullName ?? string.Empty),
495                                                      new XAttribute ("assembly", assemblyName ?? string.Empty),
496                                                      new XElement ("summary", new XCData (summary)),
497                                                      new XElement ("remarks", new XCData (remarks)));
498                         }
499                 }
500         }
501 }