示例#1
0
// Gets the raw USFM for the bible and passage given.
string search_logic_get_bible_verse_usfm (string bible, int book, int chapter, int verse)
{
  vector <string> texts;
  string path = search_logic_chapter_file (bible, book, chapter);
  string index = filter_url_file_get_contents (path);
  vector <string> lines = filter_string_explode (index, '\n');
  int index_verse = 0;
  bool read_index_verse = false;
  int index_item = 0;
  for (auto & line : lines) {
    if (read_index_verse) {
      index_verse = convert_to_int (line);
      read_index_verse = false;
    } else if (line == search_logic_verse_separator ()) {
      read_index_verse = true;
      index_item = 0;
    } else if (line == search_logic_index_separator ()) {
      index_item++;
    } else if (index_item == USFM_RAW) {
      if (verse == index_verse) {
        texts.push_back (line);
      }
    }
  }
  return filter_string_implode (texts, "\n");
}
示例#2
0
// This function takes the abbreviations stored for a Bible as $data.
// It then sorts them out.
// It adds the Bible books not in the $data.
// It sorts everything in the standard canonical order.
// It presents everything for display to the user for editing and update.
string filter_abbreviations_display (string data)
{
  vector <pair <int, string> > v_data = filter_abbreviations_read (data);
  
  vector <string> v_output;
  
  // Check for books, order them, supply missing ones.
  vector <int> books = Database_Books::getIDs ();
  for (int book : books) {
    bool found = false;
    for (auto element : v_data) {
      int bookId = element.first;
      string abbreviation = element.second;
      if (book == bookId) {
        v_output.push_back (Database_Books::getEnglishFromId (book) + " = " + abbreviation);
        found = true;
      }
    }
    if (!found) {
      v_output.push_back (Database_Books::getEnglishFromId (book) + " = ");
    }
  }
  
  // Get data as a string for display.
  return filter_string_implode (v_output, "\n");
}
示例#3
0
文件: usfm.cpp 项目: alerque/bibledit
// Returns the verse text given a $verse_number and $usfm code.
// Handles combined verses.
string usfm_get_verse_text (string usfm, int verse_number)
{
  vector <string> result;
  bool hit = (verse_number == 0);

  vector <string> lines = filter_string_explode (usfm, '\n');
  for (string line : lines) {
    vector <int> verses = usfm_get_verse_numbers (line);
    if (verse_number == 0) {
      if (verses.size () != 1) hit = false;
      if (hit) result.push_back (line);
    } else {
      if (in_array (verse_number, verses)) {
        // Desired verse found.
        hit = true;
      } else if (verses.size () == 1) {
        // No verse found: No change in situation.
      } else {
        // Outside desired verse.
        hit = false;
      }
      if (hit) result.push_back (line);
    }
  }
  
  // Return the verse text.
  string verseText = filter_string_implode (result, "\n");
  return verseText;
}
示例#4
0
// Takes the passage in $text, and explodes it into book, chapter, verse.
// The book is the numerical identifier, not the string, e.g.,
// it would not return "Genesis", but identifier 1.
Passage filter_passage_explode_passage (string text)
{
  text = filter_passage_clean_passage (text);
  // Cut the text in its parts.
  vector <string> bits = filter_string_explode (text, ' ');
  // Defaults to empty passage.
  Passage passage;
  // Take the bits.
  if (!bits.empty ()) {
    string verse = bits.back ();
    if (!verse.empty ()) passage.verse = verse;
    bits.pop_back ();
  }
  if (!bits.empty ()) {
    string chapter = bits.back ();    
    if (!chapter.empty()) passage.chapter = convert_to_int (chapter);
    bits.pop_back ();
  }
  string book = filter_string_implode (bits, " ");
  if (!book.empty()) {
    int bk = filter_passage_interpret_book (book);
    passage.book = bk;
  }
  // Return the result.
  return passage;
}
示例#5
0
void client_logic_create_note_decode (string data,
                                      string& bible, int& book, int& chapter, int& verse,
                                      string& summary, string& contents, bool& raw)
{
  vector <string> lines = filter_string_explode (data, '\n');
  if (!lines.empty ()) {
    bible = lines [0];
    lines.erase (lines.begin());
  }
  if (!lines.empty ()) {
    book = convert_to_int (lines [0]);
    lines.erase (lines.begin());
  }
  if (!lines.empty ()) {
    chapter = convert_to_int (lines [0]);
    lines.erase (lines.begin());
  }
  if (!lines.empty ()) {
    verse = convert_to_int (lines [0]);
    lines.erase (lines.begin());
  }
  if (!lines.empty ()) {
    summary = lines [0];
    lines.erase (lines.begin());
  }
  if (!lines.empty ()) {
    raw = convert_to_bool (lines [0]);
    lines.erase (lines.begin());
  }
  contents = filter_string_implode (lines, "\n");
}
示例#6
0
文件: css.cpp 项目: alerque/bibledit
// Deliver the created CSS.
// $path: If given, it saves the CSS to $path.
// The function returns the CSS as a string.
string Styles_Css::css (string path)
{
  string css = filter_string_implode (code, "\n");
  if (path != "") {
    filter_url_file_put_contents (path, css);
  }
  return css;
}
示例#7
0
string Editor_Html2Usfm::get ()
{
  // Generate the USFM as one string.
  string usfm = filter_string_implode (output, "\n");
  
  usfm = cleanUSFM (usfm);
  
  return usfm;
}
示例#8
0
// Gets the human-readable name of a $line like this:
// [CrossWire] *[Shona] (1.1) - Shona Bible
string sword_logic_get_name (string line)
{
  vector <string> bits = filter_string_explode (line, '-');
  if (bits.size () >= 2) {
    bits.erase (bits.begin ());
  }
  line = filter_string_implode (bits, "-");
  line = filter_string_trim (line);
  return line;
}
示例#9
0
void client_logic_usfm_resources_update ()
{
  // The Cloud stores the list of USFM resources.
  // It is stored in the client files area.
  // Clients can access it from there.
  string path = client_logic_usfm_resources_path ();
  Database_UsfmResources database_usfmresources;
  vector <string> resources = database_usfmresources.getResources ();
  filter_url_file_put_contents (path, filter_string_implode (resources, "\n"));
}
示例#10
0
void workspace_delete (void * webserver_request, string workspace)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;

  string rawvalue;
  vector <string> currentlines;
  vector <string> newlines;
  
  rawvalue = request->database_config_user()->getWorkspaceURLs ();
  currentlines = filter_string_explode (rawvalue, '\n');
  newlines.clear ();
  for (auto & line : currentlines) {
    if (line.find (workspace + "_") != 0) newlines.push_back (line);
  }
  rawvalue = filter_string_implode (newlines, "\n");
  request->database_config_user()->setWorkspaceURLs (rawvalue);
  
  rawvalue = request->database_config_user()->getWorkspaceWidths ();
  currentlines = filter_string_explode (rawvalue, '\n');
  newlines.clear ();
  for (auto & line : currentlines) {
    if (line.find (workspace + "_") != 0) newlines.push_back (line);
  }
  rawvalue = filter_string_implode (newlines, "\n");
  request->database_config_user()->setWorkspaceWidths (rawvalue);
  
  rawvalue = request->database_config_user()->getWorkspaceHeights ();
  currentlines = filter_string_explode (rawvalue, '\n');
  newlines.clear ();
  for (auto & line : currentlines) {
    if (line.find (workspace + "_") != 0) newlines.push_back (line);
  }
  rawvalue = filter_string_implode (newlines, "\n");
  request->database_config_user()->setWorkspaceHeights (rawvalue);
  
  request->database_config_user()->setActiveWorkspace ("");
  
  // For a client, store the setting for sending to the server.
  workspace_cache_for_cloud (request, true, true, true);
}
示例#11
0
string client_logic_create_note_encode (string bible, int book, int chapter, int verse,
                                        string summary, string contents, bool raw)
{
  vector <string> data;
  data.push_back (bible);
  data.push_back (convert_to_string (book));
  data.push_back (convert_to_string (chapter));
  data.push_back (convert_to_string (verse));
  data.push_back (summary);
  data.push_back (convert_to_string (raw));
  data.push_back (contents);
  return filter_string_implode (data, "\n");
}
示例#12
0
// Export data.
string Database_Versifications::output (const string& name)
{
  vector <string> lines;
  vector <Passage> versification_data = getBooksChaptersVerses (name);
  for (Passage & passage : versification_data) {
    string line = Database_Books::getEnglishFromId (passage.book);
    line.append (" ");
    line.append (convert_to_string (passage.chapter));
    line.append (":");
    line.append (passage.verse);
    lines.push_back (line);
  }
  return filter_string_implode (lines, "\n");
}
示例#13
0
string index_listing (void * webserver_request, string url)
{
    string page;
    page = Assets_Page::header ("Bibledit", webserver_request);
    // No breadcrumbs because the user can arrive here from more than one place.
    Assets_View view;
    url = filter_url_urldecode (url);
    url = filter_url_create_path ("", url);
    url = filter_string_str_replace ("\\", "/", url);
    view.set_variable ("url", url);
    string parent = filter_url_dirname_web (url);
    if (parent.length () > 1) {
        view.enable_zone ("parent");
        view.set_variable ("parent", parent);
    }
    string directory = filter_url_create_root_path (url);
    if (!file_or_dir_exists (directory) || filter_url_is_dir (directory)) {
        vector <string> files = filter_url_scandir (directory);
        for (auto & file : files) {
            string path = filter_url_create_path (directory, file);
            string line;
            line.append ("<tr>");
            line.append ("<td>");
            line.append ("<a href=\"" + filter_url_create_path (url, file) + "\">");
            line.append (file);
            line.append ("</a>");
            line.append ("</td>");
            line.append ("<td>");
            if (!filter_url_is_dir (path)) {
                line.append (convert_to_string (filter_url_filesize (path)));
            }
            line.append ("</td>");
            line.append ("</tr>");
            file = line;
        }
        string listing = filter_string_implode (files, "\n");
        if (listing.empty ()) listing = translate ("No files in this folder");
        else {
            listing.insert (0, "<table>");
            listing.append ("</table>");
        }
        view.set_variable ("listing", listing);
    } else {
        string filename = filter_url_create_root_path (url);
        return filter_url_file_get_contents (filename);
    }
    page += view.render ("index", "listing");
    page += Assets_Page::footer ();
    return page;
}
示例#14
0
文件: usfm.cpp 项目: alerque/bibledit
// Returns the USFM text for a range of verses for the input $usfm code.
// It handles combined verses.
// It ensures that the $exclude_usfm does not make it to the output of the function.
string usfm_get_verse_range_text (string usfm, int verse_from, int verse_to, const string& exclude_usfm)
{
  vector <string> bits;
  string previous_usfm;
  for (int vs = verse_from; vs <= verse_to; vs++) {
    string verse_usfm = usfm_get_verse_text (usfm, vs);
    // Do not include repeating USFM in the case of combined verse numbers in the input USFM code.
    if (verse_usfm == previous_usfm) continue;
    previous_usfm = verse_usfm;
    // In case of combined verses, the excluded USFM should not be included in the result.
    if (verse_usfm != exclude_usfm) {
      bits.push_back (verse_usfm);
    }
  }
  usfm = filter_string_implode (bits, "\n");
  return usfm;
}
示例#15
0
// Queue task $command to run later, with $parameters for that task.
void tasks_logic_queue (string command, vector <string> parameters)
{
  // The file on disk will contain the command on the first line,
  // and any parameters on the following lines, one parameters per line.
  vector <string> lines;
  lines.push_back (command);
  lines.insert (lines.end(), parameters.begin(), parameters.end());
  // The filename to write to contains seconds and microseconds.
  string seconds = convert_to_string (filter_date_seconds_since_epoch ());
  string time = seconds + filter_string_fill (convert_to_string (filter_date_numerical_microseconds ()), 8, '0');
  string file = filter_url_create_path (tasks_logic_folder (), time);
  // On Windows the microtime is not fine enough.
  // This leads to one task overwriting a previous one in case it is queued immediately after.
  // Deal with that problem here: Ensure the filename is unique.
  file = filter_url_unique_path (file);
  // Save it.
  command = filter_string_implode (lines, "\n");
  filter_url_file_put_contents (file, command);
}
示例#16
0
string Database_Privileges::save (string username)
{
  SqliteDatabase sql (database ());
  
  vector <string> lines;

  lines.push_back (bibles_start ());
  sql.add ("SELECT bible, book, write FROM bibles WHERE username ="******";");
  map <string, vector <string> > result = sql.query ();
  vector <string> bible = result ["bible"];
  vector <string> book =  result ["book"];
  vector <string> write = result ["write"];
  for (size_t i = 0; i < bible.size (); i++) {
    lines.push_back (bible [i]);
    lines.push_back (book [i]);
    // It could have just stored 0 or 1 for the boolean values.
    // But if that were done, then there would be no change in the length of the file
    // when changing only boolean values.
    // And then the client would not re-download that file.
    // To use "on" and "off", that solves the issue.
    bool b = convert_to_bool (write[i]);
    if (b) lines.push_back (on ());
    else lines.push_back (off ());
  }
  lines.push_back (bibles_end ());
  
  lines.push_back (features_start ());
  sql.clear ();
  sql.add ("SELECT feature FROM features WHERE username ="******";");
  result = sql.query ();
  vector <string> feature = result ["feature"];
  for (size_t i = 0; i < feature.size (); i++) {
    lines.push_back (feature [i]);
  }
  lines.push_back (features_end ());
  
  return filter_string_implode (lines, "\n");
}
示例#17
0
void workspace_set_values (void * webserver_request, int selector, const map <int, string> & values)
{
  // Store values locally, and for a client, store them also for sending to the server.
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  string workspace = workspace_get_active_name (request);
  string rawvalue;
  if (selector == URLS) rawvalue = request->database_config_user()->getWorkspaceURLs ();
  if (selector == WIDTHS) rawvalue = request->database_config_user()->getWorkspaceWidths ();
  if (selector == HEIGHTS) rawvalue = request->database_config_user()->getWorkspaceHeights ();
  if (selector == ENTIREWIDTH) rawvalue = request->database_config_user()->getEntireWorkspaceWidths ();
  vector <string> currentlines = filter_string_explode (rawvalue, '\n');
  vector <string> newlines;
  for (auto & line : currentlines) {
    if (line.find (workspace + "_") != 0) {
      newlines.push_back (line);
    }
  }
  for (auto & element : values) {
    string line = workspace + "_" + convert_to_string (element.first) + "_" + element.second;
    newlines.push_back (line);
  }
  rawvalue = filter_string_implode (newlines, "\n");
  if (selector == URLS) {
    request->database_config_user()->setWorkspaceURLs (rawvalue);
    workspace_cache_for_cloud (request, true, false, false);
  }
  if (selector == WIDTHS) {
    request->database_config_user()->setWorkspaceWidths (rawvalue);
    workspace_cache_for_cloud (request, false, true, false);
  }
  if (selector == HEIGHTS) {
    request->database_config_user()->setWorkspaceHeights (rawvalue);
    workspace_cache_for_cloud (request, false, false, true);
  }
  if (selector == ENTIREWIDTH) {
    request->database_config_user()->setEntireWorkspaceWidths (rawvalue);
    workspace_cache_for_cloud (request, false, true, false);
  }
}
示例#18
0
// This orders the workspacees.
// It takes the order as in array $workspacees.
void workspace_reorder (void * webserver_request, const vector <string> & workspacees)
{
  // The order of the workspacees is taken from the URLs.
  // Widths and heights are not considered for the order.
  
  Webserver_Request * request = (Webserver_Request *) webserver_request;

  // Retrieve the old order of the workspacees, plus their details.
  string rawvalue = request->database_config_user()->getWorkspaceURLs ();
  vector <string> oldlines = filter_string_explode (rawvalue, '\n');
  
  // Create vector with the sorted workspace definitions.
  vector <string> newlines;
  for (auto & workspace : workspacees) {
    for (auto & line : oldlines) {
      if (line.find (workspace + "_") == 0) {
        newlines.push_back (line);
        line.clear ();
      }
    }
  }
  
  // Add any extra ones.
  for (auto & line : oldlines) {
    if (!line.empty ()) {
      newlines.push_back (line);
    }
  }

  // Save everything.
  rawvalue = filter_string_implode (newlines, "\n");
  request->database_config_user()->setWorkspaceURLs (rawvalue);

  // Schedule for sending to Cloud.
  workspace_cache_for_cloud (request, true, false, false);
}
示例#19
0
文件: get.cpp 项目: alerque/bibledit
string resource_get (void * webserver_request)
{
    Webserver_Request * request = (Webserver_Request *) webserver_request;


    vector <string> bits;


    string s_resource = request->query["resource"];
    string s_book = request->query["book"];
    string s_chapter = request->query["chapter"];
    string s_verse = request->query["verse"];


    if (!s_resource.empty () && !s_book.empty () && !s_chapter.empty () && !s_verse.empty ()) {


        unsigned int resource = convert_to_int (s_resource);
        int book = convert_to_int (s_book);
        int chapter = convert_to_int (s_chapter);
        int verse = convert_to_int (s_verse);


        // In JavaScript the resource identifier starts at 1 instead of at 0.
        resource--;
        vector <string> resources = request->database_config_user()->getActiveResources ();
        if (resource <= resources.size ()) {
            s_resource = resources [resource];


            // Handle a divider.
            if (resource_logic_is_divider (s_resource)) return resource_logic_get_divider (s_resource);


            string bible = request->database_config_user ()->getBible ();
            string versification = Database_Config_Bible::getVersificationSystem (bible);
            Database_Versifications database_versifications;
            vector <int> chapters = database_versifications.getChapters (versification, book);


            // Whether to add extra verse numbers, for clarity in case of viewing more than one verse.
            bool add_verse_numbers = false;
            int context_before = request->database_config_user ()->getResourceVersesBefore ();
            if (context_before) add_verse_numbers = true;
            int context_after = request->database_config_user ()->getResourceVersesAfter ();
            if (context_after) add_verse_numbers = true;


            // Context before the focused verse.
            vector <int> chapters_before;
            vector <int> verses_before;
            if (context_before > 0) {
                for (int ch = chapter - 1; ch <= chapter; ch++) {
                    if (in_array (ch, chapters)) {
                        vector <int> verses = database_versifications.getVerses (versification, book, ch);
                        for (size_t vs = 0; vs < verses.size (); vs++) {
                            int vs2 = verses [vs];
                            if ((ch < chapter) || (vs2 < verse)) {
                                if (vs2 > 0) {
                                    chapters_before.push_back (ch);
                                    verses_before.push_back (verses[vs]);
                                }
                            }
                        }
                    }
                }
                while ((int)chapters_before.size () > context_before) {
                    chapters_before.erase (chapters_before.begin ());
                    verses_before.erase (verses_before.begin ());
                }
            }
            for (unsigned int i = 0; i < chapters_before.size (); i++) {
                bits.push_back (resource_logic_get_html (request, s_resource, book, chapters_before[i], verses_before[i], add_verse_numbers));
            }


            // Focused verse.
            bits.push_back (resource_logic_get_html (request, s_resource, book, chapter, verse, add_verse_numbers));


            // Context after the focused verse.
            vector <int> chapters_after;
            vector <int> verses_after;
            if (context_after > 0) {
                for (int ch = chapter; ch <= chapter + 1; ch++) {
                    if (in_array (ch, chapters)) {
                        vector <int> verses = database_versifications.getVerses (versification, book, ch);
                        for (size_t vs = 0; vs < verses.size (); vs++) {
                            int vs2 = verses [vs];
                            if ((ch > chapter) || (vs2 > verse)) {
                                if (vs2 > 0) {
                                    chapters_after.push_back (ch);
                                    verses_after.push_back (verses[vs]);
                                }
                            }
                        }
                    }
                }
                while ((int)chapters_after.size () > context_after) {
                    chapters_after.pop_back ();
                    verses_after.pop_back ();
                }
            }
            for (unsigned int i = 0; i < chapters_after.size (); i++) {
                bits.push_back (resource_logic_get_html (request, s_resource, book, chapters_after[i], verses_after[i], add_verse_numbers));
            }
        }
    }


    string page = filter_string_implode (bits, ""); // <br>
    return page;
}
示例#20
0
void Database_Config_General::setList (const char * key, vector <string> values)
{
  string value = filter_string_implode (values, "\n");
  setValue (key, value);
}
示例#21
0
string resource_cache (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;

  
  string resource = request->query ["resource"];
  
  
  string page;
  Assets_Header header = Assets_Header (menu_logic_resources_text (), request);
  header.addBreadCrumb (menu_logic_settings_menu (), menu_logic_settings_text ());
  page = header.run ();
  Assets_View view;

  
  if (request->query.count ("clear")) {
    sendreceive_resources_clear_all ();
  }
  vector <string> resources = Database_Config_General::getResourcesToCache ();
  if (!resources.empty ()) {
    view.enable_zone ("scheduled");
    view.set_variable ("scheduled", filter_string_implode (resources, " | "));
  }
  
  
  vector <string> listed_resources;
  string block;
  
  // Display the available USFM resources.
  resources = client_logic_usfm_resources_get ();
  for (auto & resource : resources) {
    block.append ("<p>");
    block.append ("<a href=\"download?name=" + resource + "\">" + resource + "</a>");
    block.append ("</p>\n");
  }
  listed_resources.insert (listed_resources.end (), resources.begin (), resources.end ());
  // Display the available external resources.
  resources = resource_external_names ();
  for (auto & resource : resources) {
    block.append ("<p>");
    block.append ("<a href=\"download?name=" + resource + "\">" + resource + "</a>");
    block.append ("</p>\n");
  }
  listed_resources.insert (listed_resources.end (), resources.begin (), resources.end ());
  // Display the available SWORD resources.
  resources = sword_logic_get_available ();
  for (auto & resource : resources) {
    string source = sword_logic_get_source (resource);
    string module = sword_logic_get_remote_module (resource);
    string name = "[" + source + "][" + module + "]";
    block.append ("<p>");
    block.append ("<a href=\"download?name=" + name + "\">" + resource + "</a>");
    block.append ("</p>\n");
  }
  listed_resources.insert (listed_resources.end (), resources.begin (), resources.end ());
  // Display any old USFM resources still available on the client.
  Database_UsfmResources database_usfmresources;
  resources = database_usfmresources.getResources ();
  for (auto & resource : resources) {
    if (in_array (resource, listed_resources)) continue;
    block.append ("<p>");
    block.append ("<a href=\"download?name=" + resource + "&old=yes\">" + resource + "</a>");
    block.append ("</p>\n");
  }
  // Display any offline resources still available on the client.
  Database_OfflineResources database_offlineresources;
  resources = database_offlineresources.names ();
  for (auto & resource : resources) {
    if (in_array (resource, listed_resources)) continue;
    block.append ("<p>");
    block.append ("<a href=\"download?name=" + resource + "&old=yes\">" + resource + "</a>");
    block.append ("</p>\n");
  }
  // Display the list.
  view.set_variable ("block", block);

  
  page += view.render ("resource", "cache");
  page += Assets_Page::footer ();
  return page;
}
示例#22
0
string lexicon_definition (void * webserver_request)
{
  // Retrieve the id: It may contain a Strong's number or a lemma.
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  string id = request->query["id"];
  
  vector <string> renderings;
  
  if (!id.empty ()) {
    
    string letter = id.substr (0, 1);
    if (letter == HEBREW_ETCBC4_PREFIX) {
      
      // ETCBC4 database.
      // When a defintion is clicked for the second time, it gets erased.
      if (id != request->database_config_user ()->getRequestedEtcbc4Definition ()) {
        renderings.push_back (lexicon_logic_render_etcbc4_morphology (id));
      } else {
        id.clear ();
      }
      request->database_config_user ()->setRequestedEtcbc4Definition (id);
                                                                
    } else if (letter == KJV_LEXICON_PREFIX) {
      
      // King James Bible with Strong's numbers.
      if (id != request->database_config_user ()->getRequestedKjvDefinition ()) {
        Database_Kjv database_kjv;
        string strong = database_kjv.strong (convert_to_int (id.substr (1)));
        string rendering = lexicon_logic_render_definition (strong);
        if (!rendering.empty ()) renderings.push_back (rendering);
      } else {
        id.clear ();
      }
      request->database_config_user ()->setRequestedKjvDefinition (id);
      
    } else if (letter == MORPHHB_PREFIX) {
      
      // Open Scriptures Hebrew with Strong's numbers.
      if (id != request->database_config_user ()->getRequestedMorphHbDefinition ()) {
        Database_MorphHb database_morphhb;
        string parsing = database_morphhb.parsing (convert_to_int (id.substr (1)));
        vector <string> strongs;
        vector <string> bdbs;
        lexicon_logic_convert_morphhb_parsing_to_strong (parsing, strongs, bdbs);
        for (size_t i = 0; i < strongs.size (); i++) {
          string rendering = lexicon_logic_render_definition (strongs[i]);
          if (!rendering.empty ()) renderings.push_back (rendering);
          rendering = "<a href=\"" BDB_PREFIX + bdbs[i] + "\">Brown Driver Briggs</a>";
          renderings.push_back (rendering);
        }
      } else {
        id.clear ();
      }
      request->database_config_user ()->setRequestedMorphHbDefinition (id);
      
    } else if (letter == SBLGNT_PREFIX) {
      
      // SBL Greek New Testament plus morphology.
      if (id != request->database_config_user ()->getRequestedSblGntDefinition ()) {
        Database_MorphGnt database_morphgnt;
        Database_Strong database_strong;
        int rowid = convert_to_int (id.substr (1));
        // The part of speech.
        string pos = database_morphgnt.pos (rowid);
        string rendering = lexicon_logic_render_morphgnt_part_of_speech (pos);
        rendering.append (" ");
        // The parsing.
        string parsing = database_morphgnt.parsing (rowid);
        rendering.append (lexicon_logic_render_morphgnt_parsing_code (parsing));
        renderings.push_back (rendering);
        // The lemma.
        string lemma = database_morphgnt.lemma (rowid);
        vector <string> strongs = database_strong.strong (lemma);
        for (auto & id : strongs) {
          rendering = lexicon_logic_render_definition (id);
          if (!rendering.empty ()) renderings.push_back (rendering);
        }

      } else {
        id.clear ();
      }
      request->database_config_user ()->setRequestedSblGntDefinition (id);
      
    } else if (letter == "H") {
      
      // Strong's Hebrew.
      if (id != request->database_config_user ()->getRequestedHDefinition ()) {
        string rendering = lexicon_logic_render_definition (id);
        if (!rendering.empty ()) renderings.push_back (rendering);
      } else {
        id.clear ();
      }
      request->database_config_user ()->setRequestedHDefinition (id);
      
    } else if (letter == "G") {
      
      // Strong's Greek.
      if (id != request->database_config_user ()->getRequestedGDefinition ()) {
        string rendering = lexicon_logic_render_definition (id);
        if (!rendering.empty ()) renderings.push_back (rendering);
      } else {
        id.clear ();
      }
      request->database_config_user ()->setRequestedGDefinition (id);
      
    } else if (letter == BDB_PREFIX) {
      
      // Brown Driver Briggs lexicon.
      string rendering = lexicon_logic_render_bdb_entry (id.substr (1));
      if (!rendering.empty ()) renderings.push_back (rendering);
      request->database_config_user ()->setRequestedMorphHbDefinition ("");
      
    } else {

      // Unknown definition request.
      renderings.push_back (id);
      
    }
    
  }
  
  return filter_string_implode (renderings, "<br>");
}
示例#23
0
string resource_manage (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;

  
  string page;
  Assets_Header header = Assets_Header (translate("USFM Resources"), request);
  header.addBreadCrumb (menu_logic_settings_menu (), menu_logic_settings_text ());
  page = header.run ();
  Assets_View view;
  
  
  Database_UsfmResources database_usfmresources = Database_UsfmResources ();

  
  // Delete resource.
  string remove = request->query ["delete"];
  if (remove != "") {
    string confirm = request->query ["confirm"];
    if (confirm == "") {
      Dialog_Yes dialog_yes = Dialog_Yes ("manage", translate("Would you like to delete this resource?"));
      dialog_yes.add_query ("delete", remove);
      page += dialog_yes.run ();
      return page;
    } if (confirm == "yes") {
      if (access_bible_write (request, remove)) {
        database_usfmresources.deleteResource (remove);
        // The Cloud updates the list of available USFM resources for the clients.
        tasks_logic_queue (LISTUSFMRESOURCES);
      } else {
        view.set_variable ("error", translate("You do not have write access to this resource"));
      }
    }
  }
  
  
  // Convert resource.
  string convert = request->query ["convert"];
  if (convert != "") {
    string confirm = request->query ["confirm"];
    if (confirm == "") {
      Dialog_Yes dialog_yes = Dialog_Yes ("manage", translate("Would you like to convert this resource to a Bible?"));
      dialog_yes.add_query ("convert", convert);
      page += dialog_yes.run ();
      return page;
    } if (confirm == "yes") {
      if (access_bible_write (request, convert)) {
        tasks_logic_queue (CONVERTRESOURCE2BIBLE, {convert});
        redirect_browser (request, journal_index_url ());
        return "";
      } else {
        view.set_variable ("error", translate("Insufficient privileges"));
      }
    }
  }
  
  
  vector <string> resources = database_usfmresources.getResources ();
  vector <string> resourceblock;
  for (auto & resource : resources) {
    resourceblock.push_back ("<p>");
    resourceblock.push_back ("<a href=\"?delete=" + resource + "\" class=\"deleteresource\" title=\"" + translate("Remove") + "\">");
    resourceblock.push_back (emoji_wastebasket ());
    resourceblock.push_back ("</a>");
    resourceblock.push_back ("<a href=\"?convert=" + resource + "\" class=\"convertresource\" title=\"" + translate("Convert") + "\">");
    resourceblock.push_back ("♻");
    resourceblock.push_back ("</a>");
    resourceblock.push_back (resource);
    resourceblock.push_back ("</p>");
  }
  view.set_variable ("resourceblock", filter_string_implode (resourceblock, "\n"));

  
  page += view.render ("resource", "manage");
  page += Assets_Page::footer ();
  return page;
}
示例#24
0
// This function runs the sprint burndown history logger for $bible.
// If no $bible is passed, it will do all Bibles.
// If $mail is true, it will mail the burndown chart to the subscribed users.
// If $mail is false, it decides on its own whether to mail the chart to the users.
void sprint_burndown (string bible, bool email)
{
  int localseconds = filter_date_local_seconds (filter_date_seconds_since_epoch ());
  int year = filter_date_numerical_year (localseconds);
  int month = filter_date_numerical_month (localseconds);
  int monthday = filter_date_numerical_month_day (localseconds); // 1 to 31.
  int weekday = filter_date_numerical_week_day (localseconds); // 0 (for Sunday) through 6 (for Saturday).
  int hour = filter_date_numerical_hour (localseconds);
  bool sprintstart = false;
  bool sprintfinish = false;

  // Every Friday at 2 PM (14:00h) it sends email about the sprint progress.
  if ((weekday == 5) && (hour == 14)) email = true;
  // On the first business day of the month, at 10 AM, send email about the start of the sprint.
  if (filter_date_is_first_business_day_of_month (monthday, weekday) && (hour == 10)) {
    email = true;
    sprintstart = true;
  }
  // On the last business day of the month, at 2 PM (14:00h), send email about the end of the sprint.
  if ((monthday == filter_date_get_last_business_day_of_month (year, month)) && (hour == 14)) {
    email = true;
    sprintfinish = true;
  }
  
  
  // Determine what to do, or to quit.
  if (!email && !sprintstart && !sprintfinish) {
    if (hour != 1) return;
  }
  

  Database_Logs::log ("Updating Sprint information", Filter_Roles::manager ());

  Webserver_Request request;
  Database_Mail database_mail = Database_Mail (&request);
  Database_Sprint database_sprint = Database_Sprint ();

  
  // Determine year / month / day of the current sprint.
  // If this script runs from midnight till early morning,
  // it applies to the day before.
  // If the script runs during the day, it applies to today.
  if (hour <= 6) {
    localseconds -= (3600 * 6);
  }
  year = filter_date_numerical_year (localseconds);
  month = filter_date_numerical_month (localseconds);
  monthday = filter_date_numerical_month_day (localseconds); // 1 to 31.
  
  
  vector <string> bibles = {bible};
  if (bible == "") {
    bibles = request.database_bibles()->getBibles ();
  }
  

  for (auto bible : bibles) {

    
    // Get the total number of tasks for this sprint,
    // and the average percentage of completion of them,
    // and store this information in the sprint history table.
    vector <int> ids = database_sprint.getTasks (bible, year, month);
    vector <int> percentages;
    for (auto id : ids) {
      percentages.push_back (database_sprint.getComplete (id));
    }
    int tasks = ids.size ();
    int complete = 0;
    if (tasks != 0) {
      for (auto percentage : percentages) complete += percentage;
      complete = round ((float) complete / (float) tasks);
    }
    database_sprint.logHistory (bible, year, month, monthday, tasks, complete);
    
    
    // Send email if requested.
    if (email) {
      if (tasks) {
        // Only mail if the current sprint contains tasks.
        string scategories = Database_Config_Bible::getSprintTaskCompletionCategories (bible);
        vector <string> categories = filter_string_explode (scategories, '\n');
        int category_count = categories.size();
        int category_percentage = round (100 / category_count);
        vector <string> users = request.database_users ()->getUsers ();
        for (auto user : users) {
          if (request.database_config_user()->getUserSprintProgressNotification (user)) {
            
            string subject = translate("Team's progress in Sprint");
            if (sprintstart) subject = translate("Sprint has started");
            if (sprintfinish) subject = translate("Sprint has finished");
            subject +=  " | " + bible;
            
            vector <string> body;
            
            body.push_back ("<h3>" + translate("Sprint Planning and Team's Progress") + " | " + bible + "</h3>");
            body.push_back ("<table class='burndown'>");
            vector <int> tasks = database_sprint.getTasks (bible, year, month);
            for (auto id : tasks) {
              body.push_back ("<tr>");
              string title = database_sprint.getTitle (id);
              body.push_back ("<td>" + title + "</td>");
              int complete = database_sprint.getComplete (id);
              string text;
              for (int i = 0; i < round (complete / category_percentage); i++) text.append ("▓");
              for (int i = 0; i < category_count - round (complete / category_percentage); i++) text.append ("▁");
              body.push_back ("<td>" + text + "</td>");
              body.push_back ("</tr>");
            }
            body.push_back ("</table>");
            
            body.push_back ("<h3>" + translate("Sprint Burndown Chart - Remaining Tasks") + "</h3>");
            string burndownchart = sprint_create_burndown_chart (bible, year, month);
            body.push_back ("<p>" + burndownchart + "</p>");
            
            if (!body.empty ()) {
              string mailbody = filter_string_implode (body, "\n");
              if (!client_logic_client_enabled ()) database_mail.send (user, subject, mailbody);
            }
            
          }
        }
      } else {
        
        // Since there are no tasks, no mail will be sent: Make a logbook entry.
        Database_Logs::log ("No tasks in this Sprint: No email was sent");
      }
    }
  }
}
示例#25
0
// handleEmailNew - handles an email received from $from with subject $subject and body $body.
// Returns true if the mail was processed, else false.
// The email is considered to have been processed if it created a new Consultation Note.
bool Notes_Logic::handleEmailNew (string from, string subject, string body)
{
  // Webserver request.
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  // Store the original subject.
  string originalSubject = subject;
  // Check that the subject indicates that a new consultation note is to be created.
  size_t pos = unicode_string_casefold (subject).find ("new note");
  if (pos == string::npos) return false;
  // There is a new note. Remove that bit from the subject.
  if (pos > 0) subject.erase (0, pos + 8);
  // Clean the subject line.
  subject = filter_string_trim (subject);
  subject = filter_string_str_replace (".", " ", subject);
  subject = filter_string_str_replace (":", " ", subject);
  subject = filter_string_str_replace ("   ", " ", subject);
  subject = filter_string_str_replace ("  ", " ", subject);
  // Check that the from address of the email belongs to an existing user.
  from = filter_string_extract_email (from);
  if (!request->database_users()->emailExists (from)) return false;
  string username = request->database_users()->getEmailToUser (from);
  // Extract book, chapter, verse, and note summary from the subject
  int book = -1;
  int chapter = -1;
  int verse = -1;
  string summary;
  vector <string> subjectlines = filter_string_explode (subject, ' ');
  if (!subjectlines.empty()) {
    book = filter_passage_interpret_book (subjectlines[0]);
    subjectlines.erase (subjectlines.begin());
  }
  if (!subjectlines.empty()) {
    chapter = convert_to_int (subjectlines[0]);
    subjectlines.erase (subjectlines.begin());
  }
  if (!subjectlines.empty()) {
    verse = convert_to_int (subjectlines[0]);
    subjectlines.erase (subjectlines.begin());
  }
  summary = filter_string_implode (subjectlines, " ");
  // Check book, chapter, verse, and summary. Give feedback if there's anything wrong.
  string noteCheck;
  if (book <= 0) noteCheck.append (translate("Unknown book"));
  if (chapter < 0) {
    noteCheck.append (" ");
    noteCheck.append (translate("Unknown chapter"));
  }
  if (verse < 0) {
    noteCheck.append (" ");
    noteCheck.append (translate("Unknown verse"));
  }
  if (summary.empty ()) {
    noteCheck.append (" ");
    noteCheck.append (translate("Unknown summary"));
  }
  // Mail user if the note could not be posted.
  Database_Mail database_mail = Database_Mail (webserver_request);
  if (noteCheck != "") {
    subject = translate("Your new note could not be posted");
    database_mail.send (username, subject  + ": " + originalSubject, noteCheck);
    return false;
  }
  // Clean the email's body.
  body = filter_string_extract_body (body);
  // Post the note.
  string sessionuser = request->session_logic()->currentUser ();
  request->session_logic()->setUsername (username);
  Database_Notes database_notes = Database_Notes(webserver_request);
  string bible = request->database_config_user()->getBible ();
  int identifier = database_notes.storeNewNote (bible, book, chapter, verse, summary, body, false);
  handlerNewNote (identifier);
  request->session_logic()->setUsername (sessionuser);
  // Mail confirmation to the username.
  if (request->database_config_user()->getUserNotifyMeOfMyPosts (username)) {
    subject = translate("Your new note was posted");
    database_mail.send (username, subject + ": " + originalSubject, body);
  }
  // Log operation.
  Database_Logs::log ("New note posted : " + body);
  // Job done.
  return true;
}
示例#26
0
string sync_changes (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  Sync_Logic sync_logic = Sync_Logic (webserver_request);
  Database_Modifications database_modifications;

  // Check on the credentials.
  if (!sync_logic.credentials_okay ()) return "";

  // Bail out if the change notifications are not now available to clients.
  if (!config_globals_change_notifications_available) {
    request->response_code = 503;
    return "";
  }
  
  // Client makes a prioritized server call: Record the client's IP address.
  sync_logic.prioritized_ip_address_record ();

  // Get the relevant parameters the client may have POSTed to us, the server.
  string user = hex2bin (request->post ["u"]);
  int action = convert_to_int (request->post ["a"]);
  int id = convert_to_int (request->post ["i"]);

  switch (action) {
    case Sync_Logic::changes_delete_modification:
    {
      // The server deletes the change notification.
      database_modifications.deleteNotification (id);
      Database_Logs::log ("Client deletes change notification from server: " + convert_to_string (id), Filter_Roles::translator ());
      request->database_config_user ()->setChangeNotificationsChecksum ("");
      return "";
    }
    case Sync_Logic::changes_get_checksum:
    {
      // The server responds with the possibly cached total checksum for the user's change notifications.
      string checksum = request->database_config_user ()->getChangeNotificationsChecksum ();
      if (checksum.empty ()) {
        checksum = Sync_Logic::changes_checksum (user);
        request->database_config_user ()->setChangeNotificationsChecksum (checksum);
      }
      return checksum;
    }
    case Sync_Logic::changes_get_identifiers:
    {
      // The server responds with the identifiers of all the user's change notifications.
      vector <int> ids = database_modifications.getNotificationIdentifiers (user, false);
      string response;
      for (auto & id : ids) {
        if (!response.empty ()) response.append ("\n");
        response.append (convert_to_string (id));
      }
      return response;
    }
    case Sync_Logic::changes_get_modification:
    {
      // The server responds with the relevant data of the requested modification.
      vector <string> lines;
      // category
      lines.push_back (database_modifications.getNotificationCategory (id));
      // bible
      lines.push_back (database_modifications.getNotificationBible (id));
      // book
      // chapter
      // verse
      Passage passage = database_modifications.getNotificationPassage (id);
      lines.push_back (convert_to_string (passage.book));
      lines.push_back (convert_to_string (passage.chapter));
      lines.push_back (passage.verse);
      // oldtext
      lines.push_back (database_modifications.getNotificationOldText (id));
      // modification
      lines.push_back (database_modifications.getNotificationModification (id));
      // newtext
      lines.push_back (database_modifications.getNotificationNewText (id));
      // Result.
      return filter_string_implode (lines, "\n");
    }
  }

  
  // Bad request.
  // Delay a while to obstruct a flood of bad requests.
  this_thread::sleep_for (chrono::seconds (1));
  request->response_code = 400;
  return "";
}
示例#27
0
string styles_indexm (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  
  string page;
  
  Assets_Header header = Assets_Header (translate("Styles"), webserver_request);
  header.addBreadCrumb (menu_logic_settings_menu (), menu_logic_settings_text ());
  header.addBreadCrumb (menu_logic_settings_styles_menu (), menu_logic_styles_text ());
  page = header.run ();
  
  Assets_View view;
  
  Database_Styles database_styles;
  
  string username = request->session_logic ()->currentUser ();
  int userlevel = request->session_logic ()->currentLevel ();
  
  if (request->post.count ("new")) {
    string name = request->post["entry"];
    // Remove spaces at the ends of the name for the new stylesheet.
    // Because predictive keyboards can add a space to the name,
    // and the stylesheet system is not built for whitespace at the start / end of the name of the stylesheet.
    name = filter_string_trim (name);
    vector <string> existing = database_styles.getSheets ();
    if (find (existing.begin(), existing.end (), name) != existing.end ()) {
      page += Assets_Page::error (translate("This stylesheet already exists"));
    } else {
      database_styles.createSheet (name);
      database_styles.grantWriteAccess (username, name);
      styles_sheets_create_all ();
      page += Assets_Page::success (translate("The stylesheet has been created"));
    }
  }
  if (request->query.count ("new")) {
    Dialog_Entry dialog_entry = Dialog_Entry ("indexm", translate("Please enter the name for the new stylesheet"), "", "new", "");
    page += dialog_entry.run();
    return page;
  }
  
  if (request->query.count ("delete")) {
    string del = request->query ["delete"];
    if (del != "") {
      string confirm = request->query ["confirm"];
      if (confirm == "yes") {
        bool write = database_styles.hasWriteAccess (username, del);
        if (userlevel >= Filter_Roles::admin ()) write = true;
        if (write) {
          database_styles.deleteSheet (del);
          database_styles.revokeWriteAccess ("", del);
          page += Assets_Page::success (translate("The stylesheet has been deleted"));
        }
      } if (confirm == "") {
        Dialog_Yes dialog_yes = Dialog_Yes ("indexm", translate("Would you like to delete this stylesheet?"));
        dialog_yes.add_query ("delete", del);
        page += dialog_yes.run ();
        return page;
      }
    }
  
  }
  // Delete empty sheet that may have been there.
  database_styles.deleteSheet ("");

  vector <string> sheets = database_styles.getSheets();
  vector <string> sheetblock;
  for (auto & sheet : sheets) {
    sheetblock.push_back ("<p>");
    sheetblock.push_back (sheet);
    bool editable = database_styles.hasWriteAccess (username, sheet);
    if (userlevel >= Filter_Roles::admin ()) editable = true;
    // Cannot edit the Standard stylesheet.
    if (sheet == styles_logic_standard_sheet ()) editable = false;
    if (editable) {
      sheetblock.push_back ("<a href=\"sheetm?name=" + sheet + "\">[" + translate("edit") + "]</a>");
    }
    sheetblock.push_back ("</p>");
  }
  
  view.set_variable ("sheetblock", filter_string_implode (sheetblock, "\n"));

  page += view.render ("styles", "indexm");
  
  page += Assets_Page::footer ();
  
  return page;
}
示例#28
0
string user_account (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  
  string page;

  Assets_Header header = Assets_Header (translate("Account"), webserver_request);
  header.addBreadCrumb (menu_logic_settings_menu (), menu_logic_settings_text ());
  page = header.run ();

  Assets_View view;

  string username = request->session_logic()->currentUser ();
  string email = request->database_users()->get_email (username);

  bool actions_taken = false;
  vector <string> success_messages;

  // Form submission handler.
  if (request->post.count ("submit")) {
    bool form_is_valid = true;
    string currentpassword = request->post ["currentpassword"];
    string newpassword     = request->post ["newpassword"];
    string newpassword2    = request->post ["newpassword2"];
    string newemail        = request->post ["newemail"];
  
    if ((newpassword != "") || (newpassword2 != "")) {
      if (newpassword.length () < 4) {
        form_is_valid = false;
        view.set_variable ("new_password_invalid_message", translate("Password should be at least four characters long"));
      }
      if (newpassword2.length () < 4) {
        form_is_valid = false;
        view.set_variable ("new_password2_invalid_message", translate("Password should be at least four characters long"));
      }
      if (newpassword2 != newpassword) {
        form_is_valid = false;
        view.set_variable ("new_password2_invalid_message", translate("Passwords do not match"));
      }
      if (!request->database_users()->matchUserPassword (username, currentpassword)) {
        form_is_valid = false;
        view.set_variable ("current_password_invalid_message", translate("Current password is not valid"));
      }
      if (form_is_valid) {
        request->database_users()->set_password (username, newpassword);
        actions_taken = true;
        success_messages.push_back (translate("The new password was saved"));
      }
    }
  
    if (newemail != "") {
      if (!filter_url_email_is_valid (newemail)) {
        form_is_valid = false;
        view.set_variable ("new_email_invalid_message", translate("Email address is not valid"));
      }
      if (!request->database_users()->matchUserPassword (username, currentpassword)) {
        form_is_valid = false;
        view.set_variable ("current_password_invalid_message", translate("Current password is not valid"));
      }
      if (form_is_valid) {
        Confirm_Worker confirm_worker = Confirm_Worker (webserver_request);
        string initial_subject = translate("Email address verification");
        string initial_body = translate("Somebody requested to change the email address that belongs to your account.");
        string query = request->database_users()->updateEmailQuery (username, newemail);
        string subsequent_subject = translate("Email address change");
        string subsequent_body = translate("The email address that belongs to your account has been changed successfully.");
        confirm_worker.setup (newemail, initial_subject, initial_body, query, subsequent_subject, subsequent_body);
        actions_taken = true;
        success_messages.push_back (translate("A verification email was sent to ") + newemail);
      }
    }
  
    if (!actions_taken) {
      success_messages.push_back (translate("No changes were made"));
    }
  
  }

  view.set_variable ("username", filter_string_sanitize_html (username));
  view.set_variable ("email", filter_string_sanitize_html (email));
  string success_message = filter_string_implode (success_messages, "\n");
  view.set_variable ("success_messages", success_message);
  if (!actions_taken) view.enable_zone ("no_actions_taken");

  page += view.render ("user", "account");

  page += Assets_Page::footer ();

  return page;
}
void Database_NoteAssignment::assignees (string user, vector <string> assignees)
{
  filter_url_file_put_contents (path (user), filter_string_implode (assignees, "\n"));
}
示例#30
0
// This function creates a text-based burndown chart for sprint $bible / $year / $month.
string sprint_create_burndown_chart (string bible, int year, int month)
{
  // Get the seconds for the first of the month.
  int seconds = filter_date_seconds_since_epoch (year, month, 1);
  
  // The business days in the month for on the X-axis.
  vector <int> days_in_month;
  for (unsigned int day = 1; day <= 31; day++) {
    int mymonth = filter_date_numerical_month (seconds);
    if (mymonth == month) {
      if (filter_date_is_business_day (year, month, day)) {
        days_in_month.push_back (day);
      }
    }
    seconds += 86400;
  }
  
  // Assemble history of this sprint.
  Database_Sprint database_sprint = Database_Sprint ();
  vector <Database_Sprint_Item> history = database_sprint.getHistory (bible, year, month);
  map <int, int> data;
  for (auto day : days_in_month) {
    data [day] = 0;
    for (auto item : history) {
      if (day == item.day) {
        int tasks = item.tasks;
        int complete = item.complete;
        tasks = round (tasks * (100 - complete) / 100);
        data [day] = tasks;
      }
    }
  }
  
  vector <string> lines;
  lines.push_back ("<table class='burndown'>");
  lines.push_back ("<tr>");
  for (auto element : data) {
    int tasks = element.second;
    string text;
    for (int i = 0; i < tasks; i++) text.append ("▓<br>");
    lines.push_back ("<td class='day'>" + text + "</td>");
  }
  lines.push_back ("</tr>");
  
  // Write number of days along the x-axis.
  lines.push_back ("<tr>");
  for (auto element : data) {
    int day = element.first;
    lines.push_back ("<td class='day'>" + convert_to_string (day) + "</td>");
  }
  lines.push_back ("</tr>");
                                      
  // Write "days" below the x-axis.
  lines.push_back ("<tr>");
  int columncount = data.size ();
  string text = translate("days");
  lines.push_back ("<td colspan=\"" + convert_to_string (columncount) + "\">" + text + "</td>");
  lines.push_back ("</tr>");
                                    
  lines.push_back ("</table>");
                                                                      
  string chart = filter_string_implode (lines, "\n");
  return chart;
}