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.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
207 result = currentNode.Nodes[index];
208 if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
212 currentNode = result;
214 searchNode.Caption = desc.ToCompleteTypeName ();
215 index = currentNode.Nodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
217 result = currentNode.Nodes[index];
218 if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
222 currentNode = result;
224 var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
225 currentNode = FindNodeForCaption (currentNode.Nodes, caption);
226 if (currentNode == null
227 || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
232 var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
233 searchNode.Caption = desc.ToCompleteMemberName (format);
234 index = currentNode.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
237 result = currentNode.Nodes[index];
238 if (result.Nodes.Count == 0 || desc.IsEtc)
242 currentNode = result;
243 searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
244 index = currentNode.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
247 result = result.Nodes[index];
252 // This comparer returns the answer straight from caption comparison
253 class EcmaGenericNodeComparer : IComparer<Node>
255 public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
257 public int Compare (Node n1, Node n2)
259 return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
263 // This comparer take into account the space in the caption
264 class EcmaTypeNodeComparer : IComparer<Node>
266 public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
268 public int Compare (Node n1, Node n2)
270 int length1 = CaptionLength (n1.Caption);
271 int length2 = CaptionLength (n2.Caption);
273 return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
276 int CaptionLength (string caption)
278 var length = caption.LastIndexOf (' ');
279 return length == -1 ? caption.Length : length;
283 public static Dictionary<string, string> GetContextForEcmaNode (string hash, string sourceID, Node node)
285 var args = new Dictionary<string, string> ();
287 args["source-id"] = sourceID;
290 var nodeType = GetNodeType (node);
292 case EcmaNodeType.Namespace:
293 args["show"] = "namespace";
294 args["namespace"] = node.Element.Substring ("N:".Length);
296 case EcmaNodeType.Type:
297 args["show"] = "typeoverview";
299 case EcmaNodeType.Member:
300 case EcmaNodeType.Meta:
301 switch (GetNodeMemberTypeChar (node)){
303 args["membertype"] = "Constructor";
306 args["membertype"] = "Method";
309 args["membertype"] = "Property";
312 args["membertype"] = "Field";
315 args["membertype"] = "Event";
318 args["membertype"] = "Operator";
321 args["membertype"] = "ExtensionMethod";
324 args["membertype"] = "All";
328 if (nodeType == EcmaNodeType.Meta) {
329 args["show"] = "members";
330 args["index"] = "all";
332 args["show"] = "member";
333 args["index"] = node.Element;
339 if (!string.IsNullOrEmpty (hash))
345 public static EcmaNodeType GetNodeType (Node node)
347 // We guess the node type by checking the depth level it's at in the tree
348 int level = GetNodeLevel (node);
351 return EcmaNodeType.Namespace;
353 return EcmaNodeType.Type;
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;
361 return EcmaNodeType.Invalid;
365 public static char GetNodeMemberTypeChar (Node node)
367 int level = GetNodeLevel (node);
368 // We try to reach the member group node depending on node nested level
371 return node.Element[0];
373 return node.Parent.Element[0];
375 return node.Parent.Parent.Element[0];
377 throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
381 public static int GetNodeLevel (Node node)
384 for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++)
389 public static string EtcKindToCaption (char etc)
397 return "Constructors";
411 public static string MemberKindToCaption (EcmaDesc.Kind kind)
414 case EcmaDesc.Kind.Method:
416 case EcmaDesc.Kind.Property:
418 case EcmaDesc.Kind.Constructor:
419 return "Constructors";
420 case EcmaDesc.Kind.Field:
422 case EcmaDesc.Kind.Event:
424 case EcmaDesc.Kind.Operator:
431 public static Node FindNodeForCaption (List<Node> nodes, string caption)
433 foreach (var node in nodes)
434 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
439 internal static string MakeOperatorSignature (XElement member, out string memberSignature)
441 string name = (string)member.Attribute ("MemberName");
442 var nicename = name.Substring(3);
443 memberSignature = null;
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;
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;
467 // binary operators: overloading is possible [ECMA-335 §10.3.2]
471 + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
479 static XElement ExtractClassSummary (string typeFilePath)
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 ();
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)));