[monodoc] Don't explode when treating invalid XML document
[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 namespace Monodoc.Providers
10 {
11         // Common functionality between ecma-provider and ecmauncompiled-provider
12         internal class EcmaDoc
13         {
14                 public static void PopulateTreeFromIndexFile (string indexFilePath,
15                                                               Tree tree,
16                                                               IDocStorage storage,
17                                                               Dictionary<string, XElement> nsSummaries,
18                                                               Func<XElement, string> indexGenerator = null)
19                 {
20                         var root = tree.RootNode;
21                         int resID = 0;
22                         var asm = Path.GetDirectoryName (indexFilePath);
23
24                         storage = storage ?? new Storage.NullStorage ();
25                         // nsSummaries is allowed to be null if the user doesn't care about it
26                         nsSummaries = nsSummaries ?? new Dictionary<string, XElement> ();
27                         // default index generator uses a counter
28                         indexGenerator = indexGenerator ?? (_ => resID++.ToString ());
29
30                         using (var reader = XmlReader.Create (File.OpenRead (indexFilePath))) {
31                                 reader.ReadToFollowing ("Types");
32                                 var types = XElement.Load (reader.ReadSubtree ());
33
34                                 foreach (var ns in types.Elements ("Namespace")) {
35                                         var nsName = (string)ns.Attribute ("Name");
36                                         nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
37                                         var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
38
39                                         XElement nsElements;
40                                         if (!nsSummaries.TryGetValue (nsName, out nsElements))
41                                                 nsSummaries[nsName] = nsElements = new XElement ("elements",
42                                                                                                  new XElement ("summary"),
43                                                                                                  new XElement ("remarks"));
44
45                                         foreach (var type in ns.Elements ("Type")) {
46                                                 // Add the XML file corresponding to the type to our storage
47                                                 var id = indexGenerator (type);
48                                                 string typeFilePath;
49                                                 var typeDocument = EcmaDoc.LoadTypeDocument (asm, nsName, type.Attribute ("Name").Value, out typeFilePath);
50                                                 if (typeDocument == null)
51                                                         continue;
52                                                 using (var file = File.OpenRead (typeFilePath))
53                                                         storage.Store (id, file);
54                                                 nsElements.Add (ExtractClassSummary (typeFilePath));
55
56                                                 var typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type);
57                                                 var url = "ecma:" + id + '#' + typeCaption + '/';
58                                                 typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type, true);
59                                                 var typeNode = nsNode.CreateNode (typeCaption, url);
60
61                                                 // Add meta "Members" node
62                                                 typeNode.CreateNode ("Members", "*");
63                                                 var membersNode = typeDocument.Root.Element ("Members");
64                                                 if (membersNode == null || !membersNode.Elements ().Any ())
65                                                         continue;
66                                                 var members = membersNode
67                                                         .Elements ("Member")
68                                                         .ToLookup (EcmaDoc.GetMemberType);
69
70                                                 foreach (var memberType in members) {
71                                                         // We pluralize the member type to get the caption and take the first letter as URL
72                                                         var node = typeNode.CreateNode (EcmaDoc.PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
73                                                         var memberIndex = 0;
74
75                                                         var isCtors = memberType.Key[0] == 'C';
76
77                                                         // We do not escape much member name here
78                                                         foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
79                                                                 if (memberGroup.Count () > 1) {
80                                                                         // Generate overload
81                                                                         var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
82                                                                         var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
83                                                                         foreach (var member in memberGroup)
84                                                                                 overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
85                                                                         overloadNode.Sort ();
86                                                                 } else {
87                                                                         // We treat constructor differently by showing their argument list in all cases
88                                                                         node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
89                                                                 }
90                                                         }
91                                                         node.Sort ();
92                                                 }
93                                         }
94
95                                         nsNode.Sort ();
96                                 }
97                                 root.Sort ();
98                         }
99                 }
100
101                 // Utility methods
102
103                 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName)
104                 {
105                         string dummy;
106                         return LoadTypeDocument (basePath, nsName, typeName, out dummy);
107                 }
108
109                 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, out string finalPath)
110                 {
111                         finalPath = Path.Combine (basePath, nsName, Path.ChangeExtension (typeName, ".xml"));
112                         if (!File.Exists (finalPath)) {
113                                 Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", finalPath);
114                                 return null;
115                         }
116
117                         XDocument doc = null;
118                         try {
119                                 doc = XDocument.Load (finalPath);
120                         } catch (Exception e) {
121                                 Console.WriteLine ("Document `{0}' is unparsable, {1}", finalPath, e.ToString ());
122                         }
123
124                         return doc;
125                 }
126
127                 public static string GetTypeCaptionFromIndex (XElement typeNodeFromIndex, bool full = false)
128                 {
129                         var t = typeNodeFromIndex;
130                         var c = ((string)(t.Attribute ("DisplayName") ?? t.Attribute ("Name"))).Replace ('+', '.');
131                         if (full)
132                                 c += " " + (string)t.Attribute ("Kind");
133                         return c;
134                 }
135
136                 public static string PluralizeMemberType (string memberType)
137                 {
138                         switch (memberType) {
139                         case "Property":
140                                 return "Properties";
141                         default:
142                                 return memberType + "s";
143                         }
144                 }
145
146                 public static string GetMemberType (XElement m)
147                 {
148                         return m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value;
149                 }
150
151                 public static string MakeMemberCaption (XElement member, bool withArguments)
152                 {
153                         var caption = (string)member.Attribute ("MemberName");
154                         // Use type name instead of .ctor for cosmetic sake
155                         if (caption == ".ctor") {
156                                 caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
157                                 // If this is an inner type ctor, strip the parent type reference
158                                 var plusIndex = caption.LastIndexOf ('+');
159                                 if (plusIndex != -1)
160                                         caption = caption.Substring (plusIndex + 1);
161                         }
162                         if (caption.StartsWith ("op_")) {
163                                 string sig;
164                                 caption = MakeOperatorSignature (member, out sig);
165                                 caption = withArguments ? sig : caption;
166                                 return caption;
167                         }
168                         if (withArguments) {
169                                 var args = member.Element ("Parameters");
170                                 caption += '(';
171                                 if (args != null && args.Elements ("Parameter").Any ()) {
172                                         caption += args.Elements ("Parameter")
173                                                 .Select (p => (string)p.Attribute ("Type"))
174                                                 .Aggregate ((p1, p2) => p1 + "," + p2);
175                                 }
176                                 caption += ')';
177                         }
178                         
179                         return caption;
180                 }
181
182                 internal static string MakeOperatorSignature (XElement member, out string memberSignature)
183                 {
184                         string name = (string)member.Attribute ("MemberName");
185                         var nicename = name.Substring(3);
186                         memberSignature = null;
187
188                         switch (name) {
189                         // unary operators: no overloading possible     [ECMA-335 §10.3.1]
190                         case "op_UnaryPlus":                    // static     R operator+       (T)
191                         case "op_UnaryNegation":                // static     R operator-       (T)
192                         case "op_LogicalNot":                   // static     R operator!       (T)
193                         case "op_OnesComplement":               // static     R operator~       (T)
194                         case "op_Increment":                    // static     R operator++      (T)
195                         case "op_Decrement":                    // static     R operator--      (T)
196                         case "op_True":                         // static  bool operator true   (T)
197                         case "op_False":                        // static  bool operator false  (T)
198                         case "op_AddressOf":                    // static     R operator&       (T)
199                         case "op_PointerDereference":           // static     R operator*       (T)
200                                 memberSignature = nicename;
201                                 break;
202                         // conversion operators: overloading based on parameter and return type [ECMA-335 §10.3.3]
203                         case "op_Implicit":                    // static implicit operator R (T)
204                         case "op_Explicit":                    // static explicit operator R (T)
205                                 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
206                                 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
207                                 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
208                                 memberSignature = arg + " to " + ret;
209                                 break;
210                         // binary operators: overloading is possible [ECMA-335 §10.3.2]
211                         default:
212                                 memberSignature =
213                                         nicename + "("
214                                         + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
215                                         + ")";
216                                 break;
217                         }
218
219                         return nicename;
220                 }
221
222                 static XElement ExtractClassSummary (string typeFilePath)
223                 {
224                         using (var reader = XmlReader.Create (typeFilePath)) {
225                                 reader.ReadToFollowing ("Type");
226                                 var name = reader.GetAttribute ("Name");
227                                 var fullName = reader.GetAttribute ("FullName");
228                                 reader.ReadToFollowing ("AssemblyName");
229                                 var assemblyName = reader.ReadElementString ();
230                                 reader.ReadToFollowing ("summary");
231                                 var summary = reader.ReadInnerXml ();
232                                 reader.ReadToFollowing ("remarks");
233                                 var remarks = reader.ReadInnerXml ();
234
235                                 return new XElement ("class",
236                                                      new XAttribute ("name", name ?? string.Empty),
237                                                      new XAttribute ("fullname", fullName ?? string.Empty),
238                                                      new XAttribute ("assembly", assemblyName ?? string.Empty),
239                                                      new XElement ("summary", new XCData (summary)),
240                                                      new XElement ("remarks", new XCData (remarks)));
241                         }
242                 }
243         }
244 }