/mcs/class/monodoc/Monodoc/providers/EcmaDoc.cs
C# | 595 lines | 492 code | 68 blank | 35 comment | 82 complexity | 376fd60c1f10458228e331e5966b2273 MD5 | raw file
Possible License(s): GPL-2.0, CC-BY-SA-3.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Unlicense, Apache-2.0
- using System;
- using System.Linq;
- using System.IO;
- using System.Text;
- using System.Xml;
- using System.Xml.Linq;
- using System.Collections.Generic;
- using Monodoc.Ecma;
- namespace Monodoc.Providers
- {
- public enum EcmaNodeType {
- Invalid,
- Namespace,
- Type,
- Member,
- Meta, // A node that's here to serve as a header for other node
- }
- // Common functionality between ecma-provider and ecmauncompiled-provider
- internal class EcmaDoc
- {
- static EcmaUrlParser parser = new EcmaUrlParser ();
- public static void PopulateTreeFromIndexFile (string indexFilePath,
- string idPrefix,
- Tree tree,
- IDocStorage storage,
- Dictionary<string, XElement> nsSummaries,
- Func<XElement, string> indexGenerator = null,
- IEcmaProviderFileSource fileSource = null)
- {
- fileSource = fileSource ?? DefaultEcmaProviderFileSource.Default;
- var root = tree.RootNode;
- int resID = 0;
- var asm = Path.GetDirectoryName (indexFilePath);
- storage = storage ?? new Storage.NullStorage ();
- // nsSummaries is allowed to be null if the user doesn't care about it
- nsSummaries = nsSummaries ?? new Dictionary<string, XElement> ();
- // default index generator uses a counter
- indexGenerator = indexGenerator ?? (_ => resID++.ToString ());
- using (var reader = fileSource.GetIndexReader (indexFilePath)) {
- reader.ReadToFollowing ("Types");
- var types = XElement.Load (reader.ReadSubtree ());
- foreach (var ns in types.Elements ("Namespace")) {
- var nsName = (string)ns.Attribute ("Name");
- nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
- var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
- XElement nsElements;
- if (!nsSummaries.TryGetValue (nsName, out nsElements))
- nsSummaries[nsName] = nsElements = new XElement ("elements",
- new XElement ("summary"),
- new XElement ("remarks"));
- //Add namespace summary and remarks data from file, if available
- var nsFileName = fileSource.GetNamespaceXmlPath(asm, nsName);
-
- if(File.Exists(nsFileName)){
- var nsEl = fileSource.GetNamespaceElement (nsFileName);
- nsElements.Element ("summary").ReplaceWith (nsEl.Descendants ("summary").First ());
- nsElements.Element ("remarks").ReplaceWith (nsEl.Descendants ("remarks").First ());
- }else{
- Console.WriteLine ("Error reading namespace XML for {0} at {1}", nsName, nsFileName);
- }
-
- foreach (var type in ns.Elements ("Type")) {
- // Add the XML file corresponding to the type to our storage
- var id = indexGenerator (type);
- string typeFilePath;
- var typeDocument = EcmaDoc.LoadTypeDocument (asm, nsName, type.Attribute ("Name").Value, out typeFilePath, fileSource);
- if (typeDocument == null)
- continue;
- // write the document (which may have been modified by the fileSource) to the storage
- MemoryStream io = new MemoryStream ();
- using (var writer = XmlWriter.Create (io)) {
- typeDocument.WriteTo (writer);
- }
- io.Seek (0, SeekOrigin.Begin);
- storage.Store (id, io);
- nsElements.Add (ExtractClassSummary (typeDocument));
- var typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type);
- var url = idPrefix + id + '#' + typeCaption + '/';
- typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type, true);
- var typeNode = nsNode.CreateNode (typeCaption, url);
- // Add meta "Members" node
- typeNode.CreateNode ("Members", "*");
- var membersNode = typeDocument.Root.Element ("Members");
- if (membersNode == null || !membersNode.Elements ().Any ())
- continue;
- var members = membersNode
- .Elements ("Member")
- .ToLookup (EcmaDoc.GetMemberType);
- foreach (var memberType in members) {
- // We pluralize the member type to get the caption and take the first letter as URL
- var node = typeNode.CreateNode (EcmaDoc.PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
- var memberIndex = 0;
- var isCtors = memberType.Key[0] == 'C';
- // We do not escape much member name here
- foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
- if (memberGroup.Count () > 1) {
- // Generate overload
- var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
- var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
- foreach (var member in memberGroup)
- overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
- overloadNode.Sort ();
- } else {
- // We treat constructor differently by showing their argument list in all cases
- node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
- }
- }
- node.Sort ();
- }
- }
- nsNode.Sort ();
- }
- root.Sort ();
- }
- }
- // Utility methods
- public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, IEcmaProviderFileSource fileSource = null)
- {
- string dummy;
- return LoadTypeDocument (basePath, nsName, typeName, out dummy, fileSource ?? DefaultEcmaProviderFileSource.Default);
- }
- public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, out string finalPath, IEcmaProviderFileSource fileSource = null)
- {
- fileSource = fileSource ?? DefaultEcmaProviderFileSource.Default;
- finalPath = fileSource.GetTypeXmlPath (basePath, nsName, typeName);
- if (!File.Exists (finalPath)) {
- Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", finalPath);
- return null;
- }
- XDocument doc = null;
- try {
- doc = fileSource.GetTypeDocument(finalPath);
- } catch (Exception e) {
- Console.WriteLine ("Document `{0}' is unparsable, {1}", finalPath, e.ToString ());
- }
- return doc;
- }
- public static string GetTypeCaptionFromIndex (XElement typeNodeFromIndex, bool full = false)
- {
- var t = typeNodeFromIndex;
- var c = ((string)(t.Attribute ("DisplayName") ?? t.Attribute ("Name"))).Replace ('+', '.');
- if (full)
- c += " " + (string)t.Attribute ("Kind");
- return c;
- }
- public static string PluralizeMemberType (string memberType)
- {
- switch (memberType) {
- case "Property":
- return "Properties";
- default:
- return memberType + "s";
- }
- }
- public static string GetMemberType (XElement m)
- {
- return m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value;
- }
- public static string MakeMemberCaption (XElement member, bool withArguments)
- {
- var caption = (string)member.Attribute ("MemberName");
- // Use type name instead of .ctor for cosmetic sake
- if (caption == ".ctor") {
- caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
- // If this is an inner type ctor, strip the parent type reference
- var plusIndex = caption.LastIndexOf ('+');
- if (plusIndex != -1)
- caption = caption.Substring (plusIndex + 1);
- }
- if (caption.StartsWith ("op_")) {
- string sig;
- caption = MakeOperatorSignature (member, out sig);
- caption = withArguments ? sig : caption;
- return caption;
- }
- if (withArguments) {
- var args = member.Element ("Parameters");
- caption += '(';
- if (args != null && args.Elements ("Parameter").Any ()) {
- caption += args.Elements ("Parameter")
- .Select (p => (string)p.Attribute ("Type"))
- .Aggregate ((p1, p2) => p1 + "," + p2);
- }
- caption += ')';
- }
-
- return caption;
- }
- public static Node MatchNodeWithEcmaUrl (string url, Tree tree)
- {
- Node result = null;
- EcmaDesc desc;
- if (!parser.TryParse (url, out desc))
- return null;
- // Namespace search
- Node currentNode = tree.RootNode;
- Node searchNode = new Node () { Caption = desc.Namespace };
- int index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
- if (index >= 0)
- result = currentNode.ChildNodes[index];
- if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
- return result;
- // Type search
- currentNode = result;
- result = null;
- searchNode.Caption = desc.ToCompleteTypeName ();
- if (!desc.GenericTypeArgumentsIsNumeric)
- index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
- else
- index = GenericTypeBacktickSearch (currentNode.ChildNodes, desc);
- if (index >= 0)
- result = currentNode.ChildNodes[index];
- if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
- return result;
- // Member selection
- currentNode = result;
- result = null;
- var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
- currentNode = FindNodeForCaption (currentNode.ChildNodes, caption);
- if (currentNode == null
- || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
- return currentNode;
- // Member search
- result = null;
- var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
- searchNode.Caption = desc.ToCompleteMemberName (format);
- index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
- if (index < 0)
- return null;
- result = currentNode.ChildNodes[index];
- if (result.ChildNodes.Count == 0 || desc.IsEtc)
- return result;
- // Overloads search
- currentNode = result;
- searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
- index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
- if (index < 0)
- return result;
- result = result.ChildNodes[index];
- return result;
- }
- static int GenericTypeBacktickSearch (IList<Node> childNodes, EcmaDesc desc)
- {
- /* Our strategy is to search for the non-generic variant of the type
- * (which in most case should fail) and then use the closest index
- * to linearily search for the generic variant with the right generic arg number
- */
- var searchNode = new Node () { Caption = desc.TypeName };
- int index = childNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
- // Place the index in the right start position
- if (index < 0)
- index = ~index;
- for (int i = index; i < childNodes.Count; i++) {
- var currentNode = childNodes[i];
- // Find the index of the generic argument list
- int genericIndex = currentNode.Caption.IndexOf ('<');
- // If we are not on the same base type name anymore, there is no point
- int captionSlice = genericIndex != -1 ? genericIndex : currentNode.Caption.LastIndexOf (' ');
- if (string.Compare (searchNode.Caption, 0,
- currentNode.Caption, 0,
- Math.Max (captionSlice, searchNode.Caption.Length),
- StringComparison.Ordinal) != 0)
- break;
- var numGenerics = CountTypeGenericArguments (currentNode.Caption, genericIndex);
- if (numGenerics == desc.GenericTypeArguments.Count) {
- // Simple comparison if we are not looking for an inner type
- if (desc.NestedType == null)
- return i;
- // If more complicated, we fallback to using EcmaUrlParser
- var caption = currentNode.Caption;
- caption = "T:" + caption.Substring (0, caption.LastIndexOf (' ')).Replace ('.', '+');
- EcmaDesc otherDesc;
- var parser = new EcmaUrlParser ();
- if (parser.TryParse (caption, out otherDesc) && desc.NestedType.Equals (otherDesc.NestedType))
- return i;
- }
- }
- return -1;
- }
- // This comparer returns the answer straight from caption comparison
- class EcmaGenericNodeComparer : IComparer<Node>
- {
- public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
- public int Compare (Node n1, Node n2)
- {
- return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
- }
- }
- // This comparer take into account the space in the caption
- class EcmaTypeNodeComparer : IComparer<Node>
- {
- public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
- public int Compare (Node n1, Node n2)
- {
- int length1 = CaptionLength (n1.Caption);
- int length2 = CaptionLength (n2.Caption);
- return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
- }
- int CaptionLength (string caption)
- {
- var length = caption.LastIndexOf (' ');
- return length == -1 ? caption.Length : length;
- }
- }
- public static Dictionary<string, string> GetContextForEcmaNode (string hash, string sourceID, Node node)
- {
- var args = new Dictionary<string, string> ();
- args["source-id"] = sourceID;
- if (node != null) {
- var nodeType = GetNodeType (node);
- switch (nodeType) {
- case EcmaNodeType.Namespace:
- args["show"] = "namespace";
- args["namespace"] = node.Element.Substring ("N:".Length);
- break;
- case EcmaNodeType.Type:
- args["show"] = "typeoverview";
- break;
- case EcmaNodeType.Member:
- case EcmaNodeType.Meta:
- switch (GetNodeMemberTypeChar (node)){
- case 'C':
- args["membertype"] = "Constructor";
- break;
- case 'M':
- args["membertype"] = "Method";
- break;
- case 'P':
- args["membertype"] = "Property";
- break;
- case 'F':
- args["membertype"] = "Field";
- break;
- case 'E':
- args["membertype"] = "Event";
- break;
- case 'O':
- args["membertype"] = "Operator";
- break;
- case 'X':
- args["membertype"] = "ExtensionMethod";
- break;
- case '*':
- args["membertype"] = "All";
- break;
- }
- if (nodeType == EcmaNodeType.Meta) {
- args["show"] = "members";
- args["index"] = "all";
- } else {
- args["show"] = "member";
- args["index"] = node.Element;
- }
- break;
- }
- }
- if (!string.IsNullOrEmpty (hash))
- args["hash"] = hash;
- return args;
- }
- public static EcmaNodeType GetNodeType (Node node)
- {
- // We guess the node type by checking the depth level it's at in the tree
- int level = GetNodeLevel (node);
- switch (level) {
- case 0:
- return EcmaNodeType.Namespace;
- case 1:
- return EcmaNodeType.Type;
- case 2:
- return EcmaNodeType.Meta;
- case 3: // Here it's either a member or, in case of overload, a meta
- return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
- case 4: // At this level, everything is necessarily a member
- return EcmaNodeType.Member;
- default:
- return EcmaNodeType.Invalid;
- }
- }
- public static char GetNodeMemberTypeChar (Node node)
- {
- int level = GetNodeLevel (node);
- // We try to reach the member group node depending on node nested level
- switch (level) {
- case 2:
- return node.Element[0];
- case 3:
- return node.Parent.Element[0];
- case 4:
- return node.Parent.Parent.Element[0];
- default:
- throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
- }
- }
- public static int GetNodeLevel (Node node)
- {
- int i = 0;
- for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++) {
- node = node.Parent;
- if (node == null)
- return i - 1;
- }
- return i - 1;
- }
- public static string EtcKindToCaption (char etc)
- {
- switch (etc) {
- case 'M':
- return "Methods";
- case 'P':
- return "Properties";
- case 'C':
- return "Constructors";
- case 'F':
- return "Fields";
- case 'E':
- return "Events";
- case 'O':
- return "Operators";
- case '*':
- return "Members";
- default:
- return null;
- }
- }
- public static string MemberKindToCaption (EcmaDesc.Kind kind)
- {
- switch (kind) {
- case EcmaDesc.Kind.Method:
- return "Methods";
- case EcmaDesc.Kind.Property:
- return "Properties";
- case EcmaDesc.Kind.Constructor:
- return "Constructors";
- case EcmaDesc.Kind.Field:
- return "Fields";
- case EcmaDesc.Kind.Event:
- return "Events";
- case EcmaDesc.Kind.Operator:
- return "Operators";
- default:
- return null;
- }
- }
- public static Node FindNodeForCaption (IList<Node> nodes, string caption)
- {
- foreach (var node in nodes)
- if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
- return node;
- return null;
- }
- public static int CountTypeGenericArguments (string typeDefinition, int startIndex = 0)
- {
- int nestedLevel = 0;
- int count = 0;
- bool started = false;
- foreach (char c in typeDefinition.Skip (startIndex)) {
- switch (c) {
- case '<':
- if (!started)
- count = 1;
- started = true;
- nestedLevel++;
- break;
- case ',':
- if (started && nestedLevel == 1)
- count++;
- break;
- case '>':
- nestedLevel--;
- break;
- }
- }
- return count;
- }
- internal static string MakeOperatorSignature (XElement member, out string memberSignature)
- {
- string name = (string)member.Attribute ("MemberName");
- var nicename = name.Substring(3);
- memberSignature = null;
- switch (name) {
- // unary operators: no overloading possible [ECMA-335 ยง10.3.1]
- case "op_UnaryPlus": // static R operator+ (T)
- case "op_UnaryNegation": // static R operator- (T)
- case "op_LogicalNot": // static R operator! (T)
- case "op_OnesComplement": // static R operator~ (T)
- case "op_Increment": // static R operator++ (T)
- case "op_Decrement": // static R operator-- (T)
- case "op_True": // static bool operator true (T)
- case "op_False": // static bool operator false (T)
- case "op_AddressOf": // static R operator& (T)
- case "op_PointerDereference": // static R operator* (T)
- memberSignature = nicename;
- break;
- // conversion operators: overloading based on parameter and return type [ECMA-335 ยง10.3.3]
- case "op_Implicit": // static implicit operator R (T)
- case "op_Explicit": // static explicit operator R (T)
- nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
- string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
- string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
- memberSignature = arg + " to " + ret;
- break;
- // binary operators: overloading is possible [ECMA-335 ยง10.3.2]
- default:
- if (member.Element ("Parameters") != null)
- memberSignature =
- nicename + "("
- + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
- + ")";
- break;
- }
- return nicename;
- }
- static XElement ExtractClassSummary (XDocument typeDoc)
- {
- string name = typeDoc.Root.Attribute("Name").Value;
- string fullName = typeDoc.Root.Attribute("FullName").Value;
- string assemblyName = typeDoc.Root.Element("AssemblyInfo") != null ? typeDoc.Root.Element("AssemblyInfo").Element("AssemblyName").Value : string.Empty;
- var docs = typeDoc.Root.Element("Docs");
- var summary = docs.Element("summary") ?? new XElement("summary");
- var remarks = docs.Element("remarks") ?? new XElement("remarks");
- return new XElement ("class",
- new XAttribute ("name", name ?? string.Empty),
- new XAttribute ("fullname", fullName ?? string.Empty),
- new XAttribute ("assembly", assemblyName ?? string.Empty),
- summary,
- remarks);
- }
- }
- }