PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/mcs/class/monodoc/Monodoc/providers/EcmaDoc.cs

http://github.com/mono/mono
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
  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. using Monodoc.Ecma;
  9. namespace Monodoc.Providers
  10. {
  11. public enum EcmaNodeType {
  12. Invalid,
  13. Namespace,
  14. Type,
  15. Member,
  16. Meta, // A node that's here to serve as a header for other node
  17. }
  18. // Common functionality between ecma-provider and ecmauncompiled-provider
  19. internal class EcmaDoc
  20. {
  21. static EcmaUrlParser parser = new EcmaUrlParser ();
  22. public static void PopulateTreeFromIndexFile (string indexFilePath,
  23. string idPrefix,
  24. Tree tree,
  25. IDocStorage storage,
  26. Dictionary<string, XElement> nsSummaries,
  27. Func<XElement, string> indexGenerator = null,
  28. IEcmaProviderFileSource fileSource = null)
  29. {
  30. fileSource = fileSource ?? DefaultEcmaProviderFileSource.Default;
  31. var root = tree.RootNode;
  32. int resID = 0;
  33. var asm = Path.GetDirectoryName (indexFilePath);
  34. storage = storage ?? new Storage.NullStorage ();
  35. // nsSummaries is allowed to be null if the user doesn't care about it
  36. nsSummaries = nsSummaries ?? new Dictionary<string, XElement> ();
  37. // default index generator uses a counter
  38. indexGenerator = indexGenerator ?? (_ => resID++.ToString ());
  39. using (var reader = fileSource.GetIndexReader (indexFilePath)) {
  40. reader.ReadToFollowing ("Types");
  41. var types = XElement.Load (reader.ReadSubtree ());
  42. foreach (var ns in types.Elements ("Namespace")) {
  43. var nsName = (string)ns.Attribute ("Name");
  44. nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
  45. var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
  46. XElement nsElements;
  47. if (!nsSummaries.TryGetValue (nsName, out nsElements))
  48. nsSummaries[nsName] = nsElements = new XElement ("elements",
  49. new XElement ("summary"),
  50. new XElement ("remarks"));
  51. //Add namespace summary and remarks data from file, if available
  52. var nsFileName = fileSource.GetNamespaceXmlPath(asm, nsName);
  53. if(File.Exists(nsFileName)){
  54. var nsEl = fileSource.GetNamespaceElement (nsFileName);
  55. nsElements.Element ("summary").ReplaceWith (nsEl.Descendants ("summary").First ());
  56. nsElements.Element ("remarks").ReplaceWith (nsEl.Descendants ("remarks").First ());
  57. }else{
  58. Console.WriteLine ("Error reading namespace XML for {0} at {1}", nsName, nsFileName);
  59. }
  60. foreach (var type in ns.Elements ("Type")) {
  61. // Add the XML file corresponding to the type to our storage
  62. var id = indexGenerator (type);
  63. string typeFilePath;
  64. var typeDocument = EcmaDoc.LoadTypeDocument (asm, nsName, type.Attribute ("Name").Value, out typeFilePath, fileSource);
  65. if (typeDocument == null)
  66. continue;
  67. // write the document (which may have been modified by the fileSource) to the storage
  68. MemoryStream io = new MemoryStream ();
  69. using (var writer = XmlWriter.Create (io)) {
  70. typeDocument.WriteTo (writer);
  71. }
  72. io.Seek (0, SeekOrigin.Begin);
  73. storage.Store (id, io);
  74. nsElements.Add (ExtractClassSummary (typeDocument));
  75. var typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type);
  76. var url = idPrefix + id + '#' + typeCaption + '/';
  77. typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type, true);
  78. var typeNode = nsNode.CreateNode (typeCaption, url);
  79. // Add meta "Members" node
  80. typeNode.CreateNode ("Members", "*");
  81. var membersNode = typeDocument.Root.Element ("Members");
  82. if (membersNode == null || !membersNode.Elements ().Any ())
  83. continue;
  84. var members = membersNode
  85. .Elements ("Member")
  86. .ToLookup (EcmaDoc.GetMemberType);
  87. foreach (var memberType in members) {
  88. // We pluralize the member type to get the caption and take the first letter as URL
  89. var node = typeNode.CreateNode (EcmaDoc.PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
  90. var memberIndex = 0;
  91. var isCtors = memberType.Key[0] == 'C';
  92. // We do not escape much member name here
  93. foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
  94. if (memberGroup.Count () > 1) {
  95. // Generate overload
  96. var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
  97. var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
  98. foreach (var member in memberGroup)
  99. overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
  100. overloadNode.Sort ();
  101. } else {
  102. // We treat constructor differently by showing their argument list in all cases
  103. node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
  104. }
  105. }
  106. node.Sort ();
  107. }
  108. }
  109. nsNode.Sort ();
  110. }
  111. root.Sort ();
  112. }
  113. }
  114. // Utility methods
  115. public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, IEcmaProviderFileSource fileSource = null)
  116. {
  117. string dummy;
  118. return LoadTypeDocument (basePath, nsName, typeName, out dummy, fileSource ?? DefaultEcmaProviderFileSource.Default);
  119. }
  120. public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, out string finalPath, IEcmaProviderFileSource fileSource = null)
  121. {
  122. fileSource = fileSource ?? DefaultEcmaProviderFileSource.Default;
  123. finalPath = fileSource.GetTypeXmlPath (basePath, nsName, typeName);
  124. if (!File.Exists (finalPath)) {
  125. Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", finalPath);
  126. return null;
  127. }
  128. XDocument doc = null;
  129. try {
  130. doc = fileSource.GetTypeDocument(finalPath);
  131. } catch (Exception e) {
  132. Console.WriteLine ("Document `{0}' is unparsable, {1}", finalPath, e.ToString ());
  133. }
  134. return doc;
  135. }
  136. public static string GetTypeCaptionFromIndex (XElement typeNodeFromIndex, bool full = false)
  137. {
  138. var t = typeNodeFromIndex;
  139. var c = ((string)(t.Attribute ("DisplayName") ?? t.Attribute ("Name"))).Replace ('+', '.');
  140. if (full)
  141. c += " " + (string)t.Attribute ("Kind");
  142. return c;
  143. }
  144. public static string PluralizeMemberType (string memberType)
  145. {
  146. switch (memberType) {
  147. case "Property":
  148. return "Properties";
  149. default:
  150. return memberType + "s";
  151. }
  152. }
  153. public static string GetMemberType (XElement m)
  154. {
  155. return m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value;
  156. }
  157. public static string MakeMemberCaption (XElement member, bool withArguments)
  158. {
  159. var caption = (string)member.Attribute ("MemberName");
  160. // Use type name instead of .ctor for cosmetic sake
  161. if (caption == ".ctor") {
  162. caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
  163. // If this is an inner type ctor, strip the parent type reference
  164. var plusIndex = caption.LastIndexOf ('+');
  165. if (plusIndex != -1)
  166. caption = caption.Substring (plusIndex + 1);
  167. }
  168. if (caption.StartsWith ("op_")) {
  169. string sig;
  170. caption = MakeOperatorSignature (member, out sig);
  171. caption = withArguments ? sig : caption;
  172. return caption;
  173. }
  174. if (withArguments) {
  175. var args = member.Element ("Parameters");
  176. caption += '(';
  177. if (args != null && args.Elements ("Parameter").Any ()) {
  178. caption += args.Elements ("Parameter")
  179. .Select (p => (string)p.Attribute ("Type"))
  180. .Aggregate ((p1, p2) => p1 + "," + p2);
  181. }
  182. caption += ')';
  183. }
  184. return caption;
  185. }
  186. public static Node MatchNodeWithEcmaUrl (string url, Tree tree)
  187. {
  188. Node result = null;
  189. EcmaDesc desc;
  190. if (!parser.TryParse (url, out desc))
  191. return null;
  192. // Namespace search
  193. Node currentNode = tree.RootNode;
  194. Node searchNode = new Node () { Caption = desc.Namespace };
  195. int index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
  196. if (index >= 0)
  197. result = currentNode.ChildNodes[index];
  198. if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
  199. return result;
  200. // Type search
  201. currentNode = result;
  202. result = null;
  203. searchNode.Caption = desc.ToCompleteTypeName ();
  204. if (!desc.GenericTypeArgumentsIsNumeric)
  205. index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
  206. else
  207. index = GenericTypeBacktickSearch (currentNode.ChildNodes, desc);
  208. if (index >= 0)
  209. result = currentNode.ChildNodes[index];
  210. if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
  211. return result;
  212. // Member selection
  213. currentNode = result;
  214. result = null;
  215. var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
  216. currentNode = FindNodeForCaption (currentNode.ChildNodes, caption);
  217. if (currentNode == null
  218. || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
  219. return currentNode;
  220. // Member search
  221. result = null;
  222. var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
  223. searchNode.Caption = desc.ToCompleteMemberName (format);
  224. index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
  225. if (index < 0)
  226. return null;
  227. result = currentNode.ChildNodes[index];
  228. if (result.ChildNodes.Count == 0 || desc.IsEtc)
  229. return result;
  230. // Overloads search
  231. currentNode = result;
  232. searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
  233. index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
  234. if (index < 0)
  235. return result;
  236. result = result.ChildNodes[index];
  237. return result;
  238. }
  239. static int GenericTypeBacktickSearch (IList<Node> childNodes, EcmaDesc desc)
  240. {
  241. /* Our strategy is to search for the non-generic variant of the type
  242. * (which in most case should fail) and then use the closest index
  243. * to linearily search for the generic variant with the right generic arg number
  244. */
  245. var searchNode = new Node () { Caption = desc.TypeName };
  246. int index = childNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
  247. // Place the index in the right start position
  248. if (index < 0)
  249. index = ~index;
  250. for (int i = index; i < childNodes.Count; i++) {
  251. var currentNode = childNodes[i];
  252. // Find the index of the generic argument list
  253. int genericIndex = currentNode.Caption.IndexOf ('<');
  254. // If we are not on the same base type name anymore, there is no point
  255. int captionSlice = genericIndex != -1 ? genericIndex : currentNode.Caption.LastIndexOf (' ');
  256. if (string.Compare (searchNode.Caption, 0,
  257. currentNode.Caption, 0,
  258. Math.Max (captionSlice, searchNode.Caption.Length),
  259. StringComparison.Ordinal) != 0)
  260. break;
  261. var numGenerics = CountTypeGenericArguments (currentNode.Caption, genericIndex);
  262. if (numGenerics == desc.GenericTypeArguments.Count) {
  263. // Simple comparison if we are not looking for an inner type
  264. if (desc.NestedType == null)
  265. return i;
  266. // If more complicated, we fallback to using EcmaUrlParser
  267. var caption = currentNode.Caption;
  268. caption = "T:" + caption.Substring (0, caption.LastIndexOf (' ')).Replace ('.', '+');
  269. EcmaDesc otherDesc;
  270. var parser = new EcmaUrlParser ();
  271. if (parser.TryParse (caption, out otherDesc) && desc.NestedType.Equals (otherDesc.NestedType))
  272. return i;
  273. }
  274. }
  275. return -1;
  276. }
  277. // This comparer returns the answer straight from caption comparison
  278. class EcmaGenericNodeComparer : IComparer<Node>
  279. {
  280. public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
  281. public int Compare (Node n1, Node n2)
  282. {
  283. return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
  284. }
  285. }
  286. // This comparer take into account the space in the caption
  287. class EcmaTypeNodeComparer : IComparer<Node>
  288. {
  289. public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
  290. public int Compare (Node n1, Node n2)
  291. {
  292. int length1 = CaptionLength (n1.Caption);
  293. int length2 = CaptionLength (n2.Caption);
  294. return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
  295. }
  296. int CaptionLength (string caption)
  297. {
  298. var length = caption.LastIndexOf (' ');
  299. return length == -1 ? caption.Length : length;
  300. }
  301. }
  302. public static Dictionary<string, string> GetContextForEcmaNode (string hash, string sourceID, Node node)
  303. {
  304. var args = new Dictionary<string, string> ();
  305. args["source-id"] = sourceID;
  306. if (node != null) {
  307. var nodeType = GetNodeType (node);
  308. switch (nodeType) {
  309. case EcmaNodeType.Namespace:
  310. args["show"] = "namespace";
  311. args["namespace"] = node.Element.Substring ("N:".Length);
  312. break;
  313. case EcmaNodeType.Type:
  314. args["show"] = "typeoverview";
  315. break;
  316. case EcmaNodeType.Member:
  317. case EcmaNodeType.Meta:
  318. switch (GetNodeMemberTypeChar (node)){
  319. case 'C':
  320. args["membertype"] = "Constructor";
  321. break;
  322. case 'M':
  323. args["membertype"] = "Method";
  324. break;
  325. case 'P':
  326. args["membertype"] = "Property";
  327. break;
  328. case 'F':
  329. args["membertype"] = "Field";
  330. break;
  331. case 'E':
  332. args["membertype"] = "Event";
  333. break;
  334. case 'O':
  335. args["membertype"] = "Operator";
  336. break;
  337. case 'X':
  338. args["membertype"] = "ExtensionMethod";
  339. break;
  340. case '*':
  341. args["membertype"] = "All";
  342. break;
  343. }
  344. if (nodeType == EcmaNodeType.Meta) {
  345. args["show"] = "members";
  346. args["index"] = "all";
  347. } else {
  348. args["show"] = "member";
  349. args["index"] = node.Element;
  350. }
  351. break;
  352. }
  353. }
  354. if (!string.IsNullOrEmpty (hash))
  355. args["hash"] = hash;
  356. return args;
  357. }
  358. public static EcmaNodeType GetNodeType (Node node)
  359. {
  360. // We guess the node type by checking the depth level it's at in the tree
  361. int level = GetNodeLevel (node);
  362. switch (level) {
  363. case 0:
  364. return EcmaNodeType.Namespace;
  365. case 1:
  366. return EcmaNodeType.Type;
  367. case 2:
  368. return EcmaNodeType.Meta;
  369. case 3: // Here it's either a member or, in case of overload, a meta
  370. return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
  371. case 4: // At this level, everything is necessarily a member
  372. return EcmaNodeType.Member;
  373. default:
  374. return EcmaNodeType.Invalid;
  375. }
  376. }
  377. public static char GetNodeMemberTypeChar (Node node)
  378. {
  379. int level = GetNodeLevel (node);
  380. // We try to reach the member group node depending on node nested level
  381. switch (level) {
  382. case 2:
  383. return node.Element[0];
  384. case 3:
  385. return node.Parent.Element[0];
  386. case 4:
  387. return node.Parent.Parent.Element[0];
  388. default:
  389. throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
  390. }
  391. }
  392. public static int GetNodeLevel (Node node)
  393. {
  394. int i = 0;
  395. for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++) {
  396. node = node.Parent;
  397. if (node == null)
  398. return i - 1;
  399. }
  400. return i - 1;
  401. }
  402. public static string EtcKindToCaption (char etc)
  403. {
  404. switch (etc) {
  405. case 'M':
  406. return "Methods";
  407. case 'P':
  408. return "Properties";
  409. case 'C':
  410. return "Constructors";
  411. case 'F':
  412. return "Fields";
  413. case 'E':
  414. return "Events";
  415. case 'O':
  416. return "Operators";
  417. case '*':
  418. return "Members";
  419. default:
  420. return null;
  421. }
  422. }
  423. public static string MemberKindToCaption (EcmaDesc.Kind kind)
  424. {
  425. switch (kind) {
  426. case EcmaDesc.Kind.Method:
  427. return "Methods";
  428. case EcmaDesc.Kind.Property:
  429. return "Properties";
  430. case EcmaDesc.Kind.Constructor:
  431. return "Constructors";
  432. case EcmaDesc.Kind.Field:
  433. return "Fields";
  434. case EcmaDesc.Kind.Event:
  435. return "Events";
  436. case EcmaDesc.Kind.Operator:
  437. return "Operators";
  438. default:
  439. return null;
  440. }
  441. }
  442. public static Node FindNodeForCaption (IList<Node> nodes, string caption)
  443. {
  444. foreach (var node in nodes)
  445. if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
  446. return node;
  447. return null;
  448. }
  449. public static int CountTypeGenericArguments (string typeDefinition, int startIndex = 0)
  450. {
  451. int nestedLevel = 0;
  452. int count = 0;
  453. bool started = false;
  454. foreach (char c in typeDefinition.Skip (startIndex)) {
  455. switch (c) {
  456. case '<':
  457. if (!started)
  458. count = 1;
  459. started = true;
  460. nestedLevel++;
  461. break;
  462. case ',':
  463. if (started && nestedLevel == 1)
  464. count++;
  465. break;
  466. case '>':
  467. nestedLevel--;
  468. break;
  469. }
  470. }
  471. return count;
  472. }
  473. internal static string MakeOperatorSignature (XElement member, out string memberSignature)
  474. {
  475. string name = (string)member.Attribute ("MemberName");
  476. var nicename = name.Substring(3);
  477. memberSignature = null;
  478. switch (name) {
  479. // unary operators: no overloading possible [ECMA-335 ยง10.3.1]
  480. case "op_UnaryPlus": // static R operator+ (T)
  481. case "op_UnaryNegation": // static R operator- (T)
  482. case "op_LogicalNot": // static R operator! (T)
  483. case "op_OnesComplement": // static R operator~ (T)
  484. case "op_Increment": // static R operator++ (T)
  485. case "op_Decrement": // static R operator-- (T)
  486. case "op_True": // static bool operator true (T)
  487. case "op_False": // static bool operator false (T)
  488. case "op_AddressOf": // static R operator& (T)
  489. case "op_PointerDereference": // static R operator* (T)
  490. memberSignature = nicename;
  491. break;
  492. // conversion operators: overloading based on parameter and return type [ECMA-335 ยง10.3.3]
  493. case "op_Implicit": // static implicit operator R (T)
  494. case "op_Explicit": // static explicit operator R (T)
  495. nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
  496. string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
  497. string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
  498. memberSignature = arg + " to " + ret;
  499. break;
  500. // binary operators: overloading is possible [ECMA-335 ยง10.3.2]
  501. default:
  502. if (member.Element ("Parameters") != null)
  503. memberSignature =
  504. nicename + "("
  505. + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
  506. + ")";
  507. break;
  508. }
  509. return nicename;
  510. }
  511. static XElement ExtractClassSummary (XDocument typeDoc)
  512. {
  513. string name = typeDoc.Root.Attribute("Name").Value;
  514. string fullName = typeDoc.Root.Attribute("FullName").Value;
  515. string assemblyName = typeDoc.Root.Element("AssemblyInfo") != null ? typeDoc.Root.Element("AssemblyInfo").Element("AssemblyName").Value : string.Empty;
  516. var docs = typeDoc.Root.Element("Docs");
  517. var summary = docs.Element("summary") ?? new XElement("summary");
  518. var remarks = docs.Element("remarks") ?? new XElement("remarks");
  519. return new XElement ("class",
  520. new XAttribute ("name", name ?? string.Empty),
  521. new XAttribute ("fullname", fullName ?? string.Empty),
  522. new XAttribute ("assembly", assemblyName ?? string.Empty),
  523. summary,
  524. remarks);
  525. }
  526. }
  527. }