Merge pull request #654 from alesliehughes/master
[mono.git] / mcs / class / monodoc / Monodoc / providers / ecma-provider.cs
1 //
2 // The ecmaspec provider is for ECMA specifications
3 //
4 // Authors:
5 //      John Luke (jluke@cfl.rr.com)
6 //      Ben Maurer (bmaurer@users.sourceforge.net)
7 //
8 // Use like this:
9 //   mono assembler.exe --ecmaspec DIRECTORY --out name
10 //
11
12 using System;
13 using System.Linq;
14 using System.IO;
15 using System.Text;
16 using System.Xml;
17 using System.Xml.Linq;
18 using System.Collections.Generic;
19
20 using Lucene.Net.Index;
21 using Lucene.Net.Documents;
22
23 using Monodoc.Ecma;
24 using Mono.Utilities;
25
26 namespace Monodoc.Providers
27 {
28         public class EcmaProvider : Provider
29         {
30                 HashSet<string> directories = new HashSet<string> ();
31
32                 public EcmaProvider ()
33                 {
34                 }
35
36                 public EcmaProvider (string baseDir)
37                 {
38                         AddDirectory (baseDir);
39                 }
40
41                 public void AddDirectory (string directory)
42                 {
43                         if (string.IsNullOrEmpty (directory))
44                                 throw new ArgumentNullException ("directory");
45
46                         directories.Add (directory);
47                 }
48
49                 public override void PopulateTree (Tree tree)
50                 {
51                         var storage = tree.HelpSource.Storage;
52                         var nsSummaries = new Dictionary<string, XElement> ();
53                         int resID = 0;
54
55                         foreach (var asm in directories) {
56                                 var indexFilePath = Path.Combine (asm, "index.xml");
57                                 if (!File.Exists (indexFilePath)) {
58                                         Console.Error.WriteLine ("Warning: couldn't process directory `{0}' as it has no index.xml file", asm);
59                                         continue;
60                                 }
61
62                                 EcmaDoc.PopulateTreeFromIndexFile (indexFilePath, EcmaHelpSource.EcmaPrefix, tree, storage, nsSummaries, _ => resID++.ToString ());
63                         }
64
65                         foreach (var summary in nsSummaries)
66                                 storage.Store ("xml.summary." + summary.Key, summary.Value.ToString ());
67
68                         var masterSummary = new XElement ("elements",
69                                                           directories
70                                                           .SelectMany (d => Directory.EnumerateFiles (d, "ns-*.xml"))
71                                                           .Select (ExtractNamespaceSummary));
72                         storage.Store ("mastersummary.xml", masterSummary.ToString ());
73                 }
74
75                 XElement ExtractNamespaceSummary (string nsFile)
76                 {
77                         using (var reader = XmlReader.Create (nsFile)) {
78                                 reader.ReadToFollowing ("Namespace");
79                                 var name = reader.GetAttribute ("Name");
80                                 var summary = reader.ReadToFollowing ("summary") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("summary");
81                                 var remarks = reader.ReadToFollowing ("remarks") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("remarks");
82
83                                 return new XElement ("namespace",
84                                                      new XAttribute ("ns", name ?? string.Empty),
85                                                      summary,
86                                                      remarks);
87                         }
88                 }
89
90                 public override void CloseTree (HelpSource hs, Tree tree)
91                 {
92                         AddImages (hs);
93                         AddExtensionMethods (hs);
94                 }
95
96                 void AddEcmaXml (HelpSource hs)
97                 {
98                         var xmls = directories
99                                 .SelectMany (Directory.EnumerateDirectories) // Assemblies
100                                 .SelectMany (Directory.EnumerateDirectories) // Namespaces
101                                 .SelectMany (Directory.EnumerateFiles)
102                                 .Where (f => f.EndsWith (".xml")); // Type XML files
103
104                         int resID = 0;
105                         foreach (var xml in xmls)
106                                 using (var file = File.OpenRead (xml))
107                                         hs.Storage.Store ((resID++).ToString (), file);
108                 }
109
110                 void AddImages (HelpSource hs)
111                 {
112                         var imgs = directories
113                                 .SelectMany (Directory.EnumerateDirectories)
114                                 .Select (d => Path.Combine (d, "_images"))
115                                 .Where (Directory.Exists)
116                                 .SelectMany (Directory.EnumerateFiles);
117
118                         foreach (var img in imgs)
119                                 using (var file = File.OpenRead (img))
120                                         hs.Storage.Store (Path.GetFileName (img), file);
121                 }
122
123                 void AddExtensionMethods (HelpSource hs)
124                 {
125                         var extensionMethods = directories
126                                 .SelectMany (Directory.EnumerateDirectories)
127                                 .Select (d => Path.Combine (d, "index.xml"))
128                                 .Where (File.Exists)
129                                 .Select (f => {
130                                         using (var file = File.OpenRead (f)) {
131                                                 var reader = XmlReader.Create (file);
132                                                 reader.ReadToFollowing ("ExtensionMethods");
133                                                 return reader.ReadInnerXml ();
134                                         }
135                             })
136                             .DefaultIfEmpty (string.Empty);
137
138                         hs.Storage.Store ("ExtensionMethods.xml",
139                                           "<ExtensionMethods>" + extensionMethods.Aggregate (string.Concat) + "</ExtensionMethods>");
140                 }
141
142                 IEnumerable<string> GetEcmaXmls ()
143                 {
144                         return directories
145                                 .SelectMany (Directory.EnumerateDirectories) // Assemblies
146                                 .SelectMany (Directory.EnumerateDirectories) // Namespaces
147                                 .SelectMany (Directory.EnumerateFiles)
148                                 .Where (f => f.EndsWith (".xml")); // Type XML files
149                 }
150         }
151
152         public class EcmaHelpSource : HelpSource
153         {
154                 internal const string EcmaPrefix = "ecma:";
155                 LRUCache<string, Node> cache = new LRUCache<string, Node> (4);
156
157                 public EcmaHelpSource (string base_file, bool create) : base (base_file, create)
158                 {
159                 }
160
161                 protected EcmaHelpSource () : base ()
162                 {
163                 }
164
165                 protected override string UriPrefix {
166                         get {
167                                 return EcmaPrefix;
168                         }
169                 }
170
171                 public override bool CanHandleUrl (string url)
172                 {
173                         if (url.Length > 2 && url[1] == ':') {
174                                 switch (url[0]) {
175                                 case 'T':
176                                 case 'M':
177                                 case 'C':
178                                 case 'P':
179                                 case 'E':
180                                 case 'F':
181                                 case 'N':
182                                 case 'O':
183                                         return true;
184                                 }
185                         }
186                         return base.CanHandleUrl (url);
187                 }
188
189                 // Clean the extra paramers in the id
190                 public override Stream GetHelpStream (string id)
191                 {
192                         var idParts = id.Split ('?');
193                         var name = idParts[0];
194                         if (name == "root:")
195                                 name = "mastersummary.xml";
196                         return base.GetHelpStream (name);
197                 }
198
199                 public override Stream GetCachedHelpStream (string id)
200                 {
201                         var idParts = id.Split ('?');
202                         return base.GetCachedHelpStream (idParts[0]);
203                 }
204
205                 public override DocumentType GetDocumentTypeForId (string id)
206                 {
207                         return DocumentType.EcmaXml;
208                 }
209
210                 public override string GetPublicUrl (Node node)
211                 {
212                         string url = string.Empty;
213                         var type = EcmaDoc.GetNodeType (node);
214                         //Console.WriteLine ("GetPublicUrl {0} : {1} [{2}]", node.Element, node.Caption, type.ToString ());
215                         switch (type) {
216                         case EcmaNodeType.Namespace:
217                                 return node.Element; // A namespace node has already a well formated internal url
218                         case EcmaNodeType.Type:
219                                 return MakeTypeNodeUrl (node);
220                         case EcmaNodeType.Meta:
221                                 return MakeTypeNodeUrl (GetNodeTypeParent (node)) + GenerateMetaSuffix (node);
222                         case EcmaNodeType.Member:
223                                 var typeChar = EcmaDoc.GetNodeMemberTypeChar (node);
224                                 var parentNode = GetNodeTypeParent (node);
225                                 var typeNode = MakeTypeNodeUrl (parentNode).Substring (2);
226                                 return typeChar + ":" + typeNode + MakeMemberNodeUrl (typeChar, node);
227                         default:
228                                 return null;
229                         }
230                 }
231
232                 string MakeTypeNodeUrl (Node node)
233                 {
234                         // A Type node has a Element property of the form: 'ecma:{number}#{typename}/'
235                         var hashIndex = node.Element.IndexOf ('#');
236                         var typeName = node.Element.Substring (hashIndex + 1, node.Element.Length - hashIndex - 2);
237                         return "T:" + node.Parent.Caption + '.' + typeName.Replace ('.', '+');
238                 }
239
240                 string MakeMemberNodeUrl (char typeChar, Node node)
241                 {
242                         // We clean inner type ctor name which may contain the outer type name
243                         var caption = node.Caption;
244
245                         // Sanitize constructor caption of inner types
246                         if (typeChar == 'C') {
247                                 int lastDot = -1;
248                                 for (int i = 0; i < caption.Length && caption[i] != '('; i++)
249                                         lastDot = caption[i] == '.' ? i : lastDot;
250                                 return lastDot == -1 ? '.' + caption : caption.Substring (lastDot);
251                         }
252
253                         /* We handle type conversion operator by checking if the name contains " to "
254                          * (as in 'foo to bar') and we generate a corresponding conversion signature
255                          */
256                         if (typeChar == 'O' && caption.IndexOf (" to ") != -1) {
257                                 var parts = caption.Split (' ');
258                                 return "." + node.Parent.Caption + "(" + parts[0] + ", " + parts[2] + ")";
259                         }
260
261                         /* The goal here is to treat method which are explicit interface definition
262                          * such as 'void IDisposable.Dispose ()' for which the caption is a dot
263                          * expression thus colliding with the ecma parser.
264                          * If the first non-alpha character in the caption is a dot then we have an
265                          * explicit member implementation (we assume the interface has namespace)
266                          */
267                         var firstNonAlpha = caption.FirstOrDefault (c => !char.IsLetterOrDigit (c));
268                         if (firstNonAlpha == '.')
269                                 return "$" + caption;
270
271                         return "." + caption;
272                 }
273
274                 Node GetNodeTypeParent (Node node)
275                 {
276                         // Type nodes are always at level 2 so we just need to get there
277                         while (node != null && node.Parent != null
278                                && !node.Parent.Parent.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase) && node.Parent.Parent.Parent != null)
279                                 node = node.Parent;
280                         return node;
281                 }
282
283                 string GenerateMetaSuffix (Node node)
284                 {
285                         string suffix = string.Empty;
286                         // A meta node has always a type element to begin with
287                         while (EcmaDoc.GetNodeType (node) != EcmaNodeType.Type) {
288                                 suffix = '/' + node.Element + suffix;
289                                 node = node.Parent;
290                         }
291                         return suffix;
292                 }
293
294                 public override string GetInternalIdForUrl (string url, out Node node, out Dictionary<string, string> context)
295                 {
296                         var id = string.Empty;
297                         node = null;
298                         context = null;
299
300                         if (!url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase)) {
301                                 node = MatchNode (url);
302                                 if (node == null)
303                                         return null;
304                                 id = node.GetInternalUrl ();
305                         }
306
307                         string hash;
308                         id = GetInternalIdForInternalUrl (id, out hash);
309                         context = EcmaDoc.GetContextForEcmaNode (hash, SourceID.ToString (), node);
310
311                         return id;
312                 }
313
314                 public string GetInternalIdForInternalUrl (string internalUrl, out string hash)
315                 {
316                         var id = internalUrl;
317                         if (id.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase))
318                                 id = id.Substring (UriPrefix.Length);
319                         else if (id.StartsWith ("N:", StringComparison.OrdinalIgnoreCase))
320                                 id = "xml.summary." + id.Substring ("N:".Length);
321
322                         var hashIndex = id.IndexOf ('#');
323                         hash = string.Empty;
324                         if (hashIndex != -1) {
325                                 hash = id.Substring (hashIndex + 1);
326                                 id = id.Substring (0, hashIndex);
327                         }
328
329                         return id;
330                 }
331
332                 public override Node MatchNode (string url)
333                 {
334                         Node node = null;
335                         if ((node = cache.Get (url)) == null) {
336                                 node = EcmaDoc.MatchNodeWithEcmaUrl (url, Tree);
337                                 if (node != null)
338                                         cache.Put (url, node);
339                         }
340                         return node;
341                 }
342
343                 public override void PopulateIndex (IndexMaker index_maker)
344                 {
345                         foreach (Node ns_node in Tree.RootNode.ChildNodes){
346                                 foreach (Node type_node in ns_node.ChildNodes){
347                                         string typename = type_node.Caption.Substring (0, type_node.Caption.IndexOf (' '));
348                                         string full = ns_node.Caption + "." + typename;
349
350                                         string doc_tag = GetKindFromCaption (type_node.Caption);
351                                         string url = type_node.PublicUrl;
352
353                                         //
354                                         // Add MonoMac/MonoTouch [Export] attributes, those live only in classes
355                                         //
356                                         XDocument type_doc = null;
357                                         ILookup<string, XElement> prematchedMembers = null;
358                                         bool hasExports = doc_tag == "Class" && (ns_node.Caption.StartsWith ("MonoTouch") || ns_node.Caption.StartsWith ("MonoMac"));
359                                         if (hasExports) {
360                                                 try {
361                                                         string rest, hash;
362                                                         var id = GetInternalIdForInternalUrl (type_node.GetInternalUrl (), out hash);
363                                                         type_doc = XDocument.Load (GetHelpStream (id));
364                                                         prematchedMembers = type_doc.Root.Element ("Members").Elements ("Member").ToLookup (n => (string)n.Attribute ("MemberName"), n => n);
365                                                 } catch (Exception e) {
366                                                         Console.WriteLine ("Problem processing {0} for MonoTouch/MonoMac exports\n\n{0}", e);
367                                                         hasExports = false;
368                                                 }
369                                         }
370
371                                         if (doc_tag == "Class" || doc_tag == "Structure" || doc_tag == "Interface"){
372                                                 index_maker.Add (type_node.Caption, typename, url);
373                                                 index_maker.Add (full + " " + doc_tag, full, url);
374
375                                                 foreach (Node c in type_node.ChildNodes){
376                                                         switch (c.Caption){
377                                                         case "Constructors":
378                                                                 index_maker.Add ("  constructors", typename+"0", url + "/C");
379                                                                 break;
380                                                         case "Fields":
381                                                                 index_maker.Add ("  fields", typename+"1", url + "/F");
382                                                                 break;
383                                                         case "Events":
384                                                                 index_maker.Add ("  events", typename+"2", url + "/E");
385                                                                 break;
386                                                         case "Properties":
387                                                                 index_maker.Add ("  properties", typename+"3", url + "/P");
388                                                                 break;
389                                                         case "Methods":
390                                                                 index_maker.Add ("  methods", typename+"4", url + "/M");
391                                                                 break;
392                                                         case "Operators":
393                                                                 index_maker.Add ("  operators", typename+"5", url + "/O");
394                                                                 break;
395                                                         }
396                                                 }
397
398                                                 //
399                                                 // Now repeat, but use a different sort key, to make sure we come after
400                                                 // the summary data above, start the counter at 6
401                                                 //
402                                                 string keybase = typename + "6.";
403
404                                                 foreach (Node c in type_node.ChildNodes){
405                                                         var type = c.Caption[0];
406
407                                                         foreach (Node nc in c.ChildNodes) {
408                                                                 string res = nc.Caption;
409                                                                 string nurl = nc.PublicUrl;
410
411                                                                 // Process exports
412                                                                 if (hasExports && (type == 'C' || type == 'M' || type == 'P')) {
413                                                                         try {
414                                                                                 var member = GetMemberFromCaption (type_doc, type == 'C' ? ".ctor" : res, false, prematchedMembers);
415                                                                                 var exports = member.Descendants ("AttributeName").Where (a => a.Value.Contains ("Foundation.Export"));
416                                                                                 foreach (var exportNode in exports) {
417                                                                                         var parts = exportNode.Value.Split ('"');
418                                                                                         if (parts.Length != 3) {
419                                                                                                 Console.WriteLine ("Export attribute not found or not usable in {0}", exportNode);
420                                                                                         } else {
421                                                                                                 var export = parts[1];
422                                                                                                 index_maker.Add (export + " selector", export, nurl);
423                                                                                         }
424                                                                                 }
425                                                                         } catch (Exception e) {
426                                                                                 Console.WriteLine ("Problem processing {0}/{1} for MonoTouch/MonoMac exports\n\n{2}", nurl, res, e);
427                                                                         }
428                                                                 }
429
430                                                                 switch (type){
431                                                                 case 'C':
432                                                                         break;
433                                                                 case 'F':
434                                                                         index_maker.Add (String.Format ("{0}.{1} field", typename, res),
435                                                                                          keybase + res, nurl);
436                                                                         index_maker.Add (String.Format ("{0} field", res), res, nurl);
437                                                                         break;
438                                                                 case 'E':
439                                                                         index_maker.Add (String.Format ("{0}.{1} event", typename, res),
440                                                                                          keybase + res, nurl);
441                                                                         index_maker.Add (String.Format ("{0} event", res), res, nurl);
442                                                                         break;
443                                                                 case 'P':
444                                                                         index_maker.Add (String.Format ("{0}.{1} property", typename, res),
445                                                                                          keybase + res, nurl);
446                                                                         index_maker.Add (String.Format ("{0} property", res), res, nurl);
447                                                                         break;
448                                                                 case 'M':
449                                                                         index_maker.Add (String.Format ("{0}.{1} method", typename, res),
450                                                                                          keybase + res, nurl);
451                                                                         index_maker.Add (String.Format ("{0} method", res), res, nurl);
452                                                                         break;
453                                                                 case 'O':
454                                                                         index_maker.Add (String.Format ("{0}.{1} operator", typename, res),
455                                                                                          keybase + res, nurl);
456                                                                         break;
457                                                                 }
458                                                         }
459                                                 }
460                                         } else if (doc_tag == "Enumeration"){
461                                                 //
462                                                 // Enumerations: add the enumeration values
463                                                 //
464                                                 index_maker.Add (type_node.Caption, typename, url);
465                                                 index_maker.Add (full + " " + doc_tag, full, url);
466
467                                                 // Now, pull the values.
468                                                 string rest, hash;
469                                                 var id = GetInternalIdForInternalUrl (type_node.GetInternalUrl (), out hash);
470                                                 var xdoc = XDocument.Load (GetHelpStream (id));
471                                                 if (xdoc == null)
472                                                         continue;
473
474                                                 var members = xdoc.Root.Element ("Members").Elements ("Members");
475                                                 if (members == null)
476                                                         continue;
477
478                                                 foreach (var member_node in members){
479                                                         string enum_value = member_node.Attribute ("MemberName").Value;
480                                                         string caption = enum_value + " value";
481                                                         index_maker.Add (caption, caption, url);
482                                                 }
483                                         } else if (doc_tag == "Delegate"){
484                                                 index_maker.Add (type_node.Caption, typename, url);
485                                                 index_maker.Add (full + " " + doc_tag, full, url);
486                                         }
487                                 }
488                         }
489                 }
490
491
492                 public override void PopulateSearchableIndex (IndexWriter writer)
493                 {
494                         StringBuilder text = new StringBuilder ();
495                         SearchableDocument searchDoc = new SearchableDocument ();
496
497                         foreach (Node ns_node in Tree.RootNode.ChildNodes) {
498                                 foreach (Node type_node in ns_node.ChildNodes) {
499                                         string typename = type_node.Caption.Substring (0, type_node.Caption.IndexOf (' '));
500                                         string full = ns_node.Caption + "." + typename;
501                                         string url = type_node.PublicUrl;
502                                         string doc_tag = GetKindFromCaption (type_node.Caption);
503                                         string rest, hash;
504                                         var id = GetInternalIdForInternalUrl (type_node.GetInternalUrl (), out hash);
505                                         var xdoc = XDocument.Load (GetHelpStream (id));
506                                         if (xdoc == null)
507                                                 continue;
508                                         if (string.IsNullOrEmpty (doc_tag))
509                                                 continue;
510
511                                         // For classes, structures or interfaces add a doc for the overview and
512                                         // add a doc for every constructor, method, event, ...
513                                         // doc_tag == "Class" || doc_tag == "Structure" || doc_tag == "Interface"
514                                         if (doc_tag[0] == 'C' || doc_tag[0] == 'S' || doc_tag[0] == 'I') {
515                                                 // Adds a doc for every overview of every type
516                                                 SearchableDocument doc = searchDoc.Reset ();
517                                                 doc.Title = type_node.Caption;
518                                                 doc.HotText = typename;
519                                                 doc.Url = url;
520                                                 doc.FullTitle = full;
521
522                                                 var node_sel = xdoc.Root.Element ("Docs");
523                                                 text.Clear ();
524                                                 GetTextFromNode (node_sel, text);
525                                                 doc.Text = text.ToString ();
526
527                                                 text.Clear ();
528                                                 GetExamples (node_sel, text);
529                                                 doc.Examples = text.ToString ();
530
531                                                 writer.AddDocument (doc.LuceneDoc);
532                                                 var exportParsable = doc_tag[0] == 'C' && (ns_node.Caption.StartsWith ("MonoTouch") || ns_node.Caption.StartsWith ("MonoMac"));
533
534                                                 //Add docs for contructors, methods, etc.
535                                                 foreach (Node c in type_node.ChildNodes) { // c = Constructors || Fields || Events || Properties || Methods || Operators
536                                                         if (c.Element == "*")
537                                                                 continue;
538                                                         const float innerTypeBoost = 0.2f;
539
540                                                         IEnumerable<Node> ncnodes = c.ChildNodes;
541                                                         // The rationale is that we need to properly handle method overloads
542                                                         // so for those method node which have children, flatten them
543                                                         if (c.Caption == "Methods") {
544                                                                 ncnodes = ncnodes
545                                                                         .Where (n => n.ChildNodes == null || n.ChildNodes.Count == 0)
546                                                                         .Concat (ncnodes.Where (n => n.ChildNodes.Count > 0).SelectMany (n => n.ChildNodes));
547                                                         } else if (c.Caption == "Operators") {
548                                                                 ncnodes = ncnodes
549                                                                         .Where (n => !n.Caption.EndsWith ("Conversion"))
550                                                                         .Concat (ncnodes.Where (n => n.Caption.EndsWith ("Conversion")).SelectMany (n => n.ChildNodes));
551                                                         }
552
553                                                         var prematchedMembers = xdoc.Root.Element ("Members").Elements ("Member").ToLookup (n => (string)n.Attribute ("MemberName"), n => n);
554
555                                                         foreach (Node nc in ncnodes) {
556                                                                 XElement docsNode = null;
557                                                                 try {
558                                                                         docsNode = GetDocsFromCaption (xdoc, c.Caption[0] == 'C' ? ".ctor" : nc.Caption, c.Caption[0] == 'O', prematchedMembers);
559                                                                 } catch {}
560                                                                 if (docsNode == null) {
561                                                                         Console.Error.WriteLine ("Problem: {0}", nc.PublicUrl);
562                                                                         continue;
563                                                                 }
564
565                                                                 SearchableDocument doc_nod = searchDoc.Reset ();
566                                                                 doc_nod.Title = LargeName (nc) + " " + EcmaDoc.EtcKindToCaption (c.Caption[0]);
567                                                                 doc_nod.FullTitle = ns_node.Caption + '.' + typename + "::" + nc.Caption;
568                                                                 doc_nod.HotText = string.Empty;
569
570                                                                 /* Disable constructors hottext indexing as it's often "polluting" search queries
571                                                                    because it has the same hottext than standard types */
572                                                                 if (c.Caption != "Constructors") {
573                                                                         //dont add the parameters to the hottext
574                                                                         int ppos = nc.Caption.IndexOf ('(');
575                                                                         doc_nod.HotText = ppos != -1 ? nc.Caption.Substring (0, ppos) : nc.Caption;
576                                                                 }
577
578                                                                 var urlnc = nc.PublicUrl;
579                                                                 doc_nod.Url = urlnc;
580
581                                                                 text.Clear ();
582                                                                 GetTextFromNode (docsNode, text);
583                                                                 doc_nod.Text = text.ToString ();
584
585                                                                 text.Clear ();
586                                                                 GetExamples (docsNode, text);
587                                                                 doc_nod.Examples = text.ToString ();
588
589                                                                 Document lucene_doc = doc_nod.LuceneDoc;
590                                                                 lucene_doc.Boost = innerTypeBoost;
591                                                                 writer.AddDocument (lucene_doc);
592
593                                                                 // Objective-C binding specific parsing of [Export] attributes
594                                                                 if (exportParsable) {
595                                                                         try {
596                                                                                 var exports = docsNode.Parent.Elements ("Attributes").Elements ("Attribute").Elements ("AttributeName")
597                                                                                         .Select (a => (string)a).Where (txt => txt.Contains ("Foundation.Export"));
598
599                                                                                 foreach (var exportNode in exports) {
600                                                                                         var parts = exportNode.Split ('"');
601                                                                                         if (parts.Length != 3) {
602                                                                                                 Console.WriteLine ("Export attribute not found or not usable in {0}", exportNode);
603                                                                                                 continue;
604                                                                                         }
605
606                                                                                         var export = parts[1];
607                                                                                         var export_node = searchDoc.Reset ();
608                                                                                         export_node.Title = export + " Export";
609                                                                                         export_node.FullTitle = ns_node.Caption + '.' + typename + "::" + export;
610                                                                                         export_node.Url = urlnc;
611                                                                                         export_node.HotText = export;
612                                                                                         export_node.Text = string.Empty;
613                                                                                         export_node.Examples = string.Empty;
614                                                                                         lucene_doc = export_node.LuceneDoc;
615                                                                                         lucene_doc.Boost = innerTypeBoost;
616                                                                                         writer.AddDocument (lucene_doc);
617                                                                                 }
618                                                                         } catch (Exception e){
619                                                                                 Console.WriteLine ("Problem processing {0} for MonoTouch/MonoMac exports\n\n{0}", e);
620                                                                         }
621                                                                 }
622                                                         }
623                                                 }
624                                         // doc_tag == "Enumeration"
625                                         } else if (doc_tag[0] == 'E'){
626                                                 var members = xdoc.Root.Element ("Members").Elements ("Member");
627                                                 if (members == null)
628                                                         continue;
629
630                                                 text.Clear ();
631                                                 foreach (var member_node in members) {
632                                                         string enum_value = (string)member_node.Attribute ("MemberName");
633                                                         text.Append (enum_value);
634                                                         text.Append (" ");
635                                                         GetTextFromNode (member_node.Element ("Docs"), text);
636                                                         text.AppendLine ();
637                                                 }
638
639                                                 SearchableDocument doc = searchDoc.Reset ();
640
641                                                 text.Clear ();
642                                                 GetExamples (xdoc.Root.Element ("Docs"), text);
643                                                 doc.Examples = text.ToString ();
644
645                                                 doc.Title = type_node.Caption;
646                                                 doc.HotText = (string)xdoc.Root.Attribute ("Name");
647                                                 doc.FullTitle = full;
648                                                 doc.Url = url;
649                                                 doc.Text = text.ToString();
650                                                 writer.AddDocument (doc.LuceneDoc);
651                                         // doc_tag == "Delegate"
652                                         } else if (doc_tag[0] == 'D'){
653                                                 SearchableDocument doc = searchDoc.Reset ();
654                                                 doc.Title = type_node.Caption;
655                                                 doc.HotText = (string)xdoc.Root.Attribute ("Name");
656                                                 doc.FullTitle = full;
657                                                 doc.Url = url;
658
659                                                 var node_sel = xdoc.Root.Element ("Docs");
660
661                                                 text.Clear ();
662                                                 GetTextFromNode (node_sel, text);
663                                                 doc.Text = text.ToString();
664
665                                                 text.Clear ();
666                                                 GetExamples (node_sel, text);
667                                                 doc.Examples = text.ToString();
668
669                                                 writer.AddDocument (doc.LuceneDoc);
670                                         }
671                                 }
672                         }
673                 }
674
675                 string GetKindFromCaption (string s)
676                 {
677                         int p = s.LastIndexOf (' ');
678                         if (p > 0)
679                                 return s.Substring (p + 1);
680                         return null;
681                 }
682
683                 // Extract the interesting text from the docs node
684                 void GetTextFromNode (XElement n, StringBuilder sb)
685                 {
686                         // Include the text content of the docs
687                         sb.AppendLine (n.Value);
688                         foreach (var tag in n.Descendants ())
689                                 //include the url to which points the see tag and the name of the parameter
690                                 if ((tag.Name.LocalName.Equals ("see", StringComparison.Ordinal) || tag.Name.LocalName.Equals ("paramref", StringComparison.Ordinal))
691                                     && tag.HasAttributes)
692                                         sb.AppendLine ((string)tag.Attributes ().First ());
693                 }
694
695                 // Extract the code nodes from the docs
696                 void GetExamples (XElement n, StringBuilder sb)
697                 {
698                         foreach (var code in n.Descendants ("code"))
699                                 sb.Append ((string)code);
700                 }
701
702                 // Extract a large name for the Node
703                 static string LargeName (Node matched_node)
704                 {
705                         string[] parts = matched_node.GetInternalUrl ().Split('/', '#');
706                         if (parts.Length == 3 && parts[2] != String.Empty) //List of Members, properties, events, ...
707                                 return parts[1] + ": " + matched_node.Caption;
708                         else if(parts.Length >= 4) //Showing a concrete Member, property, ...
709                                 return parts[1] + "." + matched_node.Caption;
710                         else
711                                 return matched_node.Caption;
712                 }
713
714                 XElement GetMemberFromCaption (XDocument xdoc, string caption, bool isOperator, ILookup<string, XElement> prematchedMembers)
715                 {
716                         string name;
717                         IList<string> args;
718                         var doc = xdoc.Root.Element ("Members").Elements ("Member");
719
720                         if (isOperator) {
721                                 // The first case are explicit and implicit conversion operators which are grouped specifically
722                                 if (caption.IndexOf (" to ") != -1) {
723                                         var convArgs = caption.Split (new[] { " to " }, StringSplitOptions.None);
724                                         return doc
725                                                 .First (n => (AttrEq (n, "MemberName", "op_Explicit") || AttrEq (n, "MemberName", "op_Implicit"))
726                                                         && ((string)n.Element ("ReturnValue").Element ("ReturnType")).Equals (convArgs[1], StringComparison.Ordinal)
727                                                         && AttrEq (n.Element ("Parameters").Element ("Parameter"), "Type", convArgs[0]));
728                                 } else {
729                                         return doc.First (m => AttrEq (m, "MemberName", "op_" + caption));
730                                 }
731                         }
732
733                         TryParseCaption (caption, out name, out args);
734
735                         if (!string.IsNullOrEmpty (name)) { // Filter member by name
736                                 var prematched = prematchedMembers[name];
737                                 doc = prematched.Any () ? prematched : doc.Where (m => AttrEq (m, "MemberName", name));
738                         }
739                         if (args != null && args.Count > 0) // Filter member by its argument list
740                                 doc = doc.Where (m => m.Element ("Parameters").Elements ("Parameter").Attributes ("Type").Select (a => (string)a).SequenceEqual (args));
741
742                         return doc.First ();
743                 }
744
745                 XElement GetDocsFromCaption (XDocument xdoc, string caption, bool isOperator, ILookup<string, XElement> prematchedMembers)
746                 {
747                         return GetMemberFromCaption (xdoc, caption, isOperator, prematchedMembers).Element ("Docs");
748                 }
749
750                 // A simple stack-based parser to detect single type definition separated by commas
751                 IEnumerable<string> ExtractArguments (string rawArgList)
752                 {
753                         var sb = new System.Text.StringBuilder ();
754                         int genericDepth = 0;
755                         int arrayDepth = 0;
756
757                         for (int i = 0; i < rawArgList.Length; i++) {
758                                 char c = rawArgList[i];
759                                 switch (c) {
760                                 case ',':
761                                         if (genericDepth == 0 && arrayDepth == 0) {
762                                                 yield return sb.ToString ();
763                                                 sb.Clear ();
764                                                 continue;
765                                         }
766                                         break;
767                                 case '<':
768                                         genericDepth++;
769                                         break;
770                                 case '>':
771                                         genericDepth--;
772                                         break;
773                                 case '[':
774                                         arrayDepth++;
775                                         break;
776                                 case ']':
777                                         arrayDepth--;
778                                         break;
779                                 }
780                                 sb.Append (c);
781                         }
782                         if (sb.Length > 0)
783                                 yield return sb.ToString ();
784                 }
785
786                 void TryParseCaption (string caption, out string name, out IList<string> argList)
787                 {
788                         name = null;
789                         argList = null;
790                         int parenIdx = caption.IndexOf ('(');
791                         // In case of simple name, there is no need for processing
792                         if (parenIdx == -1) {
793                                 name = caption;
794                                 return;
795                         }
796                         name = caption.Substring (0, parenIdx);
797                         // Now we gather the argument list if there is any
798                         var rawArgList = caption.Substring (parenIdx + 1, caption.Length - parenIdx - 2); // Only take what's inside the parens
799                         if (string.IsNullOrEmpty (rawArgList))
800                                 return;
801
802                         argList = ExtractArguments (rawArgList).Select (arg => arg.Trim ()).ToList ();
803                 }
804
805                 bool AttrEq (XElement element, string attributeName, string expectedValue)
806                 {
807                         return ((string)element.Attribute (attributeName)).Equals (expectedValue, StringComparison.Ordinal);
808                 }
809         }
810 }