2f4cd08f11af3ee4e39b045793f3f57abc3366dd
[mono.git] / mcs / class / monodoc / Mono.Documentation / XmlDocUtils.cs
1 using System;
2 using System.Collections;
3 using System.IO;
4 using System.Text;
5 using System.Text.RegularExpressions;
6 using System.Web;
7 using System.Xml;
8
9 namespace Mono.Documentation {
10
11         public delegate XmlDocument DocLoader (string escapedTypeName);
12
13         public static class XmlDocUtils
14         {
15                 public static XmlNodeList GetMemberGenericParameters (XmlNode member)
16                 {
17                         return member.SelectNodes ("Docs/typeparam");
18                 }
19
20                 public static XmlNodeList GetTypeGenericParameters (XmlNode member)
21                 {
22                         return member.SelectNodes ("/Type/TypeParameters/TypeParameter");
23                 }
24
25                 public static string ToTypeName (string type, XmlNode member)
26                 {
27                         return ToTypeName (type, GetTypeGenericParameters (member), 
28                                         GetMemberGenericParameters (member));
29                 }
30
31                 public static string ToTypeName (string type, XmlNodeList typeGenParams, XmlNodeList memberGenParams)
32                 {
33                         type = type.Replace ("&", "@").Replace ("<", "{").Replace (">", "}");
34                         for (int i = 0; i < typeGenParams.Count; ++i) {
35                                 string name = typeGenParams [i].InnerText;
36                                 type = Regex.Replace (type, @"\b" + name + @"\b", "`" + i);
37                         }
38                         for (int i = 0; i < memberGenParams.Count; ++i) {
39                                 string name = memberGenParams [i].Attributes ["name"].Value;
40                                 type = Regex.Replace (type, @"\b" + name + @"\b", "``" + i);
41                         }
42                         return type;
43                 }
44
45                 public static string ToEscapedTypeName (string name)
46                 {
47                         return GetCountedName (name, "`");
48                 }
49
50                 private static string GetCountedName (string name, string escape)
51                 {
52                         int lt = name.IndexOf ("<");
53                         if (lt == -1)
54                                 return name;
55                         StringBuilder type = new StringBuilder (name.Length);
56                         int start = 0;
57                         do {
58                                 type.Append (name.Substring (start, lt - start));
59                                 type.Append (escape);
60                                 type.Append (GetGenericCount (name, lt, out start));
61                         } while ((lt = name.IndexOf ('<', start)) >= 0);
62                         if (start < name.Length)
63                                 type.Append (name.Substring (start));
64                         return type.ToString ().Replace ("+", ".");
65                 }
66
67                 private static int GetGenericCount (string name, int start, out int end)
68                 {
69                         int n = 1;
70                         bool r = true;
71                         int i = start;
72                         int depth = 1;
73                         for ( ++i; r && i < name.Length; ++i) {
74                                 switch (name [i]) {
75                                         case ',': if (depth == 1) ++n; break;
76                                         case '<': ++depth; break;
77                                         case '>': --depth; if (depth == 0) r = false; break;
78                                 }
79                         }
80                         end = i;
81                         return n;
82                 }
83
84                 public static string ToEscapedMemberName (string member)
85                 {
86                         // Explicitly implemented interface members contain '.'s in the member
87                         // name, e.g. System.Collections.Generic.IEnumerable<A>.GetEnumerator.
88                         // CSC does a s/\./#/g for these.
89                         member = member.Replace (".", "#");
90                         if (member [member.Length-1] == '>') {
91                                 int i = member.LastIndexOf ("<");
92                                 int ignore;
93                                 return member.Substring (0, i).Replace ("<", "{").Replace (">", "}") + 
94                                         "``" + GetGenericCount (member, i, out ignore);
95                         }
96                         return member.Replace ("<", "{").Replace (">", "}");
97                 }
98
99                 public static void AddExtensionMethods (XmlDocument typexml, ArrayList/*<XmlNode>*/ extensions, DocLoader loader)
100                 {
101                         // if no members (enum, delegate) don't add extensions
102                         XmlNode m = typexml.SelectSingleNode ("/Type/Members");
103                         if (m == null)
104                                 return;
105
106                         // static classes can't be targets:
107                         if (typexml.SelectSingleNode (
108                                                 "/Type/TypeSignature[@Language='C#']/@Value")
109                                         .Value.IndexOf (" static ") >= 0)
110                                 return;
111
112                         foreach (string s in GetSupportedTypes (typexml, loader)) {
113                                 foreach (XmlNode extension in extensions) {
114                                         bool add = false;
115                                         foreach (XmlNode target in extension.SelectNodes ("Targets/Target")) {
116                                                 if (target.Attributes ["Type"].Value == s) {
117                                                         add = true;
118                                                         break;
119                                                 }
120                                         }
121                                         if (!add) {
122                                                 continue;
123                                         }
124                                         foreach (XmlNode c in extension.SelectNodes ("Member")) {
125                                                 XmlNode cm = typexml.ImportNode (c, true);
126                                                 m.AppendChild (cm);
127                                         }
128                                 }
129                         }
130                 }
131
132                 private static IEnumerable GetSupportedTypes (XmlDocument type, DocLoader loader)
133                 {
134                         yield return "System.Object";
135                         yield return GetEscapedPath (type, "Type/@FullName");
136
137                         Hashtable h = new Hashtable ();
138                         GetInterfaces (h, type, loader);
139
140                         string s = GetEscapedPath (type, "Type/Base/BaseTypeName");
141                         if (s != null) {
142                                 yield return s;
143                                 XmlDocument d;
144                                 string p = s;
145                                 while (s != null && (d = loader (s)) != null) {
146                                         GetInterfaces (h, d, loader);
147                                         s = GetEscapedPath (d, "Type/Base/BaseTypeName");
148                                         if (p == s)
149                                                 break;
150                                         yield return s;
151                                 }
152                         }
153
154                         foreach (object o in h.Keys)
155                                 yield return o.ToString ();
156                 }
157
158                 private static string GetEscapedPath (XmlDocument d, string path)
159                 {
160                         XmlNode n = d.SelectSingleNode (path);
161                         if (n == null)
162                                 return null;
163                         return "T:" + ToEscapedTypeName (n.InnerText);
164                 }
165
166                 private static void GetInterfaces (Hashtable ifaces, XmlDocument doc, DocLoader loader)
167                 {
168                         foreach (XmlNode n in doc.SelectNodes ("Type/Interfaces/Interface/InterfaceName")) {
169                                 string t = ToEscapedTypeName (n.InnerText);
170                                 string tk = "T:" + t;
171                                 if (!ifaces.ContainsKey (tk)) {
172                                         ifaces.Add (tk, null);
173                                         try {
174                                                 XmlDocument d = loader (t);
175                                                 if (d != null)
176                                                         GetInterfaces (ifaces, d, loader);
177                                         }
178                                         catch (FileNotFoundException e) {
179                                                 // ignore; interface documentation couldn't be found.
180                                         }
181                                 }
182                         }
183                 }
184
185                 // Turns e.g. sources/netdocs into sources/cache/netdocs
186                 public static string GetCacheDirectory (string assembledBase)
187                 {
188                         return Path.Combine (
189                                                 Path.Combine (Path.GetDirectoryName (assembledBase), "cache"),
190                                                 Path.GetFileName (assembledBase));
191                 }
192
193                 public static string GetCachedFileName (string cacheDir, string url)
194                 {
195                         return Path.Combine (cacheDir,
196                                              Uri.EscapeUriString (url).Replace ('/', '+').Replace ("*", "%2a"));
197                 }
198         }
199 }
200