1 /* 2 * Copyright 2009 Alexander Curtis <alex@logicmill.com> 3 * This file is part of GEDmill - A family history website creator 4 * 5 * GEDmill is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * GEDmill is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with GEDmill. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 using System; 20 using System.Collections.Generic; 21 using System.Globalization; 22 using System.IO; 23 using GDModel; 24 using GEDmill.Model; 25 using GKCore.Logging; 26 27 namespace GEDmill.HTML 28 { 29 /// <summary> 30 /// Constructs the HTML for the index pages for the individuals records. 31 /// </summary> 32 public class CreatorIndexIndividuals : Creator 33 { 34 private static readonly ILogger fLogger = LogManager.GetLogger(GMConfig.LOG_FILE, GMConfig.LOG_LEVEL, typeof(CreatorIndexIndividuals).Name); 35 36 37 // This is the list of all the individual records that need to be in the index. 38 private List<StringTuple> fIndividualIndex; 39 40 CreatorIndexIndividuals(GDMTree tree, IProgressCallback progress, string sW3cfile)41 public CreatorIndexIndividuals(GDMTree tree, IProgressCallback progress, string sW3cfile) : base(tree, progress, sW3cfile) 42 { 43 fIndividualIndex = new List<StringTuple>(); 44 } 45 46 // The main method that causes the index to be created. (Note that m_individualIndex needs to be populated first using AddIndividualToIndex() ) Create()47 public void Create() 48 { 49 fLogger.WriteInfo("Creating individuals index..."); 50 51 // Sort the index 52 //AlphabetComparer comparer = new AlphabetComparer(); 53 //fIndividualIndex.Sort(comparer); 54 // FIXME 55 56 // Split into letter groups 57 List<IndexLetter> letters = CreateIndexLetters(); 58 59 // Split across multiple pages 60 List<IndexPage> pages = CreateIndexPages(letters); 61 62 // Create header navbar 63 string headingsLinks = CreateIndexNavbar(pages); 64 65 // Output HTML index pages 66 foreach (IndexPage page in pages) { 67 OutputIndividualsIndexPage(headingsLinks, page); 68 } 69 } 70 71 // Creates a string for the index text and adds it to the index arraylist. AddIndividualToIndex(string firstName, string surname, bool unknownName, string alterEgo, string lifeDates, bool concealed, string relativeFilename, string userRef)72 public void AddIndividualToIndex(string firstName, string surname, bool unknownName, string alterEgo, string lifeDates, 73 bool concealed, string relativeFilename, string userRef) 74 { 75 string commaFirstName = ""; 76 if (firstName != "") { 77 commaFirstName = ", " + firstName; 78 } 79 80 string indexEntry = string.Concat(surname, commaFirstName, " ", alterEgo, lifeDates); 81 if (unknownName) { 82 // Put this entry with the no-surname people 83 indexEntry = ",_" + indexEntry; 84 } 85 86 if (!concealed || GMConfig.Instance.UseWithheldNames) { 87 fIndividualIndex.Add(new StringTuple(indexEntry, relativeFilename, userRef)); 88 } 89 } 90 91 // Creates the page header navbar containing links to the initial letters in the index. CreateIndexNavbar(List<IndexPage> pages)92 private static string CreateIndexNavbar(List<IndexPage> pages) 93 { 94 string headingsLinks = ""; 95 foreach (IndexPage indexpage in pages) { 96 bool firstLetter = true; 97 foreach (IndexLetter indexletter in indexpage.Letters) { 98 if (headingsLinks != "") { 99 headingsLinks += " "; 100 } 101 if (firstLetter) { 102 firstLetter = false; 103 headingsLinks = string.Concat(headingsLinks, "<a href=\"", indexpage.FileName, "\">", indexletter.Initial, "</a>"); 104 } else { 105 headingsLinks = string.Concat(headingsLinks, "<a href=\"", indexpage.FileName, "#", indexletter.Initial, "\">", indexletter.Initial, "</a>"); 106 } 107 } 108 } 109 return headingsLinks; 110 } 111 112 // Splits the index across multiple pages CreateIndexPages(List<IndexLetter> letters)113 private static List<IndexPage> CreateIndexPages(List<IndexLetter> letters) 114 { 115 var pages = new List<IndexPage>(); 116 int lettersCount = letters.Count; 117 int indisPerPage; 118 if (GMConfig.Instance.MultiPageIndexes == false) { 119 // Set to 0 for all names in one page. 120 indisPerPage = 0; 121 } else { 122 indisPerPage = GMConfig.Instance.IndividualsPerIndexPage; 123 } 124 int indiAccumulator = 0; 125 int currentPage = 0; 126 string currentPageName = string.Format("individuals{0}.{1}", ++currentPage, GMConfig.Instance.HtmlExtension); 127 IndexPage indexpageCurrent = new IndexPage(currentPageName); 128 uint letter = 0; 129 while (letter < lettersCount) { 130 int currentIndis = letters[(int)letter].Items.Count; 131 if (indisPerPage != 0 && indiAccumulator + currentIndis > indisPerPage) { 132 int uWith = (indiAccumulator + currentIndis - indisPerPage); 133 int uWithout = indisPerPage - indiAccumulator; 134 if (uWith < uWithout || indiAccumulator == 0) { 135 // Better to include it. 136 indexpageCurrent.TotalIndis += letters[(int)letter].Items.Count; 137 indexpageCurrent.Letters.Add(letters[(int)letter++]); 138 } 139 // Start new page. 140 pages.Add(indexpageCurrent); 141 currentPageName = string.Format("individuals{0}.{1}", ++currentPage, GMConfig.Instance.HtmlExtension); 142 indexpageCurrent = new IndexPage(currentPageName); 143 indiAccumulator = 0; 144 } else { 145 indexpageCurrent.TotalIndis += letters[(int)letter].Items.Count; 146 indexpageCurrent.Letters.Add(letters[(int)letter++]); 147 indiAccumulator += currentIndis; 148 } 149 } 150 if (indexpageCurrent != null) { 151 pages.Add(indexpageCurrent); 152 } 153 return pages; 154 } 155 156 // Collects together the first letters of the items in the index CreateIndexLetters()157 private List<IndexLetter> CreateIndexLetters() 158 { 159 var letters = new List<IndexLetter>(); 160 string lastInitial = ""; 161 string lastTitle = ""; 162 List<StringTuple> currentLetterList = null; 163 foreach (StringTuple tuple in fIndividualIndex) { 164 string name = tuple.First; 165 string link = tuple.Second; 166 string extras = tuple.Third; 167 168 string initial; 169 string title; 170 if (!string.IsNullOrEmpty(name)) { 171 initial = name.Substring(0, 1); 172 if (initial == ",") { 173 // TODO: handle no surname in such a way that names starting with commas don't count. 174 initial = "-"; 175 title = GMConfig.Instance.NoSurname; 176 } else { 177 title = initial; 178 } 179 180 int nCmp = 0; 181 if (lastInitial == "" && initial != "") { // Z,A 182 nCmp = 1; 183 } else if (lastInitial == "-" && initial != "-") { 184 nCmp = 1; 185 } else { 186 nCmp = string.Compare(initial, lastInitial, true, CultureInfo.CurrentCulture); 187 } 188 if (nCmp != 0) { 189 if (currentLetterList != null) { 190 IndexLetter letter = new IndexLetter(lastInitial, lastTitle, currentLetterList); 191 letters.Add(letter); 192 currentLetterList = null; 193 } 194 if (currentLetterList == null) { 195 currentLetterList = new List<StringTuple>(); 196 } 197 lastInitial = initial; 198 lastTitle = title; 199 } 200 currentLetterList.Add(tuple); 201 } 202 } 203 if (currentLetterList != null) { 204 IndexLetter letter = new IndexLetter(lastInitial, lastTitle, currentLetterList); 205 letters.Add(letter); 206 currentLetterList = null; 207 } 208 return letters; 209 } 210 211 // Generates the HTML file for the given page of the index. OutputIndividualsIndexPage(string headingsLinks, IndexPage indexPage)212 private void OutputIndividualsIndexPage(string headingsLinks, IndexPage indexPage) 213 { 214 fLogger.WriteInfo("OutputIndividualsIndexPage()"); 215 216 string ownerName = GMConfig.Instance.OwnersName; 217 if (ownerName != "") { 218 ownerName = " of " + ownerName; 219 } 220 221 string fullFilename = GMConfig.Instance.OutputFolder; 222 if (fullFilename != "") { 223 fullFilename += "\\"; 224 } 225 fullFilename += indexPage.FileName; 226 227 HTMLFile f = null; 228 try { 229 f = new HTMLFile(fullFilename, GMConfig.Instance.IndexTitle, "Index of all individuals in the family tree" + ownerName, "individuals index family tree people history dates"); // Creates a new file, and puts standard header html into it. 230 231 OutputPageHeader(f, "", "", false); 232 233 f.WriteLine(" <div class=\"hr\" />"); 234 f.WriteLine(""); 235 236 f.WriteLine(" <div id=\"page\">"); 237 f.WriteLine(" <h1 class=\"centred\">{0}</h1>", GMConfig.Instance.IndexTitle); 238 239 if (headingsLinks != "") { 240 f.WriteLine(" <div id=\"headingsLinks\">"); 241 f.WriteLine(" <p>{0}</p>", headingsLinks); 242 f.WriteLine(" </div>"); 243 } 244 245 OutputIndexPage(indexPage, f); 246 247 f.WriteLine(" </div> <!-- page -->"); 248 } catch (IOException e) { 249 fLogger.WriteError("Caught IO Exception(3) : ", e); 250 } catch (ArgumentException e) { 251 fLogger.WriteError("Caught Argument Exception(3) : ", e); 252 } finally { 253 if (f != null) { 254 f.Close(); 255 } 256 } 257 } 258 259 // Generates the core of the HTML file for the given page of the index. OutputIndexPage(IndexPage indexPage, HTMLFile f)260 private static void OutputIndexPage(IndexPage indexPage, HTMLFile f) 261 { 262 int nTotal = indexPage.TotalIndis + indexPage.Letters.Count; 263 264 if (indexPage.Letters.Count > 0) { 265 List<StringTuple> firstHalf = new List<StringTuple>(); 266 List<StringTuple> secondHalf = new List<StringTuple>(); 267 List<StringTuple> currentHalf = firstHalf; 268 269 int nHalfWay; 270 if (nTotal < 20) { 271 // If less than 20 individuals, list them in one nColumn. 272 // Set half-way pointer beyond end so that it is never reached. 273 nHalfWay = nTotal + 1; 274 } else { 275 nHalfWay = (nTotal + 1) / 2; 276 } 277 int nAdded = 0; 278 279 foreach (IndexLetter letter in indexPage.Letters) { 280 if (nAdded == nHalfWay) { 281 // Don't add heading letter to bottom of first half. 282 currentHalf = secondHalf; 283 } 284 285 // Add heading letter. 286 currentHalf.Add(new StringTuple(letter.Title, "")); 287 ++nAdded; 288 289 // Add indis. 290 foreach (StringTuple tuple in letter.Items) { 291 if (nAdded == nHalfWay) { 292 currentHalf = secondHalf; 293 } 294 currentHalf.Add(tuple); 295 ++nAdded; 296 } 297 } 298 299 // Output HTML. 300 OutputIndexPageColumns(f, firstHalf, secondHalf); 301 } else { 302 f.WriteLine(" <p>There are no individuals to list.</p>"); 303 } 304 } 305 306 // Outputs the HTML table that lists the names in two columns. OutputIndexPageColumns(HTMLFile f, List<StringTuple> firstHalf, List<StringTuple> secondHalf)307 private static void OutputIndexPageColumns(HTMLFile f, List<StringTuple> firstHalf, List<StringTuple> secondHalf) 308 { 309 f.WriteLine(" <table id=\"index\">"); 310 int i = 0, j = 0; 311 while (i < firstHalf.Count || j < secondHalf.Count) { 312 string sLink1 = " "; 313 string sLink2 = " "; 314 string sExtras1 = " "; 315 string sExtras2 = " "; 316 if (i < firstHalf.Count) { 317 StringTuple tuple = firstHalf[i]; 318 string sName = EscapeHTML(tuple.First, true); 319 if (sName.Length >= 7 && sName.Substring(0, 7) == ", ") // Hack for no surname. 320 { 321 if (sName.Length == 7) { 322 sName = GMConfig.Instance.UnknownName; 323 } else { 324 sName = sName.Substring(7); 325 } 326 } else if (sName.Length >= 6 && sName.Substring(0, 6) == ",_<") { 327 // Hack for unknown name 328 sName = sName.Substring(2); 329 } 330 string sLink = tuple.Second; 331 if (sLink != "") { 332 sLink1 = string.Concat("<a href=\"", sLink, "\">", sName, "</a>"); 333 } else if (sName == GMConfig.Instance.NoSurname) { 334 // Hack for no surname 335 sLink1 = string.Concat("<h2 id=\"-\">", sName, "</h2>"); 336 } else { 337 sLink1 = string.Concat("<h2 id=\"", sName[0], "\">", sName, "</h2>"); 338 } 339 340 sExtras1 = tuple.Third; 341 ++i; 342 } 343 if (j < secondHalf.Count) { 344 StringTuple tuple = secondHalf[j]; 345 string sName = EscapeHTML(tuple.First, true); 346 if (sName.Length >= 7 && sName.Substring(0, 7) == ", ") // Hack for no surname. 347 { 348 if (sName.Length == 7) { 349 sName = GMConfig.Instance.UnknownName; 350 } else { 351 sName = sName.Substring(7); 352 } 353 } else if (sName.Length >= 6 && sName.Substring(0, 6) == ",_<") { 354 // Hack for unknown name 355 sName = sName.Substring(2); 356 } 357 358 string sLink = tuple.Second; 359 if (sLink != "") { 360 sLink2 = string.Concat("<a href=\"", sLink, "\">", sName, "</a>"); 361 } else if (sName == GMConfig.Instance.NoSurname) { 362 // Hack for no surname 363 sLink2 = string.Concat("<h2 id=\"-\">", sName, "</h2>"); 364 } else { 365 sLink2 = string.Concat("<h2 id=\"", sName[0], "\">", sName, "</h2>"); 366 } 367 368 sExtras2 = tuple.Third; 369 ++j; 370 } 371 if (GMConfig.Instance.IncludeUserRefInIndex) { 372 f.WriteLine(string.Concat(" <tr><td>", sExtras1, "</td><td>", sLink1, "</td><td>", sExtras2, "</td><td>", sLink2, "</td></tr>")); 373 } else { 374 f.WriteLine(string.Concat(" <tr><td>", sLink1, "</td><td>", sLink2, "</td></tr>")); 375 } 376 } 377 f.WriteLine(" </table>"); 378 } 379 } 380 } 381