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
23 // Common functionality between ecma-provider and ecmauncompiled-provider
24 internal class EcmaDoc
26 static EcmaUrlParser parser = new EcmaUrlParser ();
28 public static void PopulateTreeFromIndexFile (string indexFilePath,
32 Dictionary<string, XElement> nsSummaries,
33 Func<XElement, string> indexGenerator = null,
34 IEcmaProviderFileSource fileSource = null)
36 fileSource = fileSource ?? DefaultEcmaProviderFileSource.Default;
37 var root = tree.RootNode;
39 var asm = Path.GetDirectoryName (indexFilePath);
41 storage = storage ?? new Storage.NullStorage ();
42 // nsSummaries is allowed to be null if the user doesn't care about it
43 nsSummaries = nsSummaries ?? new Dictionary<string, XElement> ();
44 // default index generator uses a counter
45 indexGenerator = indexGenerator ?? (_ => resID++.ToString ());
47 using (var reader = fileSource.GetIndexReader (indexFilePath)) {
48 reader.ReadToFollowing ("Types");
49 var types = XElement.Load (reader.ReadSubtree ());
51 foreach (var ns in types.Elements ("Namespace")) {
52 var nsName = (string)ns.Attribute ("Name");
53 nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
54 var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
57 if (!nsSummaries.TryGetValue (nsName, out nsElements))
58 nsSummaries[nsName] = nsElements = new XElement ("elements",
59 new XElement ("summary"),
60 new XElement ("remarks"));
61 //Add namespace summary and remarks data from file, if available
62 var nsFileName = fileSource.GetNamespaceXmlPath(asm, nsName);
64 if(File.Exists(nsFileName)){
65 var nsEl = fileSource.GetNamespaceElement (nsFileName);
67 nsElements.Element ("summary").ReplaceWith (nsEl.Descendants ("summary").First ());
68 nsElements.Element ("remarks").ReplaceWith (nsEl.Descendants ("remarks").First ());
70 Console.WriteLine ("Error reading namespace XML for {0} at {1}", nsName, nsFileName);
73 foreach (var type in ns.Elements ("Type")) {
74 // Add the XML file corresponding to the type to our storage
75 var id = indexGenerator (type);
77 var typeDocument = EcmaDoc.LoadTypeDocument (asm, nsName, type.Attribute ("Name").Value, out typeFilePath, fileSource);
78 if (typeDocument == null)
81 // write the document (which may have been modified by the fileSource) to the storage
82 MemoryStream io = new MemoryStream ();
83 using (var writer = XmlWriter.Create (io)) {
84 typeDocument.WriteTo (writer);
86 io.Seek (0, SeekOrigin.Begin);
87 storage.Store (id, io);
89 nsElements.Add (ExtractClassSummary (typeDocument));
91 var typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type);
92 var url = idPrefix + id + '#' + typeCaption + '/';
93 typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type, true);
94 var typeNode = nsNode.CreateNode (typeCaption, url);
96 // Add meta "Members" node
97 typeNode.CreateNode ("Members", "*");
98 var membersNode = typeDocument.Root.Element ("Members");
99 if (membersNode == null || !membersNode.Elements ().Any ())
101 var members = membersNode
103 .ToLookup (EcmaDoc.GetMemberType);
105 foreach (var memberType in members) {
106 // We pluralize the member type to get the caption and take the first letter as URL
107 var node = typeNode.CreateNode (EcmaDoc.PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
110 var isCtors = memberType.Key[0] == 'C';
112 // We do not escape much member name here
113 foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
114 if (memberGroup.Count () > 1) {
116 var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
117 var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
118 foreach (var member in memberGroup)
119 overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
120 overloadNode.Sort ();
122 // We treat constructor differently by showing their argument list in all cases
123 node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
138 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, IEcmaProviderFileSource fileSource = null)
141 return LoadTypeDocument (basePath, nsName, typeName, out dummy, fileSource ?? DefaultEcmaProviderFileSource.Default);
144 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, out string finalPath, IEcmaProviderFileSource fileSource = null)
146 fileSource = fileSource ?? DefaultEcmaProviderFileSource.Default;
148 finalPath = fileSource.GetTypeXmlPath (basePath, nsName, typeName);
149 if (!File.Exists (finalPath)) {
150 Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", finalPath);
154 XDocument doc = null;
156 doc = fileSource.GetTypeDocument(finalPath);
157 } catch (Exception e) {
158 Console.WriteLine ("Document `{0}' is unparsable, {1}", finalPath, e.ToString ());
164 public static string GetTypeCaptionFromIndex (XElement typeNodeFromIndex, bool full = false)
166 var t = typeNodeFromIndex;
167 var c = ((string)(t.Attribute ("DisplayName") ?? t.Attribute ("Name"))).Replace ('+', '.');
169 c += " " + (string)t.Attribute ("Kind");
173 public static string PluralizeMemberType (string memberType)
175 switch (memberType) {
179 return memberType + "s";
183 public static string GetMemberType (XElement m)
185 return m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value;
188 public static string MakeMemberCaption (XElement member, bool withArguments)
190 var caption = (string)member.Attribute ("MemberName");
191 // Use type name instead of .ctor for cosmetic sake
192 if (caption == ".ctor") {
193 caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
194 // If this is an inner type ctor, strip the parent type reference
195 var plusIndex = caption.LastIndexOf ('+');
197 caption = caption.Substring (plusIndex + 1);
199 if (caption.StartsWith ("op_")) {
201 caption = MakeOperatorSignature (member, out sig);
202 caption = withArguments ? sig : caption;
206 var args = member.Element ("Parameters");
208 if (args != null && args.Elements ("Parameter").Any ()) {
209 caption += args.Elements ("Parameter")
210 .Select (p => (string)p.Attribute ("Type"))
211 .Aggregate ((p1, p2) => p1 + "," + p2);
219 public static Node MatchNodeWithEcmaUrl (string url, Tree tree)
223 if (!parser.TryParse (url, out desc))
227 Node currentNode = tree.RootNode;
228 Node searchNode = new Node () { Caption = desc.Namespace };
229 int index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
231 result = currentNode.ChildNodes[index];
232 if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
236 currentNode = result;
238 searchNode.Caption = desc.ToCompleteTypeName ();
239 if (!desc.GenericTypeArgumentsIsNumeric)
240 index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
242 index = GenericTypeBacktickSearch (currentNode.ChildNodes, desc);
244 result = currentNode.ChildNodes[index];
245 if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
249 currentNode = result;
251 var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
252 currentNode = FindNodeForCaption (currentNode.ChildNodes, caption);
253 if (currentNode == null
254 || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
259 var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
260 searchNode.Caption = desc.ToCompleteMemberName (format);
261 index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
264 result = currentNode.ChildNodes[index];
265 if (result.ChildNodes.Count == 0 || desc.IsEtc)
269 currentNode = result;
270 searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
271 index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
274 result = result.ChildNodes[index];
279 static int GenericTypeBacktickSearch (IList<Node> childNodes, EcmaDesc desc)
281 /* Our strategy is to search for the non-generic variant of the type
282 * (which in most case should fail) and then use the closest index
283 * to linearily search for the generic variant with the right generic arg number
285 var searchNode = new Node () { Caption = desc.TypeName };
286 int index = childNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
287 // Place the index in the right start position
291 for (int i = index; i < childNodes.Count; i++) {
292 var currentNode = childNodes[i];
293 // Find the index of the generic argument list
294 int genericIndex = currentNode.Caption.IndexOf ('<');
295 // If we are not on the same base type name anymore, there is no point
296 int captionSlice = genericIndex != -1 ? genericIndex : currentNode.Caption.LastIndexOf (' ');
297 if (string.Compare (searchNode.Caption, 0,
298 currentNode.Caption, 0,
299 Math.Max (captionSlice, searchNode.Caption.Length),
300 StringComparison.Ordinal) != 0)
303 var numGenerics = CountTypeGenericArguments (currentNode.Caption, genericIndex);
304 if (numGenerics == desc.GenericTypeArguments.Count) {
305 // Simple comparison if we are not looking for an inner type
306 if (desc.NestedType == null)
308 // If more complicated, we fallback to using EcmaUrlParser
309 var caption = currentNode.Caption;
310 caption = "T:" + caption.Substring (0, caption.LastIndexOf (' ')).Replace ('.', '+');
312 var parser = new EcmaUrlParser ();
313 if (parser.TryParse (caption, out otherDesc) && desc.NestedType.Equals (otherDesc.NestedType))
321 // This comparer returns the answer straight from caption comparison
322 class EcmaGenericNodeComparer : IComparer<Node>
324 public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
326 public int Compare (Node n1, Node n2)
328 return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
332 // This comparer take into account the space in the caption
333 class EcmaTypeNodeComparer : IComparer<Node>
335 public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
337 public int Compare (Node n1, Node n2)
339 int length1 = CaptionLength (n1.Caption);
340 int length2 = CaptionLength (n2.Caption);
342 return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
345 int CaptionLength (string caption)
347 var length = caption.LastIndexOf (' ');
348 return length == -1 ? caption.Length : length;
352 public static Dictionary<string, string> GetContextForEcmaNode (string hash, string sourceID, Node node)
354 var args = new Dictionary<string, string> ();
356 args["source-id"] = sourceID;
359 var nodeType = GetNodeType (node);
361 case EcmaNodeType.Namespace:
362 args["show"] = "namespace";
363 args["namespace"] = node.Element.Substring ("N:".Length);
365 case EcmaNodeType.Type:
366 args["show"] = "typeoverview";
368 case EcmaNodeType.Member:
369 case EcmaNodeType.Meta:
370 switch (GetNodeMemberTypeChar (node)){
372 args["membertype"] = "Constructor";
375 args["membertype"] = "Method";
378 args["membertype"] = "Property";
381 args["membertype"] = "Field";
384 args["membertype"] = "Event";
387 args["membertype"] = "Operator";
390 args["membertype"] = "ExtensionMethod";
393 args["membertype"] = "All";
397 if (nodeType == EcmaNodeType.Meta) {
398 args["show"] = "members";
399 args["index"] = "all";
401 args["show"] = "member";
402 args["index"] = node.Element;
408 if (!string.IsNullOrEmpty (hash))
414 public static EcmaNodeType GetNodeType (Node node)
416 // We guess the node type by checking the depth level it's at in the tree
417 int level = GetNodeLevel (node);
420 return EcmaNodeType.Namespace;
422 return EcmaNodeType.Type;
424 return EcmaNodeType.Meta;
425 case 3: // Here it's either a member or, in case of overload, a meta
426 return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
427 case 4: // At this level, everything is necessarily a member
428 return EcmaNodeType.Member;
430 return EcmaNodeType.Invalid;
434 public static char GetNodeMemberTypeChar (Node node)
436 int level = GetNodeLevel (node);
437 // We try to reach the member group node depending on node nested level
440 return node.Element[0];
442 return node.Parent.Element[0];
444 return node.Parent.Parent.Element[0];
446 throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
450 public static int GetNodeLevel (Node node)
453 for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++) {
461 public static string EtcKindToCaption (char etc)
469 return "Constructors";
483 public static string MemberKindToCaption (EcmaDesc.Kind kind)
486 case EcmaDesc.Kind.Method:
488 case EcmaDesc.Kind.Property:
490 case EcmaDesc.Kind.Constructor:
491 return "Constructors";
492 case EcmaDesc.Kind.Field:
494 case EcmaDesc.Kind.Event:
496 case EcmaDesc.Kind.Operator:
503 public static Node FindNodeForCaption (IList<Node> nodes, string caption)
505 foreach (var node in nodes)
506 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
511 public static int CountTypeGenericArguments (string typeDefinition, int startIndex = 0)
515 bool started = false;
517 foreach (char c in typeDefinition.Skip (startIndex)) {
526 if (started && nestedLevel == 1)
538 internal static string MakeOperatorSignature (XElement member, out string memberSignature)
540 string name = (string)member.Attribute ("MemberName");
541 var nicename = name.Substring(3);
542 memberSignature = null;
545 // unary operators: no overloading possible [ECMA-335 §10.3.1]
546 case "op_UnaryPlus": // static R operator+ (T)
547 case "op_UnaryNegation": // static R operator- (T)
548 case "op_LogicalNot": // static R operator! (T)
549 case "op_OnesComplement": // static R operator~ (T)
550 case "op_Increment": // static R operator++ (T)
551 case "op_Decrement": // static R operator-- (T)
552 case "op_True": // static bool operator true (T)
553 case "op_False": // static bool operator false (T)
554 case "op_AddressOf": // static R operator& (T)
555 case "op_PointerDereference": // static R operator* (T)
556 memberSignature = nicename;
558 // conversion operators: overloading based on parameter and return type [ECMA-335 §10.3.3]
559 case "op_Implicit": // static implicit operator R (T)
560 case "op_Explicit": // static explicit operator R (T)
561 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
562 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
563 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
564 memberSignature = arg + " to " + ret;
566 // binary operators: overloading is possible [ECMA-335 §10.3.2]
568 if (member.Element ("Parameters") != null)
571 + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
579 static XElement ExtractClassSummary (XDocument typeDoc)
581 string name = typeDoc.Root.Attribute("Name").Value;
582 string fullName = typeDoc.Root.Attribute("FullName").Value;
583 string assemblyName = typeDoc.Root.Element("AssemblyInfo") != null ? typeDoc.Root.Element("AssemblyInfo").Element("AssemblyName").Value : string.Empty;
584 var docs = typeDoc.Root.Element("Docs");
585 var summary = docs.Element("summary") ?? new XElement("summary");
586 var remarks = docs.Element("remarks") ?? new XElement("remarks");
587 return new XElement ("class",
588 new XAttribute ("name", name ?? string.Empty),
589 new XAttribute ("fullname", fullName ?? string.Empty),
590 new XAttribute ("assembly", assemblyName ?? string.Empty),