using System; using System.IO; using System.Data; using System.Text.RegularExpressions; using HtmlHelp; using HtmlHelp.ChmDecoding; namespace TechBot.Library { public class ApiCommand : BaseCommand, ICommand { private const bool IsVerbose = false; private HtmlHelpSystem chm; private IServiceOutput serviceOutput; private string chmPath; private string mainChm; public ApiCommand(IServiceOutput serviceOutput, string chmPath, string mainChm) { this.serviceOutput = serviceOutput; this.chmPath = chmPath; this.mainChm = mainChm; Run(); } private void WriteIfVerbose(MessageContext context, string message) { if (IsVerbose) serviceOutput.WriteLine(context, message); } private void Run() { string CHMFilename = Path.Combine(chmPath, mainChm); chm = new HtmlHelpSystem(); chm.OpenFile(CHMFilename, null); Console.WriteLine(String.Format("Loaded main CHM: {0}", Path.GetFileName(CHMFilename))); foreach (string filename in Directory.GetFiles(chmPath)) { if (!Path.GetExtension(filename).ToLower().Equals(".chm")) continue; if (Path.GetFileName(filename).ToLower().Equals(mainChm)) continue; Console.WriteLine(String.Format("Loading CHM: {0}", Path.GetFileName(filename))); try { chm.MergeFile(filename); } catch (Exception ex) { Console.WriteLine(String.Format("Could not load CHM: {0}. Exception {1}", Path.GetFileName(filename), ex)); } } Console.WriteLine(String.Format("Loaded {0} CHMs", chm.FileList.Length)); } public bool CanHandle(string commandName) { return CanHandle(commandName, new string[] { "api" }); } public void Handle(MessageContext context, string commandName, string parameters) { if (parameters.Trim().Equals(String.Empty)) DisplayNoKeyword(context); else Search(context, parameters); } public string Help() { return "!api "; } private bool SearchIndex(MessageContext context, string keyword) { if (chm.HasIndex) { IndexItem item = chm.Index.SearchIndex(keyword, IndexType.KeywordLinks); if (item != null && item.Topics.Count > 0) { WriteIfVerbose(context, String.Format("Keyword {0} found in index", item.KeyWord)); IndexTopic indexTopic = item.Topics[0] as IndexTopic; return DisplayResult(context, keyword, indexTopic); } else { WriteIfVerbose(context, String.Format("Keyword {0} not found in index", keyword)); return false; } } else return false; } private void SearchFullText(MessageContext context, string keyword) { string sort = "Rating ASC"; WriteIfVerbose(context, String.Format("Searching fulltext database for {0}", keyword)); bool partialMatches = false; bool titlesOnly = true; int maxResults = 100; DataTable results = chm.PerformSearch(keyword, maxResults, partialMatches, titlesOnly); WriteIfVerbose(context, String.Format("results.Rows.Count = {0}", results != null ? results.Rows.Count.ToString() : "(none)")); if (results != null && results.Rows.Count > 0) { results.DefaultView.Sort = sort; if (!DisplayResult(context, keyword, results)) { DisplayNoResult(context, keyword); } } else { DisplayNoResult(context, keyword); } } private void Search(MessageContext context, string keyword) { if (!SearchIndex(context, keyword)) SearchFullText(context, keyword); } private bool DisplayResult(MessageContext context, string keyword, IndexTopic indexTopic) { keyword = keyword.Trim().ToLower(); string url = indexTopic.URL; WriteIfVerbose(context, String.Format("URL from index search {0}", url)); string prototype = ExtractPrototype(context, url); if (prototype == null || prototype.Trim().Equals(String.Empty)) return false; string formattedPrototype = FormatPrototype(prototype); serviceOutput.WriteLine(context, formattedPrototype); return true; } private bool DisplayResult(MessageContext context, string keyword, DataTable results) { keyword = keyword.Trim().ToLower(); for (int i = 0; i < results.DefaultView.Count; i++) { DataRowView row = results.DefaultView[i]; string title = row["Title"].ToString(); WriteIfVerbose(context, String.Format("Examining {0}", title)); if (title.Trim().ToLower().Equals(keyword)) { string location = row["Location"].ToString(); string rating = row["Rating"].ToString(); string url = row["Url"].ToString(); string prototype = ExtractPrototype(context, url); if (prototype == null || prototype.Trim().Equals(String.Empty)) continue; string formattedPrototype = FormatPrototype(prototype); serviceOutput.WriteLine(context, formattedPrototype); return true; } } return false; } private void DisplayNoResult(MessageContext context, string keyword) { serviceOutput.WriteLine(context, String.Format("I don't know about keyword {0}", keyword)); } private void DisplayNoKeyword(MessageContext context) { serviceOutput.WriteLine(context, "Please give me a keyword."); } private string ReplaceComments(string s) { return Regex.Replace(s, "//(.+)\r\n", ""); } private string ReplaceLineEndings(string s) { return Regex.Replace(s, "(\r\n)+", " "); } private string ReplaceSpaces(string s) { return Regex.Replace(s, @" +", " "); } private string ReplaceSpacesBeforeLeftParenthesis(string s) { return Regex.Replace(s, @"\( ", @"("); } private string ReplaceSpacesBeforeRightParenthesis(string s) { return Regex.Replace(s, @" \)", @")"); } private string ReplaceSemicolon(string s) { return Regex.Replace(s, @";", @""); } private string FormatPrototype(string prototype) { string s = ReplaceComments(prototype); s = ReplaceLineEndings(s); s = ReplaceSpaces(s); s = ReplaceSpacesBeforeLeftParenthesis(s); s = ReplaceSpacesBeforeRightParenthesis(s); s = ReplaceSemicolon(s); return s; } private string ExtractPrototype(MessageContext context, string url) { string page = GetPage(context, url); Match match = Regex.Match(page, "
(.+)
", RegexOptions.Multiline | RegexOptions.Singleline); if (match.Groups.Count > 1) { string prototype = match.Groups[1].ToString(); return StripHtml(StripAfterSlashPre(prototype)); } return ""; } private string StripAfterSlashPre(string html) { int index = html.IndexOf(""); if (index != -1) { return html.Substring(0, index); } else return html; } private string StripHtml(string html) { return Regex.Replace(html, @"<(.|\n)*?>", String.Empty); } private string GetPage(MessageContext context, string url) { string CHMFileName = ""; string topicName = ""; string anchor = ""; CHMStream.CHMStream baseStream; if (!chm.BaseStream.GetCHMParts(url, ref CHMFileName, ref topicName, ref anchor)) { baseStream = chm.BaseStream; CHMFileName = baseStream.CHMFileName; topicName = url; anchor = ""; } else { baseStream = GetBaseStreamFromCHMFileName(context, CHMFileName); } if ((topicName == "") || (CHMFileName == "") || (baseStream == null)) { return ""; } return baseStream.ExtractTextFile(topicName); } private CHMStream.CHMStream GetBaseStreamFromCHMFileName(MessageContext context, string CHMFileName) { foreach (CHMFile file in chm.FileList) { WriteIfVerbose(context, String.Format("Compare: {0} <> {1}", file.ChmFilePath, CHMFileName)); if (file.ChmFilePath.ToLower().Equals(CHMFileName.ToLower())) { return file.BaseStream; } } WriteIfVerbose(context, String.Format("Could not find loaded CHM file in list: {0}", CHMFileName)); return null; } } }