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 += "&nbsp;";
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 = "&nbsp;";
313                 string sLink2 = "&nbsp;";
314                 string sExtras1 = "&nbsp;";
315                 string sExtras2 = "&nbsp;";
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) == ",&nbsp;") // 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) == ",_&lt;") {
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) == ",&nbsp;") // 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) == ",_&lt;") {
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