* Monodoc/man-provider.cs: NEVER return a non-null string from
[mono.git] / mcs / tools / monodoc / Monodoc / man-provider.cs
1 //
2 // A provider to display man pages
3 //
4 // Authors:
5 //   Johannes Roith <johannes@roith.de>
6 //   Jonathan Pryor <jpryor@novell.com>
7 //
8 // (C) 2008 Novell, Inc.
9
10 namespace Monodoc { 
11 using System;
12 using System.Collections;
13 using System.Diagnostics;
14 using System.IO;
15 using System.Text;
16 using System.Xml;
17
18 //
19 // The simple provider generates the information source
20 //
21 public class ManProvider : Provider {
22         string[] tocFiles;
23         
24         public  ManProvider (string[] handbookTocFiles)
25         {
26                 tocFiles = handbookTocFiles;
27
28                 // huh...
29                 if (!File.Exists (tocFiles[0]))
30                         throw new FileNotFoundException (String.Format ("The table of contents, `{0}' does not exist", tocFiles[0]));
31         }
32
33         public override void PopulateTree (Tree tree)
34         {
35                 foreach(string TocFile in tocFiles) {
36
37                         XmlDocument doc = new XmlDocument();
38                         doc.Load(TocFile);
39
40                         XmlNodeList nodeList = doc.GetElementsByTagName("manpage");
41                         Node nodeToAddChildrenTo = tree;
42
43                         foreach(XmlNode node in nodeList) {
44
45                                 XmlAttribute name = node.Attributes["name"];
46                                 XmlAttribute page = node.Attributes["page"];
47
48                                 if (name == null || page == null) continue;
49
50                                 if (!File.Exists (page.Value))
51                                         continue;
52
53                                 string target = "man:" + name.Value;
54                                 nodeToAddChildrenTo.CreateNode (name.Value, target);
55
56                                 if (File.Exists(page.Value))
57                                         nodeToAddChildrenTo.tree.HelpSource.PackFile (page.Value, name.Value);
58                         }
59                 }
60         }
61
62
63         public override void CloseTree (HelpSource hs, Tree tree)
64         {
65         }
66 }
67
68 //
69 // The HelpSource is used during the rendering phase.
70 //
71
72 public class ManHelpSource : HelpSource {
73         
74         public ManHelpSource (string base_file, bool create) : base (base_file, create) {}
75         protected const string MAN_PREFIX = "man:";
76         
77         public override string GetText (string url, out Node match_node)
78         {
79                 match_node = null;
80
81                 string c = GetCachedText (url);
82                 if (c != null)
83                         return c;
84
85                 if (url.IndexOf (MAN_PREFIX) > -1)
86                         return GetTextFromUrl (url);
87                 if (url == "root:") {
88                         // display an index of sub-nodes.
89                         StringBuilder buf = new StringBuilder ();
90                         buf.Append ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
91                         buf.Append ("<p>Available man pages:</p>").Append ("<blockquote>");
92                         foreach (Node n in Tree.Nodes) {
93                                 buf.Append ("<a href=\"").Append (n.Element).Append ("\">")
94                                         .Append (n.Caption).Append ("</a><br/>");
95                         }
96                         buf.Append ("</blockquote>");
97                         return buf.ToString ();
98                 }
99
100                 return null;
101         }
102         
103         protected string GetTextFromUrl (string url)
104         {
105                 // Remove "man:" prefix including any help-source id on the front.
106                 int prefixStart = url.IndexOf(MAN_PREFIX);
107                 if (prefixStart > -1)
108                         url = url.Substring (prefixStart + 4);
109
110                 if (url == null || url.Length == 0)
111                 {
112                         Message (TraceLevel.Warning, "Warning, NULL url!");
113                         return null;
114                 }
115
116                 Stream stream = GetHelpStream (url);
117                 return GetTextFromStream (stream);
118         }
119
120         public static string GetTextFromStream (Stream stream)
121         {
122                 if (stream == null)
123                         return null;
124                 StreamReader file = new StreamReader(stream);
125
126                 string line;
127                 StateInfo s = new StateInfo ();
128
129                 while ((line = file.ReadLine ()) != null) {
130                         ProcessLine (line, s);
131                 }
132                 return s.output.ToString ();
133         }
134
135         enum ListState {
136                 None,
137                 Start,
138                 Title,
139         }
140
141         class StateInfo {
142                 public ListState ls;
143                 public Stack tags = new Stack ();
144                 public StringBuilder output = new StringBuilder ();
145         }
146
147         private static void ProcessLine (string line, StateInfo s)
148         {
149                 string[] parts = SplitLine (line);
150                 switch (parts [0]) {
151                         case ".\\\"": // comments
152                         case ".de":   // define macro
153                         case ".if":   // if
154                         case ".ne":   // ???
155                         case "..":    // end macro
156                                 // ignore
157                                 break;
158                         case ".I":
159                                 s.output.Append ("<i>");
160                                 Translate (parts, 1, s.output);
161                                 s.output.Append ("</i>");
162                                 break;
163                         case ".B":
164                                 s.output.Append ("<b>");
165                                 Translate (parts, 1, s.output);
166                                 s.output.Append ("</b>");
167                                 break;
168                         case ".br":
169                                 Translate (parts, 1, s.output);
170                                 s.output.Append ("<br />");
171                                 break;
172                         case ".nf":
173                                 Expect (s, "</p>");
174                                 s.output.Append ("<pre>\n");
175                                 s.tags.Push ("</pre>");
176                                 break;
177                         case ".fi":
178                                 Expect (s, "</pre>");
179                                 break;
180                         case ".PP":
181                                 Expect (s, "</p>", "</dd>", "</dl>");
182                                 goto case ".Sp";
183                         case ".Sp":
184                                 Expect (s, "</p>");
185                                 s.output.Append ("<p>");
186                                 Translate (parts, 1, s.output);
187                                 s.tags.Push ("</p>");
188                                 break;
189                         case ".RS":
190                                 Expect (s, "</p>");
191                                 s.output.Append ("<blockquote>");
192                                 s.tags.Push ("</blockquote>");
193                                 break;
194                         case ".RE":
195                                 ClearUntil (s, "</blockquote>");
196                                 break;
197                         case ".SH":
198                                 ClearAll (s);
199                                 s.output.Append ("<h2>");
200                                 Translate (parts, 1, s.output);
201                                 s.output.Append ("</h2>")
202                                         .Append ("<blockquote>");
203                                 s.tags.Push ("</blockquote>");
204                                 break;
205                         case ".SS":
206                                 s.output.Append ("<h3>");
207                                 Translate (parts, 1, s.output);
208                                 s.output.Append ("</h3>");
209                                 break;
210                         case ".TH": {
211                                 ClearAll (s);
212                                 string name = "", extra = "";
213                                 if (parts.Length >= 4 && parts [2].Trim ().Length == 0) {
214                                         name = parts [1] + "(" + parts [3] + ")";
215                                         if (parts.Length > 4) {
216                                                 int start = 4;
217                                                 if (parts [start].Trim ().Length == 0)
218                                                         ++start;
219                                                 extra = string.Join ("", parts, start, parts.Length-start);
220                                         }
221                                 }
222                                 else
223                                         name = string.Join ("", parts, 1, parts.Length-1);
224                                 s.output.Append ("<table width=\"100%\" bgcolor=\"#b0c4da\">" + 
225                                                 "<tr colspan=\"2\"><td>Manual Pages</td></tr>\n" +
226                                                 "<tr><td><h3>");
227                                 Translate (name, s.output);
228                                 s.output.Append ("</h3></td><td align=\"right\">");
229                                 Translate (extra, s.output);
230                                 s.output.Append ("</td></tr></table>");
231                                 break;
232                         }
233                         case ".TP":
234                                 Expect (s, "</p>");
235                                 if (s.tags.Count > 0 && s.tags.Peek ().ToString () != "</dd>") {
236                                         s.output.Append ("<dl>");
237                                         s.tags.Push ("</dl>");
238                                 }
239                                 else
240                                         Expect (s, "</dd>");
241                                 s.output.Append ("<dt>");
242                                 s.tags.Push ("</dt>");
243                                 s.ls = ListState.Start;
244                                 break;
245                         default:
246                                 Translate (line, s.output);
247                                 break;
248                 }
249                 if (s.ls == ListState.Start)
250                         s.ls = ListState.Title;
251                 else if (s.ls == ListState.Title) {
252                         Expect (s, "</dt>");
253                         s.output.Append ("<dd>");
254                         s.tags.Push ("</dd>");
255                         s.ls = ListState.None;
256                 }
257                 s.output.Append ("\n");
258         }
259
260         private static string[] SplitLine (string line)
261         {
262                 if (line.Length > 1 && line [0] != '.')
263                         return new string[]{null, line};
264
265                 int i;
266                 for (i = 0; i < line.Length; ++i) {
267                         if (char.IsWhiteSpace (line, i))
268                                 break;
269                 }
270
271                 if (i == line.Length)
272                         return new string[]{line};
273
274                 ArrayList pieces = new ArrayList ();
275                 pieces.Add (line.Substring (0, i));
276                 bool inQuotes = false;
277                 bool prevWs   = true;
278                 ++i;
279                 int start = i;
280                 for ( ; i < line.Length; ++i) {
281                         char c = line [i];
282                         if (inQuotes) {
283                                 if (c == '"') {
284                                         Add (pieces, line, start, i);
285                                         start = i+1;
286                                         inQuotes = false;
287                                 }
288                         }
289                         else {
290                                 if (prevWs && c == '"') {
291                                         Add (pieces, line, start, i);
292                                         start = i+1;
293                                         inQuotes = true;
294                                 }
295                                 else if (char.IsWhiteSpace (c)) {
296                                         if (!prevWs) {
297                                                 Add (pieces, line, start, i);
298                                                 start = i;
299                                         }
300                                         prevWs = true;
301                                 }
302                                 else {
303                                         if (prevWs) {
304                                                 Add (pieces, line, start, i);
305                                                 start = i;
306                                         }
307                                         prevWs = false;
308                                 }
309                         }
310                 }
311                 if (start > 0 && start != line.Length)
312                         pieces.Add (line.Substring (start, line.Length-start));
313                 return (string[]) pieces.ToArray (typeof(string));
314         }
315
316         private static void Add (ArrayList pieces, string line, int start, int end)
317         {
318                 if (start == end)
319                         return;
320                 pieces.Add (line.Substring (start, end-start));
321         }
322
323         private static void Expect (StateInfo s, params string[] expected)
324         {
325                 string e;
326                 while (s.tags.Count > 0 && 
327                                 Array.IndexOf (expected, (e = s.tags.Peek ().ToString ())) >= 0) {
328                         s.output.Append (s.tags.Pop ().ToString ());
329                 }
330         }
331
332         private static void ClearUntil (StateInfo s, string required)
333         {
334                 string e;
335                 while (s.tags.Count > 0 && 
336                                 (e = s.tags.Peek ().ToString ()) != required) {
337                         s.output.Append (s.tags.Pop ().ToString ());
338                 }
339                 if (e == required)
340                         s.output.Append (s.tags.Pop ().ToString ());
341         }
342
343         private static void ClearAll (StateInfo s)
344         {
345                 while (s.tags.Count > 0)
346                         s.output.Append (s.tags.Pop ().ToString ());
347         }
348
349         private static void Translate (string[] lines, int startIndex, StringBuilder output)
350         {
351                 if (lines.Length <= startIndex)
352                         return;
353                 do {
354                         Translate (lines [startIndex++], output);
355                         if (startIndex == lines.Length)
356                                 break;
357                 } while (startIndex < lines.Length);
358         }
359
360         private static void Translate (string line, StringBuilder output)
361         {
362                 string span = null;
363                 int start = output.Length;
364                 for (int i = 0; i < line.Length; ++i) {
365                         switch (line [i]) {
366                                 case '\\': {
367                                         if ((i+2) < line.Length && line [i+1] == 'f') {
368                                                 if (line [i+2] == 'I') {
369                                                         output.Append ("<i>");
370                                                         span = "</i>";
371                                                 }
372                                                 else if (line [i+2] == 'B') {
373                                                         output.Append ("<b>");
374                                                         span = "</b>";
375                                                 }
376                                                 else if (line [i+2] == 'R' || line [i+2] == 'P') {
377                                                         output.Append (span);
378                                                 }
379                                                 else
380                                                         goto default;
381                                                 i += 2;
382                                         }
383                                         else if ((i+1) < line.Length) {
384                                                 output.Append (line [i+1]);
385                                                 ++i;
386                                         }
387                                         else
388                                                 goto default;
389                                         break;
390                                 }
391                                 case '<':
392                                         output.Append ("&lt;");
393                                         break;
394                                 case '>':
395                                         output.Append ("&gt;");
396                                         break;
397                                 case '&':
398                                         output.Append ("&amp;");
399                                         break;
400                                 default:
401                                         output.Append (line [i]);
402                                         break;
403                         }
404                 }
405         }
406 }
407
408 }