Mercurial > pub > bltoolkit
comparison Tools/DocGen/Generator.cs @ 0:f990fcb411a9
Копия текущей версии из github
author | cin |
---|---|
date | Thu, 27 Mar 2014 21:46:09 +0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:f990fcb411a9 |
---|---|
1 using System; | |
2 using System.Collections.Generic; | |
3 using System.Globalization; | |
4 using System.IO; | |
5 using System.Linq; | |
6 using System.Text.RegularExpressions; | |
7 using System.Xml; | |
8 | |
9 using Rsdn.Framework.Formatting; | |
10 | |
11 namespace DocGen | |
12 { | |
13 delegate FileAction FileActionHandler(string ext); | |
14 | |
15 partial class Generator | |
16 { | |
17 string _sourcePath; | |
18 string _destFolder; | |
19 FileActionHandler _fileAction; | |
20 | |
21 public void Generate( | |
22 FileItem createdFiles, | |
23 string templateFileName, | |
24 string[] path, | |
25 string destFolder, | |
26 string sourcePath, | |
27 bool cleanUp, | |
28 bool createIndex, | |
29 FileActionHandler fileAction) | |
30 { | |
31 _sourcePath = Path.GetFullPath(sourcePath); | |
32 _destFolder = Path.GetFullPath(destFolder); | |
33 _fileAction = fileAction; | |
34 | |
35 if (cleanUp) | |
36 CleanUp(); | |
37 | |
38 CreateDestFolder(); | |
39 | |
40 var template = File.ReadAllText(templateFileName); | |
41 | |
42 GenerateContent(createdFiles, template, path, createIndex); | |
43 } | |
44 | |
45 private void CreateDestFolder() | |
46 { | |
47 if (Directory.Exists(_destFolder) == false) | |
48 Directory.CreateDirectory(_destFolder); | |
49 } | |
50 | |
51 private void CleanUp() | |
52 { | |
53 if (Directory.Exists(_destFolder) == false) | |
54 return; | |
55 | |
56 Action<string> clean = null; clean = delegate(string path) | |
57 { | |
58 foreach (var file in Directory.GetFiles(path)) | |
59 if (Path.GetExtension(file).ToLower() != ".zip") | |
60 try { File.Delete(file); } catch {} | |
61 | |
62 foreach (var dir in Directory.GetDirectories(path)) | |
63 { | |
64 clean(dir); | |
65 try { Directory.Delete(dir); } catch {} | |
66 } | |
67 }; | |
68 | |
69 clean(_destFolder); | |
70 } | |
71 | |
72 static readonly Regex ct_item1 = new Regex(@"<ct_item\s*link\=(?<link>.*?)\s*label=['""](?<label>.*?)['""]\s*/>"); | |
73 static readonly Regex ct_item2 = new Regex(@"<ct_item\s*link\=(?<link>.*?)\s*label=['""](?<label>.*?)['""]\s*>(?<text>.*?)</ct_item>", RegexOptions.Singleline); | |
74 static readonly Regex ct_item3 = new Regex(@"<mt_item\s*link\=(?<link>.*?)\s*label=['""](?<label>.*?)['""]\s*>(?<text>.*?)</mt_item>", RegexOptions.Singleline); | |
75 static readonly Regex ct_item4 = new Regex(@"<ct_item2\s*link1\=(?<link1>.*?)\s*label1=['""](?<label1>.*?)['""]\s*link2\=(?<link2>.*?)\s*label2=['""](?<label2>.*?)['""]\s*/>", RegexOptions.Singleline); | |
76 static readonly Regex ct_item5 = new Regex(@"<ct_item3\s*link1\=(?<link1>.*?)\s*label1=['""](?<label1>.*?)['""]\s*link2\=(?<link2>.*?)\s*label2=['""](?<label2>.*?)['""]\s*link3\=(?<link3>.*?)\s*label3=['""](?<label3>.*?)['""]\s*/>", RegexOptions.Singleline); | |
77 | |
78 private bool GenerateContent( | |
79 FileItem createdFiles, string template, string[] path, bool createIndex) | |
80 { | |
81 var folder = string.Join("/", path); | |
82 var destFolder = Path.Combine(_destFolder, folder); | |
83 var backPath = ""; | |
84 | |
85 for (var i = 0; i < path.Length; i++) | |
86 backPath += "../"; | |
87 | |
88 var sourcePath = Path.Combine(_sourcePath, folder); | |
89 var sourceFiles = Directory.GetFiles(sourcePath); | |
90 | |
91 var files = new List<string>(); | |
92 var folders = new List<string>(); | |
93 | |
94 foreach (var fileName in sourceFiles) | |
95 { | |
96 var backLinks = GeneratePath(path, backPath, fileName); | |
97 | |
98 var destName = Path.Combine(destFolder, Path.GetFileName(fileName)); | |
99 var ext = Path.GetExtension(destName).ToLower(); | |
100 | |
101 Console.WriteLine(destName); | |
102 | |
103 switch (_fileAction(fileName)) | |
104 { | |
105 case FileAction.Skip: | |
106 break; | |
107 | |
108 case FileAction.Copy: | |
109 File.Copy(fileName, destName, true); | |
110 break; | |
111 | |
112 case FileAction.Process: | |
113 if (Directory.Exists(destFolder) == false) | |
114 Directory.CreateDirectory(destFolder); | |
115 | |
116 switch (ext) | |
117 { | |
118 case ".htm": | |
119 case ".html": | |
120 using (var sw = File.CreateText(destName)) | |
121 using (var sr = File.OpenText(fileName)) | |
122 { | |
123 var item = new FileItem { IsFile = true, Name = destName }; | |
124 createdFiles.Add(item); | |
125 | |
126 var source = sr.ReadToEnd(); | |
127 | |
128 source = source | |
129 .Replace("<ct_table>", "<table border='0' cellpadding='0' cellspacing='0'>") | |
130 .Replace("<ct_hr>", "<ct_mg><tr><td colspan='5' class='hr'><img width='1' height='1' alt=''/></td></tr><ct_mg>") | |
131 .Replace("<ct_text>", "<tr><td colspan='5'>") | |
132 .Replace("</ct_text>", "</td></tr><ct_mg>") | |
133 .Replace("<ct_mg>", "<tr><td colspan='5' class='sp'><img width='1' height='1' alt=''/></td></tr>") | |
134 .Replace("</ct_table>", "</table>") | |
135 ; | |
136 | |
137 source = ct_item1.Replace(source, @"<tr><td nowrap colspan='5'>• <a href=${link}>${label}</a></td></tr>"); | |
138 source = ct_item2.Replace(source, @"<tr><td nowrap>• <a href=${link}>${label}</a></td><td> </td><td class='j' colspan='3'>${text}</td></tr>"); | |
139 source = ct_item3.Replace(source, @"<tr><td nowrap class='p'>• <a href=${link}>${label}</a></td><td></td><td class='pj' colspan='3'>${text}</td></tr>"); | |
140 source = ct_item4.Replace(source, @"<tr><td nowrap>• <a href=${link1}>${label1}</a></td><td> </td><td nowrap colspan='3'>• <a href=${link2}>${label2}</a></td></tr>"); | |
141 source = ct_item5.Replace(source, @"<tr><td nowrap>• <a href=${link1}>${label1}</a></td><td> </td><td nowrap>• <a href=${link2}>${label2}</a></td><td> </td><td nowrap>• <a href=${link3}>${label3}</a></td></tr>"); | |
142 | |
143 if (_modifySourceLinks) | |
144 { | |
145 source = source | |
146 .Replace("href=\"..\\..\\..\\Source\\", "target=_blank href=\"/Source/") | |
147 .Replace("href='..\\..\\..\\Source\\", "target=_blank href='/Source/") | |
148 .Replace("<a href=\"http://", "<a target=_blank href=\"http://") | |
149 .Replace("<a href='http://", "<a target=_blank href='http://") | |
150 ; | |
151 } | |
152 | |
153 var title = item.Title; | |
154 | |
155 if (title == "index") | |
156 { | |
157 title = Path.GetFileName(Path.GetDirectoryName(fileName)); | |
158 | |
159 if (title != "content") | |
160 item.Title = title; | |
161 } | |
162 | |
163 source = GenerateSource(source, item); | |
164 title = item.Title; | |
165 | |
166 if (title.Length > 0 && _addDashToTitle) | |
167 title += " - "; | |
168 | |
169 sw.WriteLine(string.Format( | |
170 template, | |
171 source, | |
172 backPath, | |
173 backLinks, | |
174 title)); | |
175 | |
176 if (item.NoIndex == false) | |
177 { | |
178 source = source | |
179 .Replace("<span class='a'>", "") | |
180 .Replace("</span>", "") | |
181 .Replace("<", "<") | |
182 .Replace(">", ">") | |
183 ; | |
184 | |
185 foreach (var index in IndexItem.Index) | |
186 if (!item.NoIndexes.Contains(index.Name)) | |
187 foreach (var s in index.Text) | |
188 if (source.IndexOf(s) >= 0) | |
189 { | |
190 index.Files.Add(item); | |
191 break; | |
192 } | |
193 | |
194 foreach (var s in item.Indexes) | |
195 { | |
196 var index = IndexItem.Index.Find(i => i.Name == s); | |
197 | |
198 if (index == null) | |
199 IndexItem.Index.Add(new IndexItem(s)); | |
200 | |
201 if (index.Files.Contains(item) == false) | |
202 index.Files.Add(item); | |
203 } | |
204 } | |
205 } | |
206 | |
207 break; | |
208 | |
209 case ".cs": | |
210 using (var sw = File.CreateText(destName + ".htm")) | |
211 { | |
212 createdFiles.Add(new FileItem { IsFile = true, Name = destName + ".htm" }); | |
213 | |
214 var source = GenerateSource("<% " + fileName + " %>", null); | |
215 | |
216 sw.WriteLine(string.Format( | |
217 template, | |
218 source, | |
219 backPath, | |
220 backLinks, | |
221 Path.GetFileNameWithoutExtension(fileName) + " - ")); | |
222 } | |
223 break; | |
224 } | |
225 | |
226 files.Add(fileName); | |
227 | |
228 break; | |
229 } | |
230 } | |
231 | |
232 var dirs = Directory.GetDirectories(sourcePath); | |
233 var newPath = new string[path.Length + 1]; | |
234 | |
235 path.CopyTo(newPath, 0); | |
236 | |
237 foreach (var dir in dirs) | |
238 { | |
239 var dirList = dir.Split('/', '\\'); | |
240 var dirName = dirList[dirList.Length - 1]; | |
241 | |
242 // Skip Subversion folders. | |
243 // | |
244 if (dirName == "_svn" || dirName == ".svn") | |
245 continue; | |
246 | |
247 newPath[path.Length] = dirName; | |
248 | |
249 var item = new FileItem { IsFile = false, Name = dirName}; | |
250 | |
251 createdFiles.Add(item); | |
252 | |
253 if (GenerateContent(item, template, newPath, createIndex)) | |
254 folders.Add(dir); | |
255 } | |
256 | |
257 if (files.Count > 0 || folders.Count > 0) | |
258 { | |
259 var indexName = destFolder + "/index.htm"; | |
260 | |
261 if (createIndex && File.Exists(indexName) == false) | |
262 { | |
263 var str = ""; | |
264 | |
265 folders.Sort(); | |
266 | |
267 foreach (var s in folders) | |
268 str += string.Format("• <a href='{0}/index.htm'>{0}</a><br>\n", | |
269 Path.GetFileName(s)); | |
270 | |
271 if (str.Length > 0) | |
272 str += "<br>"; | |
273 | |
274 files.Sort(); | |
275 | |
276 foreach (var s in files) | |
277 str += string.Format( | |
278 s.EndsWith(".htm", true, CultureInfo.CurrentCulture) || | |
279 s.EndsWith(".html", true, CultureInfo.CurrentCulture)? | |
280 "• <a href='{0}'>{0}</a><br>": | |
281 "• <a href='{0}.htm'>{0}</a><br>\n", | |
282 Path.GetFileName(s)); | |
283 | |
284 _fileAction(indexName); | |
285 | |
286 using (var sw = File.CreateText(indexName)) | |
287 { | |
288 createdFiles.Add(new FileItem { IsFile = true, Name = indexName }); | |
289 | |
290 sw.WriteLine(string.Format( | |
291 template, | |
292 str, | |
293 backPath, | |
294 GeneratePath(path, backPath, "@@@").Replace(".@@@", ""), | |
295 Path.GetFileNameWithoutExtension(destFolder) + " - ")); | |
296 } | |
297 } | |
298 | |
299 return true; | |
300 } | |
301 | |
302 return false; | |
303 } | |
304 | |
305 private string GenerateSource(string text, FileItem item) | |
306 { | |
307 for (int | |
308 idx = text.IndexOf("<%"), | |
309 end = text.IndexOf("%>", idx + 2); | |
310 idx >= 0 && | |
311 end >= 0; | |
312 idx = text.IndexOf("<%", idx + 2), | |
313 end = text.IndexOf("%>", idx + 2)) | |
314 { | |
315 var startSource = text.Substring(0, idx); | |
316 var source = text.Substring(idx + 2, end - idx - 2).Trim(); | |
317 var command = "source"; | |
318 | |
319 var cmdIdx = source.IndexOf('#'); | |
320 | |
321 if (cmdIdx >= 0) | |
322 { | |
323 command = source.Substring(0, cmdIdx).Trim().ToLower(); | |
324 source = source.Substring(cmdIdx+1).Trim(); | |
325 } | |
326 | |
327 switch (command) | |
328 { | |
329 case "source" : source = GetSourceCodeFromPath(Path.Combine(_sourcePath, source), text); break; | |
330 case "rss" : source = GetNews (Path.Combine(_sourcePath, source)); break; | |
331 case "txt" : | |
332 case "cs" : | |
333 case "sql" : source = GetSourceCode(source, "." + command, text); break; | |
334 case "title" : item.Title = source; source = ""; break; | |
335 case "order" : item.SortOrder = int.Parse(source); source = ""; break; | |
336 case "group" : item.Group = source; source = ""; break; | |
337 case "index" : item.Indexes.Add(source); source = ""; break; | |
338 case "table" : source = GetTable(Path.Combine(_sourcePath, source)); break; | |
339 case "noindex" : | |
340 if (source.Length == 0) | |
341 item.NoIndex = true; | |
342 else | |
343 item.NoIndexes.Add(source); | |
344 | |
345 source = ""; | |
346 break; | |
347 | |
348 default : throw new InvalidOperationException(); | |
349 } | |
350 | |
351 text = startSource + source + text.Substring(end + 2); | |
352 } | |
353 | |
354 return text | |
355 .Replace(@"Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=DBHost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=TestUser;Password=TestPassword;", "..."); | |
356 } | |
357 | |
358 private static string GetNews(string sourcePath) | |
359 { | |
360 var doc = new XmlDocument(); | |
361 | |
362 doc.Load(sourcePath); | |
363 | |
364 var html = "<table border='0' cellpadding='0' cellspacing='0'>"; | |
365 var @class = ""; | |
366 var i = 0; | |
367 | |
368 foreach (XmlNode item in doc.SelectNodes("rss/channel/item")) | |
369 { | |
370 html += string.Format(@" | |
371 <tr><td{0} colspan='2'><nobr><b>{1:MM/dd/yy}</nobr></b> <a href='{2}'>{3}</a></td></tr> | |
372 <tr><td> </td><td class='j'>{4}</td></tr> | |
373 ", | |
374 @class, | |
375 DateTime.Parse(item.SelectSingleNode("pubDate").InnerText), | |
376 item.SelectSingleNode("link"). InnerText, | |
377 item.SelectSingleNode("title"). InnerText, | |
378 item.SelectSingleNode("description").InnerText); | |
379 | |
380 @class = " class='p'"; | |
381 | |
382 if (++i == 20) | |
383 break; | |
384 } | |
385 | |
386 html += "</table>"; | |
387 | |
388 return html; | |
389 } | |
390 | |
391 private static string GetSourceCode(string code, string ext, string source) | |
392 { | |
393 switch (ext) | |
394 { | |
395 case ".cpp": code = "[c]" + code + "[/c]"; break; | |
396 case ".cs": code = "[c#]" + code + "[/c#]"; break; | |
397 case ".vb": code = "[vb]" + code + "[/vb]"; break; | |
398 case ".xml": | |
399 case ".config": code = "[xml]" + code + "[/xml]"; break; | |
400 case ".sql": code = "[sql]" + code + "[/sql]"; break; | |
401 default : code = "[code]" + code + "[/code]"; break; | |
402 } | |
403 | |
404 code = code | |
405 .Replace("/*[", "[") | |
406 .Replace("]*/", "]") | |
407 ; | |
408 | |
409 code = new TextFormatter().Format(code, false); | |
410 | |
411 if (source.IndexOf("<a name='Person'></a>") >= 0) | |
412 code = code | |
413 .Replace("<Person>", "<<a class=m href=#Person>Person</a>>") | |
414 .Replace(", Person>", ", <a class=m href=#Person>Person</a>>") | |
415 .Replace(" Person ", " <a class='m' href=#Person>Person</a> ") | |
416 .Replace(" Person()", " <a class='m' href=#Person>Person</a>()") | |
417 .Replace("(Person ", "(<a class='m' href=#Person>Person</a> ") | |
418 ; | |
419 | |
420 return code | |
421 .Replace("\n", "\r\n") | |
422 .Replace("\r\r\n", "\r\n") | |
423 .Replace("<table width='96%'>", "<table width='100%' class='code'>") | |
424 .Replace("<pre>", "<pre class='code'>") | |
425 .Replace("[a]", "<span class='a'>") | |
426 .Replace("[/a]", "</span>") | |
427 .Replace("[link]", "<a ") | |
428 .Replace("[/link]", "</a>") | |
429 .Replace("[file]", "href='/Source/") | |
430 .Replace("[/file]", ".htm'>") | |
431 .Replace("<!--", "<span class='com'><!--") | |
432 .Replace("-->", "--></span>") | |
433 ; | |
434 } | |
435 | |
436 private static string GetSourceCodeFromPath(string sourcePath, string source) | |
437 { | |
438 var code = ""; | |
439 | |
440 using (var sr = File.OpenText(sourcePath)) | |
441 for (var s = sr.ReadLine(); s != null; s = sr.ReadLine()) | |
442 if (!s.StartsWith("//@") && !s.StartsWith("''@")) | |
443 code += s + "\n"; | |
444 | |
445 return GetSourceCode(code, Path.GetExtension(sourcePath).ToLower(), source); | |
446 } | |
447 | |
448 private static string GeneratePath(string[] path, string backPath, string fileName) | |
449 { | |
450 if (path.Length == 0) | |
451 return ""; | |
452 | |
453 var backLinks = ""; | |
454 var parent = ""; | |
455 var name = Path.GetFileNameWithoutExtension(fileName); | |
456 | |
457 switch (path[0]) | |
458 { | |
459 case "Doc": | |
460 backLinks += string.Format( | |
461 "<br><nobr> <small><a class='m' href='{0}Doc/index.htm'>Doc</a>", | |
462 backPath); | |
463 | |
464 for (var i = 1; i < path.Length; i++) | |
465 { | |
466 parent = ""; | |
467 | |
468 for (var j = i + 1; j < path.Length; j++) | |
469 parent += "../"; | |
470 | |
471 backLinks += string.Format(".<a class='m' href='{0}index.htm'>{1}</a>", parent, path[i]); | |
472 } | |
473 | |
474 if (name.ToLower() != "index") | |
475 { | |
476 backLinks += string.Format(".<a class='m' href='{0}{1}'>{2}</a>", | |
477 parent, Path.GetFileName(fileName), name); | |
478 } | |
479 | |
480 backLinks += "<small></nobr></br>"; | |
481 | |
482 break; | |
483 | |
484 case "Source": | |
485 backLinks += string.Format( | |
486 "<br><nobr> <small><a class='m' href='{0}Source/index.htm'>Source</a>", | |
487 backPath); | |
488 | |
489 for (var i = 1; i < path.Length; i++) | |
490 { | |
491 parent = ""; | |
492 | |
493 for (var j = i + 1; j < path.Length; j++) | |
494 parent += "../"; | |
495 | |
496 backLinks += string.Format(".<a class='m' href='{0}index.htm'>{1}</a>", parent, path[i]); | |
497 } | |
498 | |
499 if (name.ToLower() != "@@@") | |
500 { | |
501 backLinks += string.Format(".<a class='m' href='{0}{1}.htm'>{1}</a>", | |
502 parent, Path.GetFileName(fileName)); | |
503 } | |
504 | |
505 backLinks += "<small></nobr></br>"; | |
506 | |
507 break; | |
508 } | |
509 | |
510 return backLinks; | |
511 } | |
512 | |
513 class TableItem | |
514 { | |
515 public string Provider; | |
516 public string Feature; | |
517 public string Linq; | |
518 public string Implementation; | |
519 } | |
520 | |
521 static string _providerName; | |
522 | |
523 static TableItem GetTableItem(string line) | |
524 { | |
525 if (line.StartsWith("*")) | |
526 { | |
527 _providerName = line.Substring(1).Trim(); | |
528 return null; | |
529 } | |
530 | |
531 var ss = line.Replace("||", "$$$$$").Split('|'); | |
532 | |
533 if (ss.Length != 4) | |
534 throw new InvalidOperationException(line); | |
535 | |
536 var impl = ss[3].Trim().Replace("$$$$$", "||"); | |
537 | |
538 if (impl.StartsWith("#")) | |
539 { | |
540 impl = impl.Substring(impl.IndexOf(' ')).Replace("\\n", "\n").Replace("\\t", "\t").Trim(); | |
541 impl = GetSourceCode(impl, ".sql", ""); | |
542 } | |
543 | |
544 return new TableItem | |
545 { | |
546 Provider = _providerName, | |
547 Feature = ss[1].Trim().Replace("$$$$$", "||"), | |
548 Linq = ss[2].Trim().Replace("$$$$$", "||"), | |
549 Implementation = impl | |
550 }; | |
551 } | |
552 | |
553 static string GetTable(string sourcePath) | |
554 { | |
555 Console.WriteLine("table {0}", sourcePath); | |
556 | |
557 var lines = File.ReadAllLines(sourcePath); | |
558 var items = (from l in lines where l.Trim().Length > 0 select GetTableItem(l)).Where(i => i != null).ToList(); | |
559 var providers = (from i in items where i.Provider.Length > 0 group i by i.Provider into g select g.Key).ToList(); | |
560 var forall = (from i in items where i.Provider.Length == 0 select i).ToList(); | |
561 | |
562 foreach (var p in providers) | |
563 { | |
564 items.AddRange(from a in forall select new TableItem | |
565 { | |
566 Provider = p, | |
567 Feature = a.Feature, | |
568 Linq = a.Linq, | |
569 Implementation = a.Implementation | |
570 }); | |
571 } | |
572 | |
573 foreach (var i in forall) | |
574 items.Remove(i); | |
575 | |
576 var features = ( | |
577 from i in items | |
578 group i by new { i.Feature, i.Linq } into g | |
579 orderby g.Key.Feature, g.Key.Linq | |
580 select new | |
581 { | |
582 g.Key.Feature, | |
583 g.Key.Linq, | |
584 Providers = new string[providers.Count()] | |
585 } | |
586 ).ToList(); | |
587 | |
588 foreach (var f in features) | |
589 { | |
590 for (var i = 0; i < providers.Count; i++) | |
591 { | |
592 f.Providers[i] = ( | |
593 from it in items | |
594 where it.Provider == providers[i] && it.Feature == f.Feature && it.Linq == f.Linq | |
595 select it.Implementation | |
596 ).FirstOrDefault(); | |
597 } | |
598 } | |
599 | |
600 var s = "<table class='data bordered nowrappable'>\n<tr><th> </th><th>Linq</th>"; | |
601 | |
602 foreach (var p in providers) | |
603 s += "<th>" + p + "</th>"; | |
604 | |
605 s += "</tr>"; | |
606 | |
607 var byFeature = from f in features group f by f.Feature; | |
608 | |
609 foreach (var f in byFeature) | |
610 { | |
611 bool first = true; | |
612 | |
613 foreach (var l in f) | |
614 { | |
615 s += "<tr>"; | |
616 | |
617 if (first) | |
618 { | |
619 s += f.Count() == 1? "<td>": "<td rowspan=" + f.Count() + ">"; | |
620 s += f.Key + "</td>"; | |
621 first = false; | |
622 } | |
623 | |
624 s += "<td>" + l.Linq + "</td>"; | |
625 | |
626 var n = 0; | |
627 string p = null; | |
628 | |
629 for (var i = 0; i < l.Providers.Count(); i++) | |
630 { | |
631 if (l.Providers[i] == p) | |
632 n++; | |
633 else | |
634 { | |
635 if (n > 0) | |
636 { | |
637 s += n == 1? "<td": "<td colspan=" + n; | |
638 s += p == null ? " class=nosup>" : ">"; | |
639 s += p ?? "X"; | |
640 s += "</td>"; | |
641 } | |
642 | |
643 p = l.Providers[i]; | |
644 n = 1; | |
645 } | |
646 } | |
647 | |
648 if (n > 0) | |
649 { | |
650 s += n == 1? "<td": "<td colspan=" + n; | |
651 s += p == null ? " class=nosup>" : ">"; | |
652 s += p ?? "X"; | |
653 s += "</td>"; | |
654 } | |
655 | |
656 s += "</tr>\n"; | |
657 } | |
658 } | |
659 | |
660 return s + "</table>"; | |
661 } | |
662 } | |
663 } |