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 Monodoc.Providers
28 public interface IEcmaProviderFileSource {
29 XmlReader GetIndexReader(string path);
30 XDocument GetTypeDocument(string path);
31 XElement GetNamespaceElement(string path);
32 string GetTypeXmlPath(string basePath, string nsName, string typeName);
33 string GetNamespaceXmlPath(string basePath, string ns);
34 XElement ExtractNamespaceSummary (string path);
37 internal class DefaultEcmaProviderFileSource : IEcmaProviderFileSource {
38 public static readonly IEcmaProviderFileSource Default = new DefaultEcmaProviderFileSource();
40 public XmlReader GetIndexReader(string path) {
41 return XmlReader.Create (File.OpenRead (path));
44 public XElement GetNamespaceElement(string path) {
45 return XElement.Load (path);
48 public string GetTypeXmlPath(string basePath, string nsName, string typeName) {
49 string finalPath = Path.Combine (basePath, nsName, Path.ChangeExtension (typeName, ".xml"));
53 public XDocument GetTypeDocument(string path) {
54 return XDocument.Load (path);
57 public string GetNamespaceXmlPath(string basePath, string ns) {
58 string finalPath = Path.Combine(basePath, String.Format("ns-{0}.xml", ns));
62 public XElement ExtractNamespaceSummary (string path)
64 using (var reader = XmlReader.Create (path)) {
65 reader.ReadToFollowing ("Namespace");
66 var name = reader.GetAttribute ("Name");
67 var summary = reader.ReadToFollowing ("summary") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("summary");
68 var remarks = reader.ReadToFollowing ("remarks") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("remarks");
70 return new XElement ("namespace",
71 new XAttribute ("ns", name ?? string.Empty),
78 public class EcmaProvider : Provider
80 HashSet<string> directories = new HashSet<string> ();
81 IEcmaProviderFileSource fileSource;
83 public EcmaProvider ()
87 public EcmaProvider (string baseDir)
89 AddDirectory (baseDir);
92 public IEcmaProviderFileSource FileSource {
94 if (fileSource == null) {
95 fileSource = new DefaultEcmaProviderFileSource();
99 set { fileSource = value; }
102 public void AddDirectory (string directory)
104 if (string.IsNullOrEmpty (directory))
105 throw new ArgumentNullException ("directory");
107 directories.Add (directory);
110 public override void PopulateTree (Tree tree)
112 var storage = tree.HelpSource.Storage;
113 var nsSummaries = new Dictionary<string, XElement> ();
116 foreach (var asm in directories) {
117 var indexFilePath = Path.Combine (asm, "index.xml");
118 if (!File.Exists (indexFilePath)) {
119 Console.Error.WriteLine ("Warning: couldn't process directory `{0}' as it has no index.xml file", asm);
123 EcmaDoc.PopulateTreeFromIndexFile (indexFilePath, EcmaHelpSource.EcmaPrefix, tree, storage, nsSummaries, _ => resID++.ToString (), FileSource);
126 foreach (var summary in nsSummaries)
127 storage.Store ("xml.summary." + summary.Key, summary.Value.ToString ());
129 var masterSummary = new XElement ("elements",
131 .SelectMany (d => Directory.EnumerateFiles (d, "ns-*.xml"))
132 .Select (FileSource.ExtractNamespaceSummary));
133 storage.Store ("mastersummary.xml", masterSummary.ToString ());
138 public override void CloseTree (HelpSource hs, Tree tree)
141 AddExtensionMethods (hs);
144 void AddEcmaXml (HelpSource hs)
146 var xmls = directories
147 .SelectMany (Directory.EnumerateDirectories) // Assemblies
148 .SelectMany (Directory.EnumerateDirectories) // Namespaces
149 .SelectMany (Directory.EnumerateFiles)
150 .Where (f => f.EndsWith (".xml")); // Type XML files
153 foreach (var xml in xmls)
154 using (var file = File.OpenRead (xml))
155 hs.Storage.Store ((resID++).ToString (), file);
158 void AddImages (HelpSource hs)
160 var imgs = directories
161 .SelectMany (Directory.EnumerateDirectories)
162 .Select (d => Path.Combine (d, "_images"))
163 .Where (Directory.Exists)
164 .SelectMany (Directory.EnumerateFiles);
166 foreach (var img in imgs)
167 using (var file = File.OpenRead (img))
168 hs.Storage.Store (Path.GetFileName (img), file);
171 void AddExtensionMethods (HelpSource hs)
173 var extensionMethods = directories
174 .SelectMany (Directory.EnumerateDirectories)
175 .Select (d => Path.Combine (d, "index.xml"))
178 using (var file = File.OpenRead (f)) {
179 var reader = XmlReader.Create (file);
180 reader.ReadToFollowing ("ExtensionMethods");
181 return reader.ReadInnerXml ();
184 .DefaultIfEmpty (string.Empty);
186 hs.Storage.Store ("ExtensionMethods.xml",
187 "<ExtensionMethods>" + extensionMethods.Aggregate (string.Concat) + "</ExtensionMethods>");
190 IEnumerable<string> GetEcmaXmls ()
193 .SelectMany (Directory.EnumerateDirectories) // Assemblies
194 .SelectMany (Directory.EnumerateDirectories) // Namespaces
195 .SelectMany (Directory.EnumerateFiles)
196 .Where (f => f.EndsWith (".xml")); // Type XML files
200 public class EcmaHelpSource : HelpSource
202 internal const string EcmaPrefix = "ecma:";
203 LRUCache<string, Node> cache = new LRUCache<string, Node> (4);
205 public EcmaHelpSource (string base_file, bool create) : base (base_file, create)
209 protected EcmaHelpSource () : base ()
213 protected override string UriPrefix {
219 public override bool CanHandleUrl (string url)
221 if (url.Length > 2 && url[1] == ':') {
234 return base.CanHandleUrl (url);
237 // Clean the extra paramers in the id
238 public override Stream GetHelpStream (string id)
240 var idParts = id.Split ('?');
241 var name = idParts[0];
243 name = "mastersummary.xml";
244 return base.GetHelpStream (name);
247 public override Stream GetCachedHelpStream (string id)
249 var idParts = id.Split ('?');
250 return base.GetCachedHelpStream (idParts[0]);
253 public override DocumentType GetDocumentTypeForId (string id)
255 return DocumentType.EcmaXml;
258 public override string GetPublicUrl (Node node)
260 string url = string.Empty;
261 var type = EcmaDoc.GetNodeType (node);
262 //Console.WriteLine ("GetPublicUrl {0} : {1} [{2}]", node.Element, node.Caption, type.ToString ());
264 case EcmaNodeType.Namespace:
265 return node.Element; // A namespace node has already a well formated internal url
266 case EcmaNodeType.Type:
267 return MakeTypeNodeUrl (node);
268 case EcmaNodeType.Meta:
269 return MakeTypeNodeUrl (GetNodeTypeParent (node)) + GenerateMetaSuffix (node);
270 case EcmaNodeType.Member:
271 var typeChar = EcmaDoc.GetNodeMemberTypeChar (node);
272 var parentNode = GetNodeTypeParent (node);
273 var typeNode = MakeTypeNodeUrl (parentNode).Substring (2);
274 return typeChar + ":" + typeNode + MakeMemberNodeUrl (typeChar, node);
280 string MakeTypeNodeUrl (Node node)
282 // A Type node has a Element property of the form: 'ecma:{number}#{typename}/'
283 var hashIndex = node.Element.IndexOf ('#');
284 var typeName = node.Element.Substring (hashIndex + 1, node.Element.Length - hashIndex - 2);
285 return "T:" + node.Parent.Caption + '.' + typeName.Replace ('.', '+');
288 string MakeMemberNodeUrl (char typeChar, Node node)
290 // We clean inner type ctor name which may contain the outer type name
291 var caption = node.Caption;
293 // Sanitize constructor caption of inner types
294 if (typeChar == 'C') {
296 for (int i = 0; i < caption.Length && caption[i] != '('; i++)
297 lastDot = caption[i] == '.' ? i : lastDot;
298 return lastDot == -1 ? '.' + caption : caption.Substring (lastDot);
301 /* We handle type conversion operator by checking if the name contains " to "
302 * (as in 'foo to bar') and we generate a corresponding conversion signature
304 if (typeChar == 'O' && caption.IndexOf (" to ") != -1) {
305 var parts = caption.Split (' ');
306 return "." + node.Parent.Caption + "(" + parts[0] + ", " + parts[2] + ")";
309 /* The goal here is to treat method which are explicit interface definition
310 * such as 'void IDisposable.Dispose ()' for which the caption is a dot
311 * expression thus colliding with the ecma parser.
312 * If the first non-alpha character in the caption is a dot then we have an
313 * explicit member implementation (we assume the interface has namespace)
315 var firstNonAlpha = caption.FirstOrDefault (c => !char.IsLetterOrDigit (c));
316 if (firstNonAlpha == '.')
317 return "$" + caption;
319 return "." + caption;
322 Node GetNodeTypeParent (Node node)
324 // Type nodes are always at level 2 so we just need to get there
325 while (node != null && node.Parent != null
326 && !node.Parent.Parent.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase) && node.Parent.Parent.Parent != null)
331 string GenerateMetaSuffix (Node node)
333 string suffix = string.Empty;
334 // A meta node has always a type element to begin with
335 while (EcmaDoc.GetNodeType (node) != EcmaNodeType.Type) {
336 suffix = '/' + node.Element + suffix;
342 public override string GetInternalIdForUrl (string url, out Node node, out Dictionary<string, string> context)
344 var id = string.Empty;
348 if (!url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase)) {
349 node = MatchNode (url);
352 id = node.GetInternalUrl ();
356 id = GetInternalIdForInternalUrl (id, out hash);
357 context = EcmaDoc.GetContextForEcmaNode (hash, SourceID.ToString (), node);
362 public string GetInternalIdForInternalUrl (string internalUrl, out string hash)
364 var id = internalUrl;
365 if (id.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase))
366 id = id.Substring (UriPrefix.Length);
367 else if (id.StartsWith ("N:", StringComparison.OrdinalIgnoreCase))
368 id = "xml.summary." + id.Substring ("N:".Length);
370 var hashIndex = id.IndexOf ('#');
372 if (hashIndex != -1) {
373 hash = id.Substring (hashIndex + 1);
374 id = id.Substring (0, hashIndex);
380 public override Node MatchNode (string url)
383 if ((node = cache.Get (url)) == null) {
384 node = EcmaDoc.MatchNodeWithEcmaUrl (url, Tree);
386 cache.Put (url, node);
391 public override void PopulateIndex (IndexMaker index_maker)
393 foreach (Node ns_node in Tree.RootNode.ChildNodes){
394 foreach (Node type_node in ns_node.ChildNodes){
395 string typename = type_node.Caption.Substring (0, type_node.Caption.IndexOf (' '));
396 string full = ns_node.Caption + "." + typename;
398 string doc_tag = GetKindFromCaption (type_node.Caption);
399 string url = type_node.PublicUrl;
402 // Add MonoMac/MonoTouch [Export] attributes, those live only in classes
404 XDocument type_doc = null;
405 ILookup<string, XElement> prematchedMembers = null;
406 bool hasExports = doc_tag == "Class" && (ns_node.Caption.StartsWith ("MonoTouch") || ns_node.Caption.StartsWith ("MonoMac"));
410 var id = GetInternalIdForInternalUrl (type_node.GetInternalUrl (), out hash);
411 type_doc = XDocument.Load (GetHelpStream (id));
412 prematchedMembers = type_doc.Root.Element ("Members").Elements ("Member").ToLookup (n => (string)n.Attribute ("MemberName"), n => n);
413 } catch (Exception e) {
414 Console.WriteLine ("Problem processing {0} for MonoTouch/MonoMac exports\n\n{0}", e);
419 if (doc_tag == "Class" || doc_tag == "Structure" || doc_tag == "Interface"){
420 index_maker.Add (type_node.Caption, typename, url);
421 index_maker.Add (full + " " + doc_tag, full, url);
423 foreach (Node c in type_node.ChildNodes){
426 index_maker.Add (" constructors", typename+"0", url + "/C");
429 index_maker.Add (" fields", typename+"1", url + "/F");
432 index_maker.Add (" events", typename+"2", url + "/E");
435 index_maker.Add (" properties", typename+"3", url + "/P");
438 index_maker.Add (" methods", typename+"4", url + "/M");
441 index_maker.Add (" operators", typename+"5", url + "/O");
447 // Now repeat, but use a different sort key, to make sure we come after
448 // the summary data above, start the counter at 6
450 string keybase = typename + "6.";
452 foreach (Node c in type_node.ChildNodes){
453 var type = c.Caption[0];
455 foreach (Node nc in c.ChildNodes) {
456 string res = nc.Caption;
457 string nurl = nc.PublicUrl;
460 if (hasExports && (type == 'C' || type == 'M' || type == 'P')) {
462 var member = GetMemberFromCaption (type_doc, type == 'C' ? ".ctor" : res, false, prematchedMembers);
463 var exports = member.Descendants ("AttributeName").Where (a => a.Value.Contains ("Foundation.Export"));
464 foreach (var exportNode in exports) {
465 var parts = exportNode.Value.Split ('"');
466 if (parts.Length != 3) {
467 Console.WriteLine ("Export attribute not found or not usable in {0}", exportNode);
469 var export = parts[1];
470 index_maker.Add (export + " selector", export, nurl);
473 } catch (Exception e) {
474 Console.WriteLine ("Problem processing {0}/{1} for MonoTouch/MonoMac exports\n\n{2}", nurl, res, e);
482 index_maker.Add (String.Format ("{0}.{1} field", typename, res),
483 keybase + res, nurl);
484 index_maker.Add (String.Format ("{0} field", res), res, nurl);
487 index_maker.Add (String.Format ("{0}.{1} event", typename, res),
488 keybase + res, nurl);
489 index_maker.Add (String.Format ("{0} event", res), res, nurl);
492 index_maker.Add (String.Format ("{0}.{1} property", typename, res),
493 keybase + res, nurl);
494 index_maker.Add (String.Format ("{0} property", res), res, nurl);
497 index_maker.Add (String.Format ("{0}.{1} method", typename, res),
498 keybase + res, nurl);
499 index_maker.Add (String.Format ("{0} method", res), res, nurl);
502 index_maker.Add (String.Format ("{0}.{1} operator", typename, res),
503 keybase + res, nurl);
508 } else if (doc_tag == "Enumeration"){
510 // Enumerations: add the enumeration values
512 index_maker.Add (type_node.Caption, typename, url);
513 index_maker.Add (full + " " + doc_tag, full, url);
515 // Now, pull the values.
517 var id = GetInternalIdForInternalUrl (type_node.GetInternalUrl (), out hash);
518 var xdoc = XDocument.Load (GetHelpStream (id));
522 var members = xdoc.Root.Element ("Members").Elements ("Members");
526 foreach (var member_node in members){
527 string enum_value = member_node.Attribute ("MemberName").Value;
528 string caption = enum_value + " value";
529 index_maker.Add (caption, caption, url);
531 } else if (doc_tag == "Delegate"){
532 index_maker.Add (type_node.Caption, typename, url);
533 index_maker.Add (full + " " + doc_tag, full, url);
540 public override void PopulateSearchableIndex (IndexWriter writer)
542 StringBuilder text = new StringBuilder ();
543 SearchableDocument searchDoc = new SearchableDocument ();
545 foreach (Node ns_node in Tree.RootNode.ChildNodes) {
546 foreach (Node type_node in ns_node.ChildNodes) {
547 string typename = type_node.Caption.Substring (0, type_node.Caption.IndexOf (' '));
548 string full = ns_node.Caption + "." + typename;
549 string url = type_node.PublicUrl;
550 string doc_tag = GetKindFromCaption (type_node.Caption);
552 var id = GetInternalIdForInternalUrl (type_node.GetInternalUrl (), out hash);
553 var xdoc = XDocument.Load (GetHelpStream (id));
556 if (string.IsNullOrEmpty (doc_tag))
559 // For classes, structures or interfaces add a doc for the overview and
560 // add a doc for every constructor, method, event, ...
561 // doc_tag == "Class" || doc_tag == "Structure" || doc_tag == "Interface"
562 if (doc_tag[0] == 'C' || doc_tag[0] == 'S' || doc_tag[0] == 'I') {
563 // Adds a doc for every overview of every type
564 SearchableDocument doc = searchDoc.Reset ();
565 doc.Title = type_node.Caption;
566 doc.HotText = typename;
568 doc.FullTitle = full;
570 var node_sel = xdoc.Root.Element ("Docs");
572 GetTextFromNode (node_sel, text);
573 doc.Text = text.ToString ();
576 GetExamples (node_sel, text);
577 doc.Examples = text.ToString ();
579 writer.AddDocument (doc.LuceneDoc);
580 var exportParsable = doc_tag[0] == 'C' && (ns_node.Caption.StartsWith ("MonoTouch") || ns_node.Caption.StartsWith ("MonoMac"));
582 //Add docs for contructors, methods, etc.
583 foreach (Node c in type_node.ChildNodes) { // c = Constructors || Fields || Events || Properties || Methods || Operators
584 if (c.Element == "*")
586 const float innerTypeBoost = 0.2f;
588 IEnumerable<Node> ncnodes = c.ChildNodes;
589 // The rationale is that we need to properly handle method overloads
590 // so for those method node which have children, flatten them
591 if (c.Caption == "Methods") {
593 .Where (n => n.ChildNodes == null || n.ChildNodes.Count == 0)
594 .Concat (ncnodes.Where (n => n.ChildNodes.Count > 0).SelectMany (n => n.ChildNodes));
595 } else if (c.Caption == "Operators") {
597 .Where (n => !n.Caption.EndsWith ("Conversion"))
598 .Concat (ncnodes.Where (n => n.Caption.EndsWith ("Conversion")).SelectMany (n => n.ChildNodes));
601 var prematchedMembers = xdoc.Root.Element ("Members").Elements ("Member").ToLookup (n => (string)n.Attribute ("MemberName"), n => n);
603 foreach (Node nc in ncnodes) {
604 XElement docsNode = null;
606 docsNode = GetDocsFromCaption (xdoc, c.Caption[0] == 'C' ? ".ctor" : nc.Caption, c.Caption[0] == 'O', prematchedMembers);
608 if (docsNode == null) {
609 Console.Error.WriteLine ("Problem: {0}", nc.PublicUrl);
613 SearchableDocument doc_nod = searchDoc.Reset ();
614 doc_nod.Title = LargeName (nc) + " " + EcmaDoc.EtcKindToCaption (c.Caption[0]);
615 doc_nod.FullTitle = ns_node.Caption + '.' + typename + "::" + nc.Caption;
616 doc_nod.HotText = string.Empty;
618 /* Disable constructors hottext indexing as it's often "polluting" search queries
619 because it has the same hottext than standard types */
620 if (c.Caption != "Constructors") {
621 //dont add the parameters to the hottext
622 int ppos = nc.Caption.IndexOf ('(');
623 doc_nod.HotText = ppos != -1 ? nc.Caption.Substring (0, ppos) : nc.Caption;
626 var urlnc = nc.PublicUrl;
630 GetTextFromNode (docsNode, text);
631 doc_nod.Text = text.ToString ();
634 GetExamples (docsNode, text);
635 doc_nod.Examples = text.ToString ();
637 Document lucene_doc = doc_nod.LuceneDoc;
638 lucene_doc.Boost = innerTypeBoost;
639 writer.AddDocument (lucene_doc);
641 // Objective-C binding specific parsing of [Export] attributes
642 if (exportParsable) {
644 var exports = docsNode.Parent.Elements ("Attributes").Elements ("Attribute").Elements ("AttributeName")
645 .Select (a => (string)a).Where (txt => txt.Contains ("Foundation.Export"));
647 foreach (var exportNode in exports) {
648 var parts = exportNode.Split ('"');
649 if (parts.Length != 3) {
650 Console.WriteLine ("Export attribute not found or not usable in {0}", exportNode);
654 var export = parts[1];
655 var export_node = searchDoc.Reset ();
656 export_node.Title = export + " Export";
657 export_node.FullTitle = ns_node.Caption + '.' + typename + "::" + export;
658 export_node.Url = urlnc;
659 export_node.HotText = export;
660 export_node.Text = string.Empty;
661 export_node.Examples = string.Empty;
662 lucene_doc = export_node.LuceneDoc;
663 lucene_doc.Boost = innerTypeBoost;
664 writer.AddDocument (lucene_doc);
666 } catch (Exception e){
667 Console.WriteLine ("Problem processing {0} for MonoTouch/MonoMac exports\n\n{0}", e);
672 // doc_tag == "Enumeration"
673 } else if (doc_tag[0] == 'E'){
674 var members = xdoc.Root.Element ("Members").Elements ("Member");
679 foreach (var member_node in members) {
680 string enum_value = (string)member_node.Attribute ("MemberName");
681 text.Append (enum_value);
683 GetTextFromNode (member_node.Element ("Docs"), text);
687 SearchableDocument doc = searchDoc.Reset ();
690 GetExamples (xdoc.Root.Element ("Docs"), text);
691 doc.Examples = text.ToString ();
693 doc.Title = type_node.Caption;
694 doc.HotText = (string)xdoc.Root.Attribute ("Name");
695 doc.FullTitle = full;
697 doc.Text = text.ToString();
698 writer.AddDocument (doc.LuceneDoc);
699 // doc_tag == "Delegate"
700 } else if (doc_tag[0] == 'D'){
701 SearchableDocument doc = searchDoc.Reset ();
702 doc.Title = type_node.Caption;
703 doc.HotText = (string)xdoc.Root.Attribute ("Name");
704 doc.FullTitle = full;
707 var node_sel = xdoc.Root.Element ("Docs");
710 GetTextFromNode (node_sel, text);
711 doc.Text = text.ToString();
714 GetExamples (node_sel, text);
715 doc.Examples = text.ToString();
717 writer.AddDocument (doc.LuceneDoc);
723 string GetKindFromCaption (string s)
725 int p = s.LastIndexOf (' ');
727 return s.Substring (p + 1);
731 // Extract the interesting text from the docs node
732 void GetTextFromNode (XElement n, StringBuilder sb)
734 // Include the text content of the docs
735 sb.AppendLine (n.Value);
736 foreach (var tag in n.Descendants ())
737 //include the url to which points the see tag and the name of the parameter
738 if ((tag.Name.LocalName.Equals ("see", StringComparison.Ordinal) || tag.Name.LocalName.Equals ("paramref", StringComparison.Ordinal))
739 && tag.HasAttributes)
740 sb.AppendLine ((string)tag.Attributes ().First ());
743 // Extract the code nodes from the docs
744 void GetExamples (XElement n, StringBuilder sb)
746 foreach (var code in n.Descendants ("code"))
747 sb.Append ((string)code);
750 // Extract a large name for the Node
751 static string LargeName (Node matched_node)
753 string[] parts = matched_node.GetInternalUrl ().Split('/', '#');
754 if (parts.Length == 3 && parts[2] != String.Empty) //List of Members, properties, events, ...
755 return parts[1] + ": " + matched_node.Caption;
756 else if(parts.Length >= 4) //Showing a concrete Member, property, ...
757 return parts[1] + "." + matched_node.Caption;
759 return matched_node.Caption;
762 XElement GetMemberFromCaption (XDocument xdoc, string caption, bool isOperator, ILookup<string, XElement> prematchedMembers)
766 var doc = xdoc.Root.Element ("Members").Elements ("Member");
769 // The first case are explicit and implicit conversion operators which are grouped specifically
770 if (caption.IndexOf (" to ") != -1) {
771 var convArgs = caption.Split (new[] { " to " }, StringSplitOptions.None);
773 .First (n => (AttrEq (n, "MemberName", "op_Explicit") || AttrEq (n, "MemberName", "op_Implicit"))
774 && ((string)n.Element ("ReturnValue").Element ("ReturnType")).Equals (convArgs[1], StringComparison.Ordinal)
775 && AttrEq (n.Element ("Parameters").Element ("Parameter"), "Type", convArgs[0]));
777 return doc.First (m => AttrEq (m, "MemberName", "op_" + caption));
781 TryParseCaption (caption, out name, out args);
783 if (!string.IsNullOrEmpty (name)) { // Filter member by name
784 var prematched = prematchedMembers[name];
785 doc = prematched.Any () ? prematched : doc.Where (m => AttrEq (m, "MemberName", name));
787 if (args != null && args.Count > 0) // Filter member by its argument list
788 doc = doc.Where (m => m.Element ("Parameters").Elements ("Parameter").Attributes ("Type").Select (a => (string)a).SequenceEqual (args));
793 XElement GetDocsFromCaption (XDocument xdoc, string caption, bool isOperator, ILookup<string, XElement> prematchedMembers)
795 return GetMemberFromCaption (xdoc, caption, isOperator, prematchedMembers).Element ("Docs");
798 // A simple stack-based parser to detect single type definition separated by commas
799 IEnumerable<string> ExtractArguments (string rawArgList)
801 var sb = new System.Text.StringBuilder ();
802 int genericDepth = 0;
805 for (int i = 0; i < rawArgList.Length; i++) {
806 char c = rawArgList[i];
809 if (genericDepth == 0 && arrayDepth == 0) {
810 yield return sb.ToString ();
831 yield return sb.ToString ();
834 void TryParseCaption (string caption, out string name, out IList<string> argList)
838 int parenIdx = caption.IndexOf ('(');
839 // In case of simple name, there is no need for processing
840 if (parenIdx == -1) {
844 name = caption.Substring (0, parenIdx);
845 // Now we gather the argument list if there is any
846 var rawArgList = caption.Substring (parenIdx + 1, caption.Length - parenIdx - 2); // Only take what's inside the parens
847 if (string.IsNullOrEmpty (rawArgList))
850 argList = ExtractArguments (rawArgList).Select (arg => arg.Trim ()).ToList ();
853 bool AttrEq (XElement element, string attributeName, string expectedValue)
855 return ((string)element.Attribute (attributeName)).Equals (expectedValue, StringComparison.Ordinal);