C++. Application for processing input files and generate output html-files

В начале года на глаза попалась одна задачка. Суть состояла в следующем: разработать приложение, на вход которого подаются входные файлы, с размером не более 2Мб, один из которых - текстовый файл, состоящий из некоторого текста, второй - справочник, состоящий из различных слов (слово занимает ровно одну строку). Справочник полностью должен быть загружен в память машины, в отличие от первого входного файла, его нельзя сразу загружать. Приложение должно обрабатывать первый входной файл с учетом справочника и генерировать выходные html-файлы, валидные по отношению к различным браузерам (IE, Firefox, Chrome и др.) и иметь способность загрузки браузером за время не более 5-10 секунд. Содержимое этих выходных файликов должно в точности соответствовать содержимому первого входного файла с одним исключением: нужно пометить жирным и наклонным шрифтом все слова из справочника. Количество выходных html-файлов должно определяться пользователем при определении желаемого максимального количества строк в этих выходных файликах. Помимо всего прочего необходимо реализовать перехват и обработку исключительных ситуаций.

Решение построил на основе объектно-ориентированного подхода. Для этого было создано два класса: class Search (основной класс) и class Exceptions (очень упрощенный класс, без обработки, только отлавливает и выводит сообщение об исключении). Эти классы приведены ниже



Search.h

#ifndef SEARCH_H
#define SEARCH_H


#include <iostream>
#include <fstream>
#include <string>
#include <cctype>
#include <sstream>
#include <map>


using namespace std;

class Search {

 // Допустимое количество строк в выходных файлах
 int N;

 // Текущая позиция обрабатываемой строки входного файла
 int current_pos;

 // Нумератор ( необходим для именования выходных файлов )
 int numerator;

 // Префикс ( статическая часть имени выходных файлов )
 string prefix;

 // Расширение выходных файлов ( default - .html )
 string file_extension;

 // Буферная строка ( используем для сохранения и записи обработанных строк )
 string current_str_buffer;

 // Ассоциативный map-контейнер ( слово - тегированное слово ). Для загрузки справочника
 map<string, string> catalogBuffer;

public:
 Search() : N(0), current_pos(0), numerator(0) {
  // Первоначальная инициализация
  Search::set_file_extension();
  Search::set_name_prefix();
  Search::set_num_lines();
 };

 // Определение имен входных файлов
 string get_ifstream_file_name();

 // Определение имен выходных файлов
 string get_ofstream_file_name();

 // Определение префикса (статическая часть) имен выходных файлов
 void set_name_prefix();

 
// Определение расширения выходных файлов
 void set_file_extension();

 // Определение допустимого максимального количества строк в выходных файлах
 void set_num_lines();

 // Загрузка справочника в память
 void get_catalog(ifstream &file);

 // Открытие файла (ifstream поток)
 void open(ifstream &file);

 // Открытие файла (ofstream поток)
 void open(ofstream &file);

 // Закрытие файла (ifstream поток)
 void close(ifstream &file);

 // Закрытие файла (ofstream поток)
 void close(ofstream &file);

 // Разбор строки по словам и поиск по справочнику. Результат заносится в буферную строку
 void find(string &temp);

 // Запись текущей буферной строки в выходной файл
 void write(ofstream &output);

 // Обработка входного файла по справочнику и генерация выходных файлов
 void get_output(ifstream &input, ofstream &output);

};

#endif SEARCH_H



 





Exceptions.h

#ifndef Exceptions_H
#define Exceptions_H


#include <iostream>

using namespace std;

class Exceptions {

 char str_what[80];


public:

 Exceptions() {*str_what = 0;}

 Exceptions(char *str) {strcpy(str_what, str);}

 void what() {
 
  cout << "Exception: " << str_what << endl;
 
 }


};

#endif Exceptions_H

class Search ориентирован на прямую работу с входными файлами, поэтому большинству методов передаются соответствующие ссылки на наши файлики (если точнее - на ifstream и ofstream потоки). 

class Exceptions очень прост, но можно развить и далее, пока ограничился простым уровнем.

Реализация класса Search и реализация программы SearchMain представлено ниже

Search.cpp

#include "Search.h"
#include "Exceptions.h"


// Определение имен входных файлов
string Search::get_ifstream_file_name() {
 string file_name;
 cout << "Enter file name:" << endl;
 cin >> file_name;
 return file_name;
}


// Определение имен выходных файлов
string Search::get_ofstream_file_name() {
 ++Search::numerator;
 // Конвертация нумератора - numerator - в строку типа string ( integer -> string )
 stringstream stream_converter;
 if (!(stream_converter << Search::numerator)) {
  throw Exceptions("internal fail of convertation");
 }
 string temp_converter = stream_converter.str();

// Формируем полное имя выходного файла
 string temp = Search::prefix + temp_converter + Search::file_extension;
 return temp;
}


// Определение префикса (статическая часть) имен выходных файлов
void Search::set_name_prefix() {
 string temp;
 cout << "Enter name prefix for naming output files:" << endl;
 cin >> temp;

// Небольшая проверка на валидность введенного префикса имени файла
 for (int i = 0; i < temp.length(); i++) {
  if (!isalnum(temp[i])) {
   throw Exceptions("name prefix content is not valid symbols");
  }
 }
 Search::prefix = temp;
}


// Определение расширения выходных файлов
void Search::set_file_extension() {
 // По умолчанию...
 Search::file_extension = ".html";
}


// Определение допустимого максимального количества строк в выходных файлах
void Search::set_num_lines() {
 string temp;
 // Флаг валидности значения ( введено ли целое число )
 bool stop;
 for (;;) {
  cout << "Enter max string number lines in output file(s):" << endl;
  stop = true;
  cin >> temp;
  // Небольшой анализатор валидности введенного пользователем значения
  for (int i = 0; i < temp.length(); i++) {
   // Если число
   if (isdigit(temp[i])) {
    // Недопустимо
    if (temp[0] == '0') {
     stop = false;
     break;
    }
   }
   // Если не число
   else {
    stop = false;
    break;
   }
  }
  // Если введено валидное значение (целое число), выходим из цикла
  if (stop) {
   break;
  }
  cout << "Illegal value (not an integer or illegal character). Please, repeat!" << endl;
 }
 // Конвертируем валидное значение в целое ( string -> integer )
 // и сохраняем значение
 int n;
 stringstream stream_converter;
 stream_converter << temp;
 if (!(stream_converter >> n)) {
  throw Exceptions("internal fail of convertation");
 }
 else {
  Search::N = n;
 }
}


// Загрузка справочника в память
void Search::get_catalog(ifstream &file) {
 if(!file.fail()) {
  // Наполняем map - контейнер catalogBuffer словами из справочника catalog.txt
  for(string search_word, wrapper; !file.eof();) {
   // Считываем слово из справочника
   file >> search_word;
   // Небольшая защита от несоответствия структуры входного справочника
   // на наличие пустых строк между словами
   if (!search_word.empty()) {
    // Оборачиваем в требуемые html - теги
    wrapper = "<b><i>" + search_word + "</i></b>";
    // Сохраняем результат в map - контейнер catalogBuffer
    Search::catalogBuffer[search_word] = wrapper;
   }
   // Сброс содержимого строк
   search_word.clear();
   wrapper.clear();
  }
 }
 else {
  throw Exceptions("file is failed");
 }
}


// Открытие файла ( ifstream поток )
void Search::open(ifstream &file) {
 // Получаем имя файла входного файла
 string file_name = Search::get_ifstream_file_name();
 // Открытие файла
 file.open(file_name.c_str(), ios::in);
 if (!file.fail()) {
  // Определение размера входного файла, b
  file.seekg (0, ios::end);
  double length = file.tellg();
  file.seekg (0, ios::beg);
  if ((length/1024/1024) > 2) {
   cout << "size of file ( " << length << "Mb ) bigger than available ( 2Mb )" << endl;
   throw Exceptions("file is failed");
  } else {
   cout << "size of file in available zone" << endl;
  }
 }
 else {
  cout << "file is not open, check your file!" << endl;
  throw Exceptions("file is failed");
 }
}


// Открытие файла ( ofstream поток )
void Search::open(ofstream &file) {
 // Получаем имя файла выходного файла
 string file_name = Search::get_ofstream_file_name();
 // Открытие файла
 file.open(file_name.c_str(), ios::out);
 if (file.fail()) {
  throw Exceptions("file is failed");
 }
}


// Закрытие файла (ifstream поток)
void Search::close(ifstream &file) {
 // Закрытие входного файла
 file.close();
}


// Закрытие файла (ofstream поток)
void Search::close(ofstream &file) {
 // Закрытие выходного файла
 file.close();
}


// Разбор строки по словам и поиск по справочнику. Результат заносится в буферную строку 
void Search::find(string &temp) {
 ++Search::current_pos;
 // При возникновении неожиданных исключительных ситуаций перехватываем их и снова перенапрвляем
 try {
  // Итератор для map-контейнера catalogBuffer
  map<string, string>::iterator iteratorCatalogBuffer;
  string search_word, wrapper;
  // Проссматриваем слова строки temp в справочнике catalogBuffer
  for (iteratorCatalogBuffer=Search::catalogBuffer.begin(); iteratorCatalogBuffer!=Search::catalogBuffer.end(); ++iteratorCatalogBuffer) {
   search_word = iteratorCatalogBuffer->first;
   wrapper = iteratorCatalogBuffer->second;
   // Поиск соответствий слов от начала и до конца по справочнику.
   // При нахождении соответствия производим замену на тегированное слово из справочника ( обертка html - тегами )
   for (int found = temp.find(search_word); found!=string::npos; found=temp.find(search_word,found+wrapper.length())) {
    temp.replace(found, search_word.length(), wrapper);
   }
  }
 } catch (...) {
  throw Exceptions("Error in find method!");
 }
 // Добавляем тег переноса html строк
 temp+= "<br>";
 // Итоговая строка готова для записи. Сохраняем в буферную строку
 Search::current_str_buffer = temp;
}


// Запись текущей буферной строки в выходной файл
void Search::write(ofstream &output) {
 // output.fail()
 if (!output.fail()) {
  // Запись содержимого буферной строки в выходной файл
  output.write(Search::current_str_buffer.c_str(), Search::current_str_buffer.length());
 }
 else {
  throw Exceptions("write to file is failed");
 }
}


// Обработка входного файла по справочнику и генерация выходных html - файлов
void Search::get_output(ifstream &input, ofstream &output) {
 for (string temp; !input.eof(); temp.clear()) {
  // При текущей позиции обрабатываемой строки входного файла в начале 
  if (Search::current_pos == 0) {
   // Открытие выходного файла
   Search::open(output);
  }
  // При достижении текущей позиции обрабатываемой строки входного файла
  // заданной границы допустимого количества строк в выходных файлах
  if (Search::current_pos == Search::N) {
   // Сброс текущей позиции обрабатываемой строки входного файла в начало
   Search::current_pos = 0;
   // Закрытие выходного файла
   Search::close(output);
   continue;
  }
  // Извлечение текущей строки из входного файла для последующего поиска соответствий по справочнику
  getline(input, temp);
  // Поиск соответствий слов текущей строки по справочнику.
  // Результат заносится в буферную строку
  Search::find(temp);
  // Запись буферной строки в выходной файл
  Search::write(output);
 }
}


SearchMain.cpp
 
#include "Search.h"
#include "Exceptions.h"


int main() {

 // Входной файл (*.txt) input.txt
 ifstream input;
 // Справочник (*.txt) catalog.txt
 ifstream catalog;
 // Выходной файл (*.html) prefix1.txt
 ofstream output;

 // Создаем объект
 Search *search = new Search();

 try {
 
  // Открываем входные файлы
  cout << "Input file. ";
  search->open(input);
  cout << "Catalog file. ";
  search->open(catalog);
 
  // Загружаем словарь справочника в память
  cout << "Download catalog to memory..." << endl;
  search->get_catalog(catalog);
 
  // Начинаем обрабатывать входной файл с учетом справочника
  // и генерировать выходные html - файлы
  cout << "Processing..." << endl;
  search->get_output(input, output);


 } catch(Exceptions e) {
   e.what();
 }


 // Закрываем входные файлы
  search->close(input);
  search->close(catalog);
  delete search; 

 return 0;
}

Ниже приведен алгоритм сие программы

1. Создаем объект класса Search и ссылку на него. В это время будут вызваны методы, которые затребуют ввода префикса выходных файлов (это статическая часть имени) и желаемое максимальное количество строк в выходных html-файлах

2. Далее создаем ifstream и ofstream потоки (точнее - объекты), которые передаем классу Search для открытия этих потоков (открытие входных файлов)

3. Если справочник открылся, то загружаем его полностью в память машины. Загружается в память ассоциативный map-контейнер, которое получаем из разбора слов, в итоге получаем пару <слово - тегированное слово>. Тегированное слово - это слово обернутое в html-теги для жирности и наклонности шрифта, например - <b><i>word</i></b>

4.1 Далее начинаем построчно считывать содержимое первого входного файла, которое в свою очередь разбирается на слова, которые сравниваются со словами справочника. Если найдено соответствие, то заменяем слово в считанной строке на тегированное слово из справочника (по сути - из map-контейнера)

4.2 После полного разбора, строка записывается в буфер класса (буферная строка)

4.3 Открываем выходной файл.

4.4 Записываем содержимое буфера (буферная строка)

4.5 Закрываем выходной файл

Продолжаем считывать следующую строку входного файла. Далее все повторяется до тех пор, пока не будет достигнут конец входного файла. Стоит отметить, что на этих шагах при каждом считывании строки входного файла производится инкрементирование внутренней переменной класса (Search::current_pos) и сравнение с числом допустимых максимальных строк в выходных файлах (Search::N), которое было введено во время создания объекта нашего класса. В сравнении определяется, или открыть новый выходной файл, или же закрываем выходной файл, или продолжаем работать.

5. Закрываем входные файлы (потоки) и уничтожаем объект класса Search

6. Программа завершена

Также отмечу, что поиск соответствий слов по справочнику очень прост: каждое слово приводит к полному просмотру справочника, от начала и до конца, здесь можно поработать над оптимизацией, например, проводить поиск по первым символам в слове подобно поиску в базе данных по индексу. Есть над чем подумать :)

Если запстить прогу, то вот что получим.


Исходники можно скачать по ссылке - download.