PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/SHFB/Source/PresentationStyles/LegacyWeb/SearchHelp.aspx

#
ASP.NET | 236 lines | 199 code | 37 blank | 0 comment | 22 complexity | d32ba010a4618e9c00a19bfc54fb1a80 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. <%@ Page Language="C#" EnableViewState="False" %>
  2. <script runat="server">
  3. //===============================================================================================================
  4. // System : Sandcastle Help File Builder
  5. // File : SearchHelp.aspx
  6. // Author : Eric Woodruff (Eric@EWoodruff.us)
  7. // Updated : 04/09/2014
  8. // Note : Copyright 2007-2014, Eric Woodruff, All rights reserved
  9. // Compiler: Microsoft C#
  10. //
  11. // This file contains the code used to search for keywords within the help topics using the full-text index
  12. // files created by the help file builder.
  13. //
  14. // This code is published under the Microsoft Public License (Ms-PL). A copy of the license should be
  15. // distributed with the code. It can also be found at the project website: http://SHFB.CodePlex.com. This
  16. // notice, the author's name, and all copyright notices must remain intact in all applications, documentation,
  17. // and source files.
  18. //
  19. // Date Who Comments
  20. // ==============================================================================================================
  21. // 06/24/2007 EFW Created the code
  22. // 02/17/2012 EFW Switched to JSON serialization to support websites that use something other than ASP.NET
  23. // such as PHP.
  24. //===============================================================================================================
  25. /// <summary>
  26. /// This class is used to track the results and their rankings
  27. /// </summary>
  28. private class Ranking
  29. {
  30. public string Filename, PageTitle;
  31. public int Rank;
  32. public Ranking(string file, string title, int rank)
  33. {
  34. Filename = file;
  35. PageTitle = title;
  36. Rank = rank;
  37. }
  38. }
  39. /// <summary>
  40. /// Render the search results
  41. /// </summary>
  42. /// <param name="writer">The writer to which the results are written</param>
  43. protected override void Render(HtmlTextWriter writer)
  44. {
  45. JavaScriptSerializer jss = new JavaScriptSerializer();
  46. string searchText, ftiFile;
  47. char letter;
  48. bool sortByTitle = false;
  49. jss.MaxJsonLength = Int32.MaxValue;
  50. // The keywords for which to search should be passed in the query string
  51. searchText = this.Request.QueryString["Keywords"];
  52. if(String.IsNullOrEmpty(searchText))
  53. {
  54. writer.Write("<b class=\"PaddedText\">Nothing found</b>");
  55. return;
  56. }
  57. // An optional SortByTitle option can also be specified
  58. if(this.Request.QueryString["SortByTitle"] != null)
  59. sortByTitle = Convert.ToBoolean(this.Request.QueryString["SortByTitle"]);
  60. List<string> keywords = this.ParseKeywords(searchText);
  61. List<char> letters = new List<char>();
  62. List<string> fileList;
  63. Dictionary<string, List<long>> ftiWords, wordDictionary =
  64. new Dictionary<string,List<long>>();
  65. // Load the file index
  66. using(StreamReader sr = new StreamReader(Server.MapPath("fti/FTI_Files.json")))
  67. {
  68. fileList = jss.Deserialize<List<string>>(sr.ReadToEnd());
  69. }
  70. // Load the required word index files
  71. foreach(string word in keywords)
  72. {
  73. letter = word[0];
  74. if(!letters.Contains(letter))
  75. {
  76. letters.Add(letter);
  77. ftiFile = Server.MapPath(String.Format(CultureInfo.InvariantCulture,
  78. "fti/FTI_{0}.json", (int)letter));
  79. if(File.Exists(ftiFile))
  80. {
  81. using(StreamReader sr = new StreamReader(ftiFile))
  82. {
  83. ftiWords = jss.Deserialize<Dictionary<string, List<long>>>(sr.ReadToEnd());
  84. }
  85. foreach(string ftiWord in ftiWords.Keys)
  86. wordDictionary.Add(ftiWord, ftiWords[ftiWord]);
  87. }
  88. }
  89. }
  90. // Perform the search and return the results as a block of HTML
  91. writer.Write(this.Search(keywords, fileList, wordDictionary, sortByTitle));
  92. }
  93. /// <summary>
  94. /// Split the search text up into keywords
  95. /// </summary>
  96. /// <param name="keywords">The keywords to parse</param>
  97. /// <returns>A list containing the words for which to search</returns>
  98. private List<string> ParseKeywords(string keywords)
  99. {
  100. List<string> keywordList = new List<string>();
  101. string checkWord;
  102. string[] words = Regex.Split(keywords, @"\W+");
  103. foreach(string word in words)
  104. {
  105. checkWord = word.ToLower(CultureInfo.InvariantCulture);
  106. if(checkWord.Length > 2 && !Char.IsDigit(checkWord[0]) &&
  107. !keywordList.Contains(checkWord))
  108. keywordList.Add(checkWord);
  109. }
  110. return keywordList;
  111. }
  112. /// <summary>
  113. /// Search for the specified keywords and return the results as a block of
  114. /// HTML.
  115. /// </summary>
  116. /// <param name="keywords">The keywords for which to search</param>
  117. /// <param name="fileInfo">The file list</param>
  118. /// <param name="wordDictionary">The dictionary used to find the words</param>
  119. /// <param name="sortByTitle">True to sort by title, false to sort by
  120. /// ranking</param>
  121. /// <returns>A block of HTML representing the search results.</returns>
  122. private string Search(List<string> keywords, List<string> fileInfo,
  123. Dictionary<string, List<long>> wordDictionary, bool sortByTitle)
  124. {
  125. StringBuilder sb = new StringBuilder(10240);
  126. Dictionary<string, List<long>> matches = new Dictionary<string, List<long>>();
  127. List<long> occurrences;
  128. List<int> matchingFileIndices = new List<int>(),
  129. occurrenceIndices = new List<int>();
  130. List<Ranking> rankings = new List<Ranking>();
  131. string filename, title;
  132. string[] fileIndex;
  133. bool isFirst = true;
  134. int idx, wordCount, matchCount;
  135. foreach(string word in keywords)
  136. {
  137. if(!wordDictionary.TryGetValue(word, out occurrences))
  138. return "<b class=\"PaddedText\">Nothing found</b>";
  139. matches.Add(word, occurrences);
  140. occurrenceIndices.Clear();
  141. // Get a list of the file indices for this match
  142. foreach(long entry in occurrences)
  143. occurrenceIndices.Add((int)(entry >> 16));
  144. if(isFirst)
  145. {
  146. isFirst = false;
  147. matchingFileIndices.AddRange(occurrenceIndices);
  148. }
  149. else
  150. {
  151. // After the first match, remove files that do not appear for
  152. // all found keywords.
  153. for(idx = 0; idx < matchingFileIndices.Count; idx++)
  154. if(!occurrenceIndices.Contains(matchingFileIndices[idx]))
  155. {
  156. matchingFileIndices.RemoveAt(idx);
  157. idx--;
  158. }
  159. }
  160. }
  161. if(matchingFileIndices.Count == 0)
  162. return "<b class=\"PaddedText\">Nothing found</b>";
  163. // Rank the files based on the number of times the words occurs
  164. foreach(int index in matchingFileIndices)
  165. {
  166. // Split out the title, filename, and word count
  167. fileIndex = fileInfo[index].Split('\x0');
  168. title = fileIndex[0];
  169. filename = fileIndex[1];
  170. wordCount = Convert.ToInt32(fileIndex[2]);
  171. matchCount = 0;
  172. foreach(string word in keywords)
  173. {
  174. occurrences = matches[word];
  175. foreach(long entry in occurrences)
  176. if((int)(entry >> 16) == index)
  177. matchCount += (int)(entry & 0xFFFF);
  178. }
  179. rankings.Add(new Ranking(filename, title, matchCount * 1000 / wordCount));
  180. }
  181. // Sort by rank in descending order or by page title in ascending order
  182. rankings.Sort(delegate (Ranking x, Ranking y)
  183. {
  184. if(!sortByTitle)
  185. return y.Rank - x.Rank;
  186. return x.PageTitle.CompareTo(y.PageTitle);
  187. });
  188. // Format the file list and return the results
  189. foreach(Ranking r in rankings)
  190. sb.AppendFormat("<div class=\"TreeItem\">\r\n<img src=\"Item.gif\"/>" +
  191. "<a class=\"UnselectedNode\" target=\"TopicContent\" " +
  192. "href=\"{0}\" onclick=\"javascript: SelectSearchNode(this);\">" +
  193. "{1}</a>\r\n</div>\r\n", r.Filename, r.PageTitle);
  194. // Return the keywords used as well in a hidden span
  195. sb.AppendFormat("<span id=\"SearchKeywords\" style=\"display: none\">{0}</span>",
  196. String.Join(" ", keywords.ToArray()));
  197. return sb.ToString();
  198. }
  199. </script>