2 // The ecmaspec provider is for ECMA specifications
5 // John Luke (jluke@cfl.rr.com)
6 // Ben Maurer (bmaurer@users.sourceforge.net)
9 // mono assembler.exe --ecmaspec DIRECTORY --out name
17 using System.Xml.Linq;
18 using System.Collections.Generic;
20 using Lucene.Net.Index;
21 using Lucene.Net.Documents;
26 namespace MonkeyDoc.Providers
28 public enum EcmaNodeType {
33 Meta, // A node that's here to serve as a header for other node
36 public class EcmaProvider : Provider
38 HashSet<string> directories = new HashSet<string> ();
40 public EcmaProvider ()
44 public EcmaProvider (string baseDir)
46 AddDirectory (baseDir);
49 public void AddDirectory (string directory)
51 if (string.IsNullOrEmpty (directory))
52 throw new ArgumentNullException ("directory");
54 directories.Add (directory);
57 public override void PopulateTree (Tree tree)
59 var root = tree.RootNode;
60 var storage = tree.HelpSource.Storage;
62 var nsSummaries = new Dictionary<string, XElement> ();
64 foreach (var asm in directories) {
65 var indexFilePath = Path.Combine (asm, "index.xml");
66 if (!File.Exists (indexFilePath)) {
67 Console.Error.WriteLine ("Warning: couldn't process directory `{0}' as it has no index.xml file", asm);
70 using (var reader = XmlReader.Create (File.OpenRead (indexFilePath))) {
71 reader.ReadToFollowing ("Types");
72 var types = XElement.Load (reader.ReadSubtree ());
74 foreach (var ns in types.Elements ("Namespace")) {
75 var nsName = (string)ns.Attribute ("Name");
76 nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
77 var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
80 if (!nsSummaries.TryGetValue (nsName, out nsElements))
81 nsSummaries[nsName] = nsElements = new XElement ("elements",
82 new XElement ("summary"),
83 new XElement ("remarks"));
85 foreach (var type in ns.Elements ("Type")) {
86 // Add the XML file corresponding to the type to our storage
88 var typeFilePath = Path.Combine (asm, nsName, Path.ChangeExtension (type.Attribute ("Name").Value, ".xml"));
89 if (!File.Exists (typeFilePath)) {
90 Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", typeFilePath);
93 using (var file = File.OpenRead (typeFilePath))
94 storage.Store (id.ToString (), file);
95 nsElements.Add (ExtractClassSummary (typeFilePath));
96 var typeDocument = XDocument.Load (typeFilePath);
98 var typeCaption = ((string)(type.Attribute ("DisplayName") ?? type.Attribute ("Name"))).Replace ('+', '.');
99 var url = "ecma:" + id + '#' + typeCaption + '/';
100 typeCaption += " " + (string)type.Attribute ("Kind");
101 var typeNode = nsNode.CreateNode (typeCaption, url);
103 // Add meta "Members" node
104 typeNode.CreateNode ("Members", "*");
105 var membersNode = typeDocument.Root.Element ("Members");
106 if (membersNode == null || !membersNode.Elements ().Any ())
108 var members = membersNode
110 .ToLookup (m => m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value);
112 foreach (var memberType in members) {
113 // We pluralize the member type to get the caption and take the first letter as URL
114 var node = typeNode.CreateNode (PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
117 var isCtors = memberType.Key[0] == 'C';
119 // We do not escape much member name here
120 foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
121 if (memberGroup.Count () > 1) {
123 var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
124 var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
125 foreach (var member in memberGroup)
126 overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
127 overloadNode.Sort ();
129 // We treat constructor differently by showing their argument list in all cases
130 node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
143 foreach (var summary in nsSummaries)
144 storage.Store ("xml.summary." + summary.Key, summary.Value.ToString ());
146 var masterSummary = new XElement ("elements",
148 .SelectMany (d => Directory.EnumerateFiles (d, "ns-*.xml"))
149 .Select (ExtractNamespaceSummary));
150 storage.Store ("mastersummary.xml", masterSummary.ToString ());
153 string PluralizeMemberType (string memberType)
155 switch (memberType) {
159 return memberType + "s";
163 string MakeMemberCaption (XElement member, bool withArguments)
165 var caption = (string)member.Attribute ("MemberName");
166 // Use type name instead of .ctor for cosmetic sake
167 if (caption == ".ctor") {
168 caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
169 // If this is an inner type ctor, strip the parent type reference
170 var plusIndex = caption.LastIndexOf ('+');
172 caption = caption.Substring (plusIndex + 1);
174 if (caption.StartsWith ("op_")) {
176 caption = MakeOperatorSignature (member, out sig);
177 caption = withArguments ? sig : caption;
181 var args = member.Element ("Parameters");
183 if (args != null && args.Elements ("Parameter").Any ()) {
184 caption += args.Elements ("Parameter")
185 .Select (p => (string)p.Attribute ("Type"))
186 .Aggregate ((p1, p2) => p1 + "," + p2);
194 XElement ExtractClassSummary (string typeFilePath)
196 using (var reader = XmlReader.Create (typeFilePath)) {
197 reader.ReadToFollowing ("Type");
198 var name = reader.GetAttribute ("Name");
199 var fullName = reader.GetAttribute ("FullName");
200 reader.ReadToFollowing ("AssemblyName");
201 var assemblyName = reader.ReadElementString ();
202 reader.ReadToFollowing ("summary");
203 var summary = reader.ReadInnerXml ();
204 reader.ReadToFollowing ("remarks");
205 var remarks = reader.ReadInnerXml ();
207 return new XElement ("class",
208 new XAttribute ("name", name ?? string.Empty),
209 new XAttribute ("fullname", fullName ?? string.Empty),
210 new XAttribute ("assembly", assemblyName ?? string.Empty),
211 new XElement ("summary", new XCData (summary)),
212 new XElement ("remarks", new XCData (remarks)));
216 XElement ExtractNamespaceSummary (string nsFile)
218 using (var reader = XmlReader.Create (nsFile)) {
219 reader.ReadToFollowing ("Namespace");
220 var name = reader.GetAttribute ("Name");
221 reader.ReadToFollowing ("summary");
222 var summary = reader.ReadInnerXml ();
223 reader.ReadToFollowing ("remarks");
224 var remarks = reader.ReadInnerXml ();
226 return new XElement ("namespace",
227 new XAttribute ("ns", name ?? string.Empty),
228 new XElement ("summary", new XCData (summary)),
229 new XElement ("remarks", new XCData (remarks)));
233 public override void CloseTree (HelpSource hs, Tree tree)
236 AddExtensionMethods (hs);
239 void AddEcmaXml (HelpSource hs)
241 var xmls = directories
242 .SelectMany (Directory.EnumerateDirectories) // Assemblies
243 .SelectMany (Directory.EnumerateDirectories) // Namespaces
244 .SelectMany (Directory.EnumerateFiles)
245 .Where (f => f.EndsWith (".xml")); // Type XML files
248 foreach (var xml in xmls)
249 using (var file = File.OpenRead (xml))
250 hs.Storage.Store ((resID++).ToString (), file);
253 void AddImages (HelpSource hs)
255 var imgs = directories
256 .SelectMany (Directory.EnumerateDirectories)
257 .Select (d => Path.Combine (d, "_images"))
258 .Where (Directory.Exists)
259 .SelectMany (Directory.EnumerateFiles);
261 foreach (var img in imgs)
262 using (var file = File.OpenRead (img))
263 hs.Storage.Store (Path.GetFileName (img), file);
266 void AddExtensionMethods (HelpSource hs)
268 var extensionMethods = directories
269 .SelectMany (Directory.EnumerateDirectories)
270 .Select (d => Path.Combine (d, "index.xml"))
273 using (var file = File.OpenRead (f)) {
274 var reader = XmlReader.Create (file);
275 reader.ReadToFollowing ("ExtensionMethods");
276 return reader.ReadInnerXml ();
279 .DefaultIfEmpty (string.Empty);
281 hs.Storage.Store ("ExtensionMethods.xml",
282 "<ExtensionMethods>" + extensionMethods.Aggregate (string.Concat) + "</ExtensionMethods>");
285 IEnumerable<string> GetEcmaXmls ()
288 .SelectMany (Directory.EnumerateDirectories) // Assemblies
289 .SelectMany (Directory.EnumerateDirectories) // Namespaces
290 .SelectMany (Directory.EnumerateFiles)
291 .Where (f => f.EndsWith (".xml")); // Type XML files
294 string MakeOperatorSignature (XElement member, out string memberSignature)
296 string name = (string)member.Attribute ("MemberName");
297 var nicename = name.Substring(3);
298 memberSignature = null;
301 // unary operators: no overloading possible [ECMA-335 §10.3.1]
302 case "op_UnaryPlus": // static R operator+ (T)
303 case "op_UnaryNegation": // static R operator- (T)
304 case "op_LogicalNot": // static R operator! (T)
305 case "op_OnesComplement": // static R operator~ (T)
306 case "op_Increment": // static R operator++ (T)
307 case "op_Decrement": // static R operator-- (T)
308 case "op_True": // static bool operator true (T)
309 case "op_False": // static bool operator false (T)
310 case "op_AddressOf": // static R operator& (T)
311 case "op_PointerDereference": // static R operator* (T)
312 memberSignature = nicename;
314 // conversion operators: overloading based on parameter and return type [ECMA-335 §10.3.3]
315 case "op_Implicit": // static implicit operator R (T)
316 case "op_Explicit": // static explicit operator R (T)
317 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
318 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
319 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
320 memberSignature = arg + " to " + ret;
322 // binary operators: overloading is possible [ECMA-335 §10.3.2]
326 + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
335 public class EcmaHelpSource : HelpSource
337 const string EcmaPrefix = "ecma:";
338 EcmaUrlParser parser = new EcmaUrlParser ();
339 LRUCache<string, Node> cache = new LRUCache<string, Node> (4);
341 public EcmaHelpSource (string base_file, bool create) : base (base_file, create)
345 protected override string UriPrefix {
351 public override bool CanHandleUrl (string url)
353 if (url.Length > 2 && url[1] == ':') {
366 return base.CanHandleUrl (url);
369 // Clean the extra paramers in the id
370 public override Stream GetHelpStream (string id)
372 var idParts = id.Split ('?');
373 return base.GetHelpStream (idParts[0]);
376 public override Stream GetCachedHelpStream (string id)
378 var idParts = id.Split ('?');
379 return base.GetCachedHelpStream (idParts[0]);
382 public override DocumentType GetDocumentTypeForId (string id, out Dictionary<string, string> extraParams)
385 int interMark = id.LastIndexOf ('?');
387 extraParams = id.Substring (interMark)
390 var eqIdx = nvp.IndexOf ('=');
391 return new { Key = nvp.Substring (0, eqIdx < 0 ? nvp.Length : eqIdx), Value = nvp.Substring (eqIdx + 1) };
393 .ToDictionary (kvp => kvp.Key, kvp => kvp.Value );
394 return DocumentType.EcmaXml;
397 public override string GetPublicUrl (Node node)
399 string url = string.Empty;
400 var type = GetNodeType (node);
401 //Console.WriteLine ("GetPublicUrl {0} : {1} [{2}]", node.Element, node.Caption, type.ToString ());
403 case EcmaNodeType.Namespace:
404 return node.Element; // A namespace node has already a well formated internal url
405 case EcmaNodeType.Type:
406 return MakeTypeNodeUrl (node);
407 case EcmaNodeType.Meta:
408 return MakeTypeNodeUrl (GetNodeTypeParent (node)) + GenerateMetaSuffix (node);
409 case EcmaNodeType.Member:
410 var typeChar = GetNodeMemberTypeChar (node);
411 var parentNode = GetNodeTypeParent (node);
412 var typeNode = MakeTypeNodeUrl (parentNode).Substring (2);
413 return typeChar + ":" + typeNode + MakeMemberNodeUrl (typeChar, node);
419 string MakeTypeNodeUrl (Node node)
421 // A Type node has a Element property of the form: 'ecma:{number}#{typename}/'
422 var hashIndex = node.Element.IndexOf ('#');
423 var typeName = node.Element.Substring (hashIndex + 1, node.Element.Length - hashIndex - 2);
424 return "T:" + node.Parent.Caption + '.' + typeName.Replace ('.', '+');
427 string MakeMemberNodeUrl (char typeChar, Node node)
429 // We clean inner type ctor name which may contain the outer type name
430 var caption = node.Caption;
432 // Sanitize constructor caption of inner types
433 if (typeChar == 'C') {
435 for (int i = 0; i < caption.Length && caption[i] != '('; i++)
436 lastDot = caption[i] == '.' ? i : lastDot;
437 return lastDot == -1 ? '.' + caption : caption.Substring (lastDot);
440 /* We handle type conversion operator by checking if the name contains " to "
441 * (as in 'foo to bar') and we generate a corresponding conversion signature
443 if (typeChar == 'O' && caption.IndexOf (" to ") != -1) {
444 var parts = caption.Split (' ');
445 return "." + node.Parent.Caption + "(" + parts[0] + ", " + parts[2] + ")";
448 /* The goal here is to treat method which are explicit interface definition
449 * such as 'void IDisposable.Dispose ()' for which the caption is a dot
450 * expression thus colliding with the ecma parser.
451 * If the first non-alpha character in the caption is a dot then we have an
452 * explicit member implementation (we assume the interface has namespace)
454 var firstNonAlpha = caption.FirstOrDefault (c => !char.IsLetterOrDigit (c));
455 if (firstNonAlpha == '.')
456 return "$" + caption;
458 return "." + caption;
461 EcmaNodeType GetNodeType (Node node)
463 // We guess the node type by checking the depth level it's at in the tree
464 int level = GetNodeLevel (node);
467 return EcmaNodeType.Namespace;
469 return EcmaNodeType.Type;
471 return EcmaNodeType.Meta;
472 case 3: // Here it's either a member or, in case of overload, a meta
473 return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
474 case 4: // At this level, everything is necessarily a member
475 return EcmaNodeType.Member;
477 return EcmaNodeType.Invalid;
481 int GetNodeLevel (Node node)
484 for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++) {
485 //Console.WriteLine ("\tLevel {0} : {1} {2}", i, node.Element, node.Caption);
491 char GetNodeMemberTypeChar (Node node)
493 int level = GetNodeLevel (node);
494 // We try to reach the member group node depending on node nested level
497 return node.Element[0];
499 return node.Parent.Element[0];
501 return node.Parent.Parent.Element[0];
503 throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
507 Node GetNodeTypeParent (Node node)
509 // Type nodes are always at level 2 so we just need to get there
510 while (node != null && node.Parent != null && !node.Parent.Parent.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase))
515 string GenerateMetaSuffix (Node node)
517 string suffix = string.Empty;
518 // A meta node has always a type element to begin with
519 while (GetNodeType (node) != EcmaNodeType.Type) {
520 suffix = '/' + node.Element + suffix;
526 public override string GetInternalIdForUrl (string url, out Node node)
528 var id = string.Empty;
531 if (!url.StartsWith (EcmaPrefix, StringComparison.OrdinalIgnoreCase)) {
532 node = MatchNode (url);
535 id = node.GetInternalUrl ();
539 id = GetInternalIdForInternalUrl (id, out hash);
541 return id + GetArgs (hash, node);
544 public string GetInternalIdForInternalUrl (string internalUrl, out string hash)
546 var id = internalUrl;
547 if (id.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase))
548 id = id.Substring (UriPrefix.Length);
549 else if (id.StartsWith ("N:", StringComparison.OrdinalIgnoreCase))
550 id = "xml.summary." + id.Substring ("N:".Length);
552 var hashIndex = id.IndexOf ('#');
554 if (hashIndex != -1) {
555 hash = id.Substring (hashIndex + 1);
556 id = id.Substring (0, hashIndex);
562 public override Node MatchNode (string url)
565 if ((node = cache.Get (url)) == null) {
566 node = InternalMatchNode (url);
568 cache.Put (url, node);
573 public Node InternalMatchNode (string url)
577 if (!parser.TryParse (url, out desc))
581 Node currentNode = Tree.RootNode;
582 Node searchNode = new Node () { Caption = desc.Namespace };
583 int index = currentNode.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
585 result = currentNode.Nodes[index];
586 if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
590 currentNode = result;
592 searchNode.Caption = desc.ToCompleteTypeName ();
593 index = currentNode.Nodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
595 result = currentNode.Nodes[index];
596 if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
600 currentNode = result;
602 var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
603 currentNode = FindNodeForCaption (currentNode.Nodes, caption);
604 if (currentNode == null
605 || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
610 var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
611 searchNode.Caption = desc.ToCompleteMemberName (format);
612 index = currentNode.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
615 result = currentNode.Nodes[index];
616 if (result.Nodes.Count == 0 || desc.IsEtc)
620 currentNode = result;
621 searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
622 index = currentNode.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
625 result = result.Nodes[index];
630 // This comparer returns the answer straight from caption comparison
631 class EcmaGenericNodeComparer : IComparer<Node>
633 public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
635 public int Compare (Node n1, Node n2)
637 return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
641 // This comparer take into account the space in the caption
642 class EcmaTypeNodeComparer : IComparer<Node>
644 public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
646 public int Compare (Node n1, Node n2)
648 int length1 = CaptionLength (n1.Caption);
649 int length2 = CaptionLength (n2.Caption);
651 return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
654 int CaptionLength (string caption)
656 var length = caption.LastIndexOf (' ');
657 return length == -1 ? caption.Length : length;
661 string EtcKindToCaption (char etc)
669 return "Constructors";
683 string MemberKindToCaption (EcmaDesc.Kind kind)
686 case EcmaDesc.Kind.Method:
688 case EcmaDesc.Kind.Property:
690 case EcmaDesc.Kind.Constructor:
691 return "Constructors";
692 case EcmaDesc.Kind.Field:
694 case EcmaDesc.Kind.Event:
696 case EcmaDesc.Kind.Operator:
703 Node FindNodeForCaption (List<Node> nodes, string caption)
705 foreach (var node in nodes)
706 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
711 string GetArgs (string hash, Node node)
713 var args = new Dictionary<string, string> ();
715 args["source-id"] = SourceID.ToString ();
718 var nodeType = GetNodeType (node);
720 case EcmaNodeType.Namespace:
721 args["show"] = "namespace";
722 args["namespace"] = node.Element.Substring ("N:".Length);
724 case EcmaNodeType.Type:
725 args["show"] = "typeoverview";
727 case EcmaNodeType.Member:
728 case EcmaNodeType.Meta:
729 switch (GetNodeMemberTypeChar (node)){
731 args["membertype"] = "Constructor";
734 args["membertype"] = "Method";
737 args["membertype"] = "Property";
740 args["membertype"] = "Field";
743 args["membertype"] = "Event";
746 args["membertype"] = "Operator";
749 args["membertype"] = "ExtensionMethod";
752 args["membertype"] = "All";
756 if (nodeType == EcmaNodeType.Meta) {
757 args["show"] = "members";
758 args["index"] = "all";
760 args["show"] = "member";
761 args["index"] = node.Element;
767 if (!string.IsNullOrEmpty (hash))
770 return "?" + string.Join ("&", args.Select (kvp => kvp.Key == kvp.Value ? kvp.Key : kvp.Key + '=' + kvp.Value));
773 public override void PopulateSearchableIndex (IndexWriter writer)
775 StringBuilder text = new StringBuilder ();
776 SearchableDocument searchDoc = new SearchableDocument ();
778 foreach (Node ns_node in Tree.RootNode.Nodes) {
779 foreach (Node type_node in ns_node.Nodes) {
780 string typename = type_node.Caption.Substring (0, type_node.Caption.IndexOf (' '));
781 string full = ns_node.Caption + "." + typename;
782 string doc_tag = GetKindFromCaption (type_node.Caption);
783 string url = "T:" + full;
785 var id = GetInternalIdForInternalUrl (type_node.GetInternalUrl (), out hash);
786 var xdoc = XDocument.Load (GetHelpStream (id));
789 if (string.IsNullOrEmpty (doc_tag)) {
790 Console.WriteLine (type_node.Caption);
794 // For classes, structures or interfaces add a doc for the overview and
795 // add a doc for every constructor, method, event, ...
796 // doc_tag == "Class" || doc_tag == "Structure" || doc_tag == "Interface"
797 if (doc_tag[0] == 'C' || doc_tag[0] == 'S' || doc_tag[0] == 'I') {
798 // Adds a doc for every overview of every type
799 SearchableDocument doc = searchDoc.Reset ();
800 doc.Title = type_node.Caption;
801 doc.HotText = typename;
803 doc.FullTitle = full;
805 var node_sel = xdoc.Root.Element ("Docs");
807 GetTextFromNode (node_sel, text);
808 doc.Text = text.ToString ();
811 GetExamples (node_sel, text);
812 doc.Examples = text.ToString ();
814 writer.AddDocument (doc.LuceneDoc);
815 var exportParsable = doc_tag[0] == 'C' && (ns_node.Caption.StartsWith ("MonoTouch") || ns_node.Caption.StartsWith ("MonoMac"));
817 //Add docs for contructors, methods, etc.
818 foreach (Node c in type_node.Nodes) { // c = Constructors || Fields || Events || Properties || Methods || Operators
819 if (c.Element == "*")
821 const float innerTypeBoost = 0.2f;
823 IEnumerable<Node> ncnodes = c.Nodes;
824 // The rationale is that we need to properly handle method overloads
825 // so for those method node which have children, flatten them
826 if (c.Caption == "Methods") {
828 .Where (n => n.Nodes == null || n.Nodes.Count == 0)
829 .Concat (ncnodes.Where (n => n.Nodes.Count > 0).SelectMany (n => n.Nodes));
830 } else if (c.Caption == "Operators") {
832 .Where (n => !n.Caption.EndsWith ("Conversion"))
833 .Concat (ncnodes.Where (n => n.Caption.EndsWith ("Conversion")).SelectMany (n => n.Nodes));
836 foreach (Node nc in ncnodes) {
837 var docsNode = GetDocsFromCaption (xdoc, c.Caption[0] == 'C' ? ".ctor" : nc.Caption, c.Caption[0] == 'O');
839 SearchableDocument doc_nod = searchDoc.Reset ();
840 doc_nod.Title = LargeName (nc) + " " + EtcKindToCaption (c.Caption[0]);
841 doc_nod.FullTitle = ns_node.Caption + '.' + typename + "::" + nc.Caption;
842 doc_nod.HotText = string.Empty;
844 /* Disable constructors hottext indexing as it's often "polluting" search queries
845 because it has the same hottext than standard types */
846 if (c.Caption != "Constructors") {
847 //dont add the parameters to the hottext
848 int ppos = nc.Caption.IndexOf ('(');
849 doc_nod.HotText = ppos != -1 ? nc.Caption.Substring (0, ppos) : nc.Caption;
852 var urlnc = nc.PublicUrl;
855 if (docsNode == null) {
856 Console.Error.WriteLine ("Problem: {0}", urlnc);
861 GetTextFromNode (docsNode, text);
862 doc_nod.Text = text.ToString ();
865 GetExamples (docsNode, text);
866 doc_nod.Examples = text.ToString ();
868 Document lucene_doc = doc_nod.LuceneDoc;
869 lucene_doc.Boost = innerTypeBoost;
870 writer.AddDocument (lucene_doc);
872 // MonoTouch/Monomac specific parsing of [Export] attributes
873 /*if (exportParsable) {
876 xdoc.SelectNodes (string.Format ("/Type/Members/Member[@MemberName='{0}']/Attributes/Attribute/AttributeName[contains(text(), 'Foundation.Export')]", nc.Caption));
877 foreach (XmlNode exportNode in exports) {
878 var inner = exportNode.InnerText;
879 var parts = inner.Split ('"');
880 if (parts.Length != 3) {
881 Console.WriteLine ("Export attribute not found or not usable in {0}", inner);
885 var export = parts[1];
886 var export_node = new SearchableDocument ();
887 export_node.Title = export + " Export";
888 export_node.FullTitle = string.Format ("{0}.{1}::{2}", ns_node.Caption, typename, export);
889 export_node.Url = urlnc;
890 export_node.HotText = export + ":";
891 export_node.Text = string.Empty;
892 export_node.Examples = string.Empty;
893 lucene_doc = export_node.LuceneDoc;
894 lucene_doc.SetBoost (innerTypeBoost);
895 writer.AddDocument (lucene_doc);
897 } catch (Exception e){
898 Console.WriteLine ("Problem processing {0} for MonoTouch/MonoMac exports\n\n{0}", e);
903 // doc_tag == "Enumeration"
904 } else if (doc_tag[0] == 'E'){
905 var members = xdoc.Root.Element ("Members").Elements ("Member");
910 foreach (var member_node in members) {
911 string enum_value = (string)member_node.Attribute ("MemberName");
912 text.Append (enum_value);
914 GetTextFromNode (member_node.Element ("Docs"), text);
918 SearchableDocument doc = searchDoc.Reset ();
921 GetExamples (xdoc.Root.Element ("Docs"), text);
922 doc.Examples = text.ToString ();
924 doc.Title = type_node.Caption;
925 doc.HotText = (string)xdoc.Root.Attribute ("Name");
926 doc.FullTitle = full;
928 doc.Text = text.ToString();
929 writer.AddDocument (doc.LuceneDoc);
930 // doc_tag == "Delegate"
931 } else if (doc_tag[0] == 'D'){
932 SearchableDocument doc = searchDoc.Reset ();
933 doc.Title = type_node.Caption;
934 doc.HotText = (string)xdoc.Root.Attribute ("Name");
935 doc.FullTitle = full;
938 var node_sel = xdoc.Root.Element ("Docs");
941 GetTextFromNode (node_sel, text);
942 doc.Text = text.ToString();
945 GetExamples (node_sel, text);
946 doc.Examples = text.ToString();
948 writer.AddDocument (doc.LuceneDoc);
954 string GetKindFromCaption (string s)
956 int p = s.LastIndexOf (' ');
958 return s.Substring (p + 1);
962 // Extract the interesting text from the docs node
963 void GetTextFromNode (XElement n, StringBuilder sb)
965 // Include the text content of the docs
966 sb.AppendLine (n.Value);
967 foreach (var tag in n.Descendants ())
968 //include the url to which points the see tag and the name of the parameter
969 if ((tag.Name.LocalName.Equals ("see", StringComparison.Ordinal) || tag.Name.LocalName.Equals ("paramref", StringComparison.Ordinal))
970 && tag.HasAttributes)
971 sb.AppendLine ((string)tag.Attributes ().First ());
974 // Extract the code nodes from the docs
975 void GetExamples (XElement n, StringBuilder sb)
977 foreach (var code in n.Descendants ("code"))
978 sb.Append ((string)code);
981 // Extract a large name for the Node
982 static string LargeName (Node matched_node)
984 string[] parts = matched_node.GetInternalUrl ().Split('/', '#');
985 if (parts.Length == 3 && parts[2] != String.Empty) //List of Members, properties, events, ...
986 return parts[1] + ": " + matched_node.Caption;
987 else if(parts.Length >= 4) //Showing a concrete Member, property, ...
988 return parts[1] + "." + matched_node.Caption;
990 return matched_node.Caption;
993 XElement GetDocsFromCaption (XDocument xdoc, string caption, bool isOperator)
997 var doc = xdoc.Root.Element ("Members").Elements ("Member");
1000 // The first case are explicit and implicit conversion operators which are grouped specifically
1001 if (caption.IndexOf (" to ") != -1) {
1002 var convArgs = caption.Split (new[] { " to " }, StringSplitOptions.None);
1004 .First (n => (AttrEq (n, "MemberName", "op_Explicit") || AttrEq (n, "MemberName", "op_Implicit"))
1005 && ((string)n.Element ("ReturnValue").Element ("ReturnType")).Equals (convArgs[1], StringComparison.Ordinal)
1006 && AttrEq (n.Element ("Parameters").Element ("Parameter"), "Type", convArgs[0]))
1009 return doc.First (m => AttrEq (m, "MemberName", "op_" + caption)).Element ("Docs");
1013 TryParseCaption (caption, out name, out args);
1015 if (!string.IsNullOrEmpty (name)) // Filter member by name
1016 doc = doc.Where (m => AttrEq (m, "MemberName", name));
1017 if (args != null && args.Count > 0) // Filter member by its argument list
1018 doc = doc.Where (m => m.Element ("Parameters").Elements ("Parameter").Attributes ("Type").Select (a => (string)a).SequenceEqual (args));
1020 return doc.First ().Element ("Docs");
1023 // A simple stack-based parser to detect single type definition separated by commas
1024 IEnumerable<string> ExtractArguments (string rawArgList)
1026 var sb = new System.Text.StringBuilder ();
1027 int genericDepth = 0;
1030 for (int i = 0; i < rawArgList.Length; i++) {
1031 char c = rawArgList[i];
1034 if (genericDepth == 0 && arrayDepth == 0) {
1035 yield return sb.ToString ();
1056 yield return sb.ToString ();
1059 void TryParseCaption (string caption, out string name, out IList<string> argList)
1063 int parenIdx = caption.IndexOf ('(');
1064 // In case of simple name, there is no need for processing
1065 if (parenIdx == -1) {
1069 name = caption.Substring (0, parenIdx);
1070 // Now we gather the argument list if there is any
1071 var rawArgList = caption.Substring (parenIdx + 1, caption.Length - parenIdx - 2); // Only take what's inside the parens
1072 if (string.IsNullOrEmpty (rawArgList))
1075 argList = ExtractArguments (rawArgList).Select (arg => arg.Trim ()).ToList ();
1078 bool AttrEq (XElement element, string attributeName, string expectedValue)
1080 return ((string)element.Attribute (attributeName)).Equals (expectedValue, StringComparison.Ordinal);