7 using System.Collections.Generic;
11 namespace Monodoc.Providers
13 public enum EcmaNodeType {
18 Meta, // A node that's here to serve as a header for other node
21 // Common functionality between ecma-provider and ecmauncompiled-provider
22 internal class EcmaDoc
24 static EcmaUrlParser parser = new EcmaUrlParser ();
26 public static void PopulateTreeFromIndexFile (string indexFilePath,
30 Dictionary<string, XElement> nsSummaries,
31 Func<XElement, string> indexGenerator = null)
33 var root = tree.RootNode;
35 var asm = Path.GetDirectoryName (indexFilePath);
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 ());
43 using (var reader = XmlReader.Create (File.OpenRead (indexFilePath))) {
44 reader.ReadToFollowing ("Types");
45 var types = XElement.Load (reader.ReadSubtree ());
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);
53 if (!nsSummaries.TryGetValue (nsName, out nsElements))
54 nsSummaries[nsName] = nsElements = new XElement ("elements",
55 new XElement ("summary"),
56 new XElement ("remarks"));
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);
62 var typeDocument = EcmaDoc.LoadTypeDocument (asm, nsName, type.Attribute ("Name").Value, out typeFilePath);
63 if (typeDocument == null)
65 using (var file = File.OpenRead (typeFilePath))
66 storage.Store (id, file);
67 nsElements.Add (ExtractClassSummary (typeFilePath));
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);
74 // Add meta "Members" node
75 typeNode.CreateNode ("Members", "*");
76 var membersNode = typeDocument.Root.Element ("Members");
77 if (membersNode == null || !membersNode.Elements ().Any ())
79 var members = membersNode
81 .ToLookup (EcmaDoc.GetMemberType);
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 ());
88 var isCtors = memberType.Key[0] == 'C';
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) {
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 ());
100 // We treat constructor differently by showing their argument list in all cases
101 node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
116 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName)
119 return LoadTypeDocument (basePath, nsName, typeName, out dummy);
122 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, out string finalPath)
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);
130 XDocument doc = null;
132 doc = XDocument.Load (finalPath);
133 } catch (Exception e) {
134 Console.WriteLine ("Document `{0}' is unparsable, {1}", finalPath, e.ToString ());
140 public static string GetTypeCaptionFromIndex (XElement typeNodeFromIndex, bool full = false)
142 var t = typeNodeFromIndex;
143 var c = ((string)(t.Attribute ("DisplayName") ?? t.Attribute ("Name"))).Replace ('+', '.');
145 c += " " + (string)t.Attribute ("Kind");
149 public static string PluralizeMemberType (string memberType)
151 switch (memberType) {
155 return memberType + "s";
159 public static string GetMemberType (XElement m)
161 return m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value;
164 public static string MakeMemberCaption (XElement member, bool withArguments)
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 ('+');
173 caption = caption.Substring (plusIndex + 1);
175 if (caption.StartsWith ("op_")) {
177 caption = MakeOperatorSignature (member, out sig);
178 caption = withArguments ? sig : caption;
182 var args = member.Element ("Parameters");
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);
195 public static Node MatchNodeWithEcmaUrl (string url, Tree tree)
199 if (!parser.TryParse (url, out desc))
203 Node currentNode = tree.RootNode;
204 Node searchNode = new Node () { Caption = desc.Namespace };
205 int index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
207 result = currentNode.ChildNodes[index];
208 if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
212 currentNode = result;
214 searchNode.Caption = desc.ToCompleteTypeName ();
215 if (!desc.GenericTypeArgumentsIsNumeric)
216 index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
218 index = GenericTypeBacktickSearch (currentNode.ChildNodes, desc);
220 result = currentNode.ChildNodes[index];
221 if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
225 currentNode = result;
227 var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
228 currentNode = FindNodeForCaption (currentNode.ChildNodes, caption);
229 if (currentNode == null
230 || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
235 var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
236 searchNode.Caption = desc.ToCompleteMemberName (format);
237 index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
240 result = currentNode.ChildNodes[index];
241 if (result.ChildNodes.Count == 0 || desc.IsEtc)
245 currentNode = result;
246 searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
247 index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
250 result = result.ChildNodes[index];
255 static int GenericTypeBacktickSearch (IList<Node> childNodes, EcmaDesc desc)
257 /* Our strategy is to search for the non-generic variant of the type
258 * (which in most case should fail) and then use the closest index
259 * to linearily search for the generic variant with the right generic arg number
261 var searchNode = new Node () { Caption = desc.TypeName };
262 int index = childNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
263 // Place the index in the right start position
267 for (int i = index; i < childNodes.Count; i++) {
268 var currentNode = childNodes[i];
269 // Find the index of the generic argument list
270 int genericIndex = currentNode.Caption.IndexOf ('<');
271 // If we are not on the same base type name anymore, there is no point
272 int captionSlice = genericIndex != -1 ? genericIndex : currentNode.Caption.LastIndexOf (' ');
273 if (string.Compare (searchNode.Caption, 0,
274 currentNode.Caption, 0,
275 Math.Max (captionSlice, searchNode.Caption.Length),
276 StringComparison.Ordinal) != 0)
279 var numGenerics = CountTypeGenericArguments (currentNode.Caption, genericIndex);
280 if (numGenerics == desc.GenericTypeArguments.Count) {
281 // Simple comparison if we are not looking for an inner type
282 if (desc.NestedType == null)
284 // If more complicated, we fallback to using EcmaUrlParser
285 var caption = currentNode.Caption;
286 caption = "T:" + caption.Substring (0, caption.LastIndexOf (' ')).Replace ('.', '+');
288 var parser = new EcmaUrlParser ();
289 if (parser.TryParse (caption, out otherDesc) && desc.NestedType.Equals (otherDesc.NestedType))
297 // This comparer returns the answer straight from caption comparison
298 class EcmaGenericNodeComparer : IComparer<Node>
300 public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
302 public int Compare (Node n1, Node n2)
304 return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
308 // This comparer take into account the space in the caption
309 class EcmaTypeNodeComparer : IComparer<Node>
311 public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
313 public int Compare (Node n1, Node n2)
315 int length1 = CaptionLength (n1.Caption);
316 int length2 = CaptionLength (n2.Caption);
318 return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
321 int CaptionLength (string caption)
323 var length = caption.LastIndexOf (' ');
324 return length == -1 ? caption.Length : length;
328 public static Dictionary<string, string> GetContextForEcmaNode (string hash, string sourceID, Node node)
330 var args = new Dictionary<string, string> ();
332 args["source-id"] = sourceID;
335 var nodeType = GetNodeType (node);
337 case EcmaNodeType.Namespace:
338 args["show"] = "namespace";
339 args["namespace"] = node.Element.Substring ("N:".Length);
341 case EcmaNodeType.Type:
342 args["show"] = "typeoverview";
344 case EcmaNodeType.Member:
345 case EcmaNodeType.Meta:
346 switch (GetNodeMemberTypeChar (node)){
348 args["membertype"] = "Constructor";
351 args["membertype"] = "Method";
354 args["membertype"] = "Property";
357 args["membertype"] = "Field";
360 args["membertype"] = "Event";
363 args["membertype"] = "Operator";
366 args["membertype"] = "ExtensionMethod";
369 args["membertype"] = "All";
373 if (nodeType == EcmaNodeType.Meta) {
374 args["show"] = "members";
375 args["index"] = "all";
377 args["show"] = "member";
378 args["index"] = node.Element;
384 if (!string.IsNullOrEmpty (hash))
390 public static EcmaNodeType GetNodeType (Node node)
392 // We guess the node type by checking the depth level it's at in the tree
393 int level = GetNodeLevel (node);
396 return EcmaNodeType.Namespace;
398 return EcmaNodeType.Type;
400 return EcmaNodeType.Meta;
401 case 3: // Here it's either a member or, in case of overload, a meta
402 return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
403 case 4: // At this level, everything is necessarily a member
404 return EcmaNodeType.Member;
406 return EcmaNodeType.Invalid;
410 public static char GetNodeMemberTypeChar (Node node)
412 int level = GetNodeLevel (node);
413 // We try to reach the member group node depending on node nested level
416 return node.Element[0];
418 return node.Parent.Element[0];
420 return node.Parent.Parent.Element[0];
422 throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
426 public static int GetNodeLevel (Node node)
429 for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++)
434 public static string EtcKindToCaption (char etc)
442 return "Constructors";
456 public static string MemberKindToCaption (EcmaDesc.Kind kind)
459 case EcmaDesc.Kind.Method:
461 case EcmaDesc.Kind.Property:
463 case EcmaDesc.Kind.Constructor:
464 return "Constructors";
465 case EcmaDesc.Kind.Field:
467 case EcmaDesc.Kind.Event:
469 case EcmaDesc.Kind.Operator:
476 public static Node FindNodeForCaption (IList<Node> nodes, string caption)
478 foreach (var node in nodes)
479 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
484 public static int CountTypeGenericArguments (string typeDefinition, int startIndex = 0)
488 bool started = false;
490 foreach (char c in typeDefinition.Skip (startIndex)) {
499 if (started && nestedLevel == 1)
511 internal static string MakeOperatorSignature (XElement member, out string memberSignature)
513 string name = (string)member.Attribute ("MemberName");
514 var nicename = name.Substring(3);
515 memberSignature = null;
518 // unary operators: no overloading possible [ECMA-335 §10.3.1]
519 case "op_UnaryPlus": // static R operator+ (T)
520 case "op_UnaryNegation": // static R operator- (T)
521 case "op_LogicalNot": // static R operator! (T)
522 case "op_OnesComplement": // static R operator~ (T)
523 case "op_Increment": // static R operator++ (T)
524 case "op_Decrement": // static R operator-- (T)
525 case "op_True": // static bool operator true (T)
526 case "op_False": // static bool operator false (T)
527 case "op_AddressOf": // static R operator& (T)
528 case "op_PointerDereference": // static R operator* (T)
529 memberSignature = nicename;
531 // conversion operators: overloading based on parameter and return type [ECMA-335 §10.3.3]
532 case "op_Implicit": // static implicit operator R (T)
533 case "op_Explicit": // static explicit operator R (T)
534 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
535 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
536 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
537 memberSignature = arg + " to " + ret;
539 // binary operators: overloading is possible [ECMA-335 §10.3.2]
543 + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
551 static XElement ExtractClassSummary (string typeFilePath)
553 using (var reader = XmlReader.Create (typeFilePath)) {
554 reader.ReadToFollowing ("Type");
555 var name = reader.GetAttribute ("Name");
556 var fullName = reader.GetAttribute ("FullName");
557 reader.ReadToFollowing ("AssemblyName");
558 var assemblyName = reader.ReadElementString ();
559 var summary = reader.ReadToFollowing ("summary") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("summary");
560 var remarks = reader.ReadToFollowing ("remarks") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("remarks");
562 return new XElement ("class",
563 new XAttribute ("name", name ?? string.Empty),
564 new XAttribute ("fullname", fullName ?? string.Empty),
565 new XAttribute ("assembly", assemblyName ?? string.Empty),