Система создания отчетов (Text Report System)

Опубликовано May 24, 2013 в Тестовые проекты (2-3 часа) | 5 коммент.

, ,

Система создания отчетов (Text Report System)

Тестовое задание: система создания отчетов

Данное задание долгое время служило нам верой и правдой – по результатам его выполнения мы приняли на работу несколько разработчиков. И вот пришло время его опубликовать вместе с нашим вариантом решения. Кроме того будут  рассмотрены типичные способы реализации, которые мы считаем ошибками или недочетами.

Постановка задачи

Необходимо разработать  упрощенный вариант Report System для создания параметризованных текстовых отчетов.

Входные данные

  • Файл с шаблоном отчета.
  • INI-файл, содержащий значения текстовых параметров. Формат этого файла – произвольный и подразумевается, что ошибок форматирования в нем не будет.

Выходные данные

  • Файл со сгенерированным отчетом (можно отчет выводить просто на  экран через cout).
  • Лог-файл, содержащий сообщения об ошибках и записи об успешной генерации отчета (лог-файл также можно  выводить просто на  экран через cout).

Файл “Шаблон отчета”

Шаблон отчета – это простой текстовый файл. Проблему локализации можно не решать – все на английском. В этом файле в произвольном тексте размещены параметры в виде следующей конструкции:

Можно считать, что символы “{}” никогда не появляются в произвольном тексте.

Обязательные параметры отмечаются символом “*”. Обязательным является тот параметр, без значения которого отчет генерироваться не должен. Для обязательных текстовых параметров их значение обязательно должно присутствовать в INI-файле.  Если параметр не является обязательным и его значение не удалось получить – в отчет вставляется пустая строка.

Ниже приведен пример шаблона отчета

Здравствуйте {*user_name},

Большое спасибо, что вы решили воспользоваться услугами нашей компании.
Ваш заказ номер {*order_number} будет доставлен через 10 рабочих дней после {current_date}.

 С Уважением, Администрация интернет магазина.

 Типы параметров отчета

Параметры отчета могут быть двух видов

  • Текстовые параметры. В этом случае для работы с ними достаточно совпадения имени параметра в файле шаблона отчета и в INI-файле, если параметр обязательный.
  • Функциональные параметры. Представляют собой результат вызова предопределенной функции.  Допустимы только зарегистрированные в программе имена подобных параметров, каждому из которых соответствует вызов определенной функции. При попадании имени функционального параметра в INI-файл должна генерироваться ошибка, а отчет не создается. (В рамках тестового задания должна быть реализована только одна функция – возвращающая current_date. Формат даты времени – произвольный, такой как вернет системная функция, ничего менять не нужно).

Необходимо разработать структуру данных для хранения параметров в памяти и регистрации функциональных параметров. Для экономии времени – инициализация параметров может быть выполнена прямо в коде С++ (без чтения из INI-файла).

Логирование

Как часть проекта необходимо разработать класс, реализующий функциональность логирования. Для простоты лог сообщений и ошибок может выводиться на экран через cout. Логируется факт успешной генерации отчета и перечисленные ниже ошибки.

  • Если в процессе генерации отчета любой из обязательных параметров не получил значения.
  • Если имя зарегистрированного функционального параметра попало в INI-файл.
  • Если вызов функции, инициированный функциональным параметром привел к ее внутреннему исключению.

При возникновении любой из этих ошибок отчет не создается, работа прекращается после обнаружения первой по ходу дела ошибки.

Возможные дополнительные вопросы

  • Как бы вы реализовали хранение параметров в шаблоне отчета при условии, что символы “{}” могут присутствовать в произвольном тексте?
  • Как расширить вашу реализацию для добавления новых типов параметров?

Автор публикации:

5 Коммент. : “Система создания отчетов (Text Report System)”

  1. Игорь says:

    могу предложить такую реализацию, хотя с VCL компонентами было бы все намного проще. Создаем список параметров и их значений, программно добавляем функциональные параметры и их номера по которым будет происходить вызов функций, загружаем из ini-файла список параметров, далее посимвольно перебрасываем шаблон в отчет, до вхождения символа ‘{‘ считываем идентификатор до символа ‘}’ по полученному идентификатору выполняем проверку и замену идентификатора на его значение из списка, если он функциональный – вызываем в блоке switch необходимую функцию.
    файл cpp

    //---------------------------------------------------------------------------
     
    #pragma hdrstop
    #include <dos.h>
    #include <Unit1.h>
    //---------------------------------------------------------------------------
     
    #pragma argsused
    int main(int argc, char* argv[])
    {
            ParamList *List1;
            char ch, IdStr[32];
     
            List1 = new ParamList();
    //---äîáàâëÿåì ôóíêöèîíàëüíûå ïàðàìåòðû-----
            List1->Add(1, "current_date");
     
    //---Çàíîñèì äàííûå èç èíè-ôàéëà------------
            try{
                    List1->Create("param.ini");
     
                    ifstream SrcTempl ("templ.txt");
                    if(!SrcTempl)
                            throw "File \"templ\" Open Error";
     
                    ofstream Report ("report.txt");
                    if(!Report)
                            throw "File \"report\" Open Error";
     
                    do
                    {
                            ch = SrcTempl.get();
     
                            if(ch == '{')
                            {
                                    for(int i = 0; i < 32; i++)
                                    {
                                            ch = SrcTempl.get();
                                            if(ch == '}')
                                            {
                                                    IdStr[i] = '';
                                                    break;
                                            }
                                            IdStr[i] = ch;
                                    }
                                    if(List1->Replace(IdStr))
                                    {
                                            switch(IdStr[0])//Îáðàáîòêà ôóíêöèîíàëüíûõ ïàðàìåòðîâ
                                            {
                                                    case 1:
                                                            AddDate(&Report);
                                            }
                                    }
                                    else
                                            Report << IdStr;
     
                                    continue;
                            }
                            if(!SrcTempl.eof())
                                    Report << ch;
                    }while(!SrcTempl.eof());
     
                    SrcTempl.close();
                    Report.close();
            cout << endl << "Finish the report";
            cin >> ch;
            }
            catch(char *th)
            {
                    cout << th;
            }
            delete List1;
            return 0;
    }

    Файл h

    #include <fstream.h>
    #include <iostream.h>
     
    void AddDate(ofstream *OutRep)
    {
            struct date d;
            getdate(&d);
            if(d.da_day < 10)
                    *OutRep << '0';
            *OutRep << (int)d.da_day << '.';
            if(d.da_mon < 10)
                    *OutRep << '0';
            *OutRep << (int)d.da_mon << '.' << d.da_year;
    }
    //-----------------------------------
    struct ParUnit
    {
            char *Input, *Out;
            bool Functional;
            ParUnit *Next;
     
            ParUnit(int, int);
            ~ParUnit();
    };
     
    ParUnit::ParUnit(int i, int o)
    {
            Input = new char[i];
            Out   = new char[o];
    }
     
    ParUnit::~ParUnit()
    {
            delete [] Input;
            delete [] Out;
    }
     
     
    class ParamList
    {
            ParUnit *Head, *Current;
            int Count;
     
            public:
            ParamList();
            ~ParamList();
     
            void Add(int, const char *, int, const char *);
            void Add(int, const char *);
            void Check();
            void Create(const char*);
            bool Replace(char*);
    };
     
    ParamList::ParamList()
    {
            Count = 0;
    }
     
    ParamList::~ParamList() 
    {
            Current = Head;
            for(int i=1; i < Count; i++)
            {
                    Current = Current->Next;
                    delete Head;
                    Head = Current;
            }
            delete Head;
    }
     
    void ParamList::Add(int CIS, const char *IS, int COS, const char *OS)
    {
            Current = Head;
            for(int i=1; i < Count; i++)
                    Current = Current->Next;
     
            if(Count)
            {
                    Current->Next = new ParUnit(CIS+1, COS+1);
                    Current = Current->Next;
            }
            else
            {
                    Head = new ParUnit(CIS+1, COS+1);
                    Current = Head;
            }
            Count++;
     
            strncpy(Current->Input, IS, CIS);
            strncpy(Current->Out, OS, COS);
            Current->Input[CIS] = '';
            Current->Out[COS] = '';
            Current->Functional = false;
    }
     
    void ParamList::Add(int NumF, const char *FuncId)
    {
            char FN[1];
            FN[0] = (char)NumF;
            Add(strlen(FuncId), FuncId, 1, FN);
            Current->Functional = true;
    }
     
    void ParamList::Check()
    {
            ParUnit *Test = Head;
     
            for(int i = 1; i < Count; i++)
            {
                    if(!strcmp(Test->Input, Current->Input))
                    {
                            Current = Test;
                            if(Test->Functional)
                                    throw "Functional Id in the ini-fine.";
                            else
                                    throw "This ID is already available.";
                    }
                    Test = Test->Next;
            }
    }
     
    void ParamList::Create(const char* path)
    {
            char ch, strid[64], strfield[64];
            int i = 0, j = 0;
            bool field = false;
     
            ifstream Src (path);
     
            if(!Src)
                    throw "File Open Error";
            do
            {
                    ch = Src.get();
     
                    if(ch == '=')
                    {
                            field = true;
                            strid[i] = '';
                            ch = Src.get();
                    }
                    if(ch == '\n')
                    {
                            field = false;
                            strfield[j] = '';
                            Add(i,strid,j,strfield);
                            Check();
                            i = 0;
                            j = 0;
                            continue;
                    }
     
                    if(field)
                            strfield[j++] = ch;
                    else
                            strid[i++] = ch;
     
            }
            while(!Src.eof());
    }
     
    bool ParamList::Replace(char* src)
    {
            ParUnit *Test = Head;
     
            for(int i = 0; i < Count; i++)
            {
                            if(!strcmp(Test->Input, src))
                            {
                                    strcpy(src, Test->Out);
                                    return Test->Functional;
                            }
                    Test = Test->Next;
            }
            throw "ID not found";
    }
  2. Галина says:

    Спасибо за решение, оно выглядит работоспособным. Вот какие бы были замечания, если бы оно проверялось как тестовое задание.
    1)Нет использования STL. Для нас это важно и важен выбор правильного готового контейнера, а также привветствуется использование типа string вместо char*. Вместо этого создается собственный контейнер. Вероятно, вы могли бы использовать компоненты VCL как раз для решения этих вопросов?
    2) Перекодирование ключей параметров в их цифровые коды совершенно излишне. Если выбрать подходящий ассоциативных контейнер то эта необходимость полностью отпадает.
    3) Вся логика построена на if/switch. Важным моментом, который мы всегда пытаемся прояснить являетя умение перевести эту логику на использование ООП решений.

    • Игорь says:

      1) решение было построено при использовании самых простых элементов.
      2) при добавлении новых ключей, код не меняется, их просто можно добавить во входной файл
      3) вывод сделан, больше уклон к ООП и СТЛ, спасибо.

  3. galileo says:

    Еще один подход к решению задачи.

    #include <iostream>
    #include <fstream>
    #include <map>
    #include <ctime>
    #include <string>
    using namespace std;
     
    class Exception {
        string message;
        public:
     
        Exception(const char *mes) : message(mes) {}
     
        string getMessage() {
            return message;
        }
    };
     
    class Logger {
     
        Logger() {}
     
        public:
     
        static Logger& getInstance() {
            static Logger logger;
            return logger;
        }
     
        void write(const string &s) {
            cout << s;
        }
    };
     
    class Function {
        public:
        virtual string run() = 0;
    };
     
    class CurrentDateFunction : public Function {
     
        string run() {
            time_t rawtime;
            struct tm *timeinfo;
            time(&rawtime);
            timeinfo = localtime(&rawtime);
            return asctime(timeinfo);
        }
    };
     
    class FunctionalVariable : public Function {
        private:
        string variableValue;
     
        public:
        FunctionalVariable(string value) : variableValue(value) {}
     
        string run() {
            if (variableValue.empty()) throw Exception("Empty value set for variable\n");
            return variableValue;
        }
    };
     
    class Context {
        map<string, Function*> functions;
     
        public:
        void addFunction(string functionName, Function* functionObject, bool checkIfAlreadyExists = false)
        {
            if (checkIfAlreadyExists && functionExists(functionName))
                throw Exception("Such entity already exists\n");
            functions[functionName] = functionObject;
        }
     
        bool functionExists(string functionName) {
            return functions.count(functionName);
        }
     
        string executeFunction(string functionName) {
            Function *func = functions[functionName];
            return func->run();
        }
    };
     
    class Parser {
        Context context;
        ifstream *fileToParse;
     
        void parseLine(string& line) {
            int startPos = 0, endPos;
     
            while ((startPos = line.find('{', startPos)) != string::npos) {
                bool isRequired = (line[startPos + 1] == '*');
     
                endPos = line.find('}');
     
                string entityName = line.substr(startPos + 1 + isRequired, endPos - startPos - 1 - isRequired);
                string value;
     
                if (context.functionExists(entityName)) {
                    value = context.executeFunction(entityName);
                }
                else {
                    if (isRequired)
                        throw Exception("Required entity is missing\n");
                }
     
                line.replace(startPos, endPos - startPos + 1, value);
                startPos += value.size();
            }
        }
     
        public:
     
        Parser(const char* fileNameToParse, Context &context) {
            fileToParse = new ifstream(fileNameToParse, ifstream::in);
            this->context = context;
        }
     
        string parse() {
            string line, outData;
     
            while (!fileToParse->eof() && !fileToParse->fail()) {
                getline(*fileToParse, line);
                parseLine(line);
                outData += line;
                outData += "\n";
            }
     
            return outData;
        }
    };
     
    int main()
    {
        Logger logger = Logger::getInstance();
     
        try {
            Context context;
            context.addFunction("current_date", new CurrentDateFunction(), false);
            context.addFunction("user_name", new FunctionalVariable("John"));
            context.addFunction("order_number", new FunctionalVariable("OrderNumber"));
     
            Parser *parser = new Parser("file_to_parse.txt", context);
            cout << parser->parse() << endl;
        }
        catch(Exception &e) {
            logger.write("Error: ");
            logger.write(e.getMessage());
            return 1;
        }
     
        logger.write("File was successfully parsed\n");
     
        return 0;
    }
  4. Решение задачи про генерацию отчета:

     
    файл Functions.h
     
    #include<fstream>
    #include <windows.h>
    #include<map>
    #include<string>
    #include<iostream>
    #include<set>
    using namespace std;
    class Parameters{
    public:
    	map<string, string> param;
    	set<string> func_set;
    	void add_func_param(string func_param);
    	void getparam();
    	void showparam();	
    };
    class Logger{
    public:
    	Logger(string outcome);
    };
    class ReportGenerate{
    public:
    	Parameters par;
    	string text_init;
    	string text_mod;
    		ReportGenerate();
    	void init_Generate();
    	void mod_Generate();
    	void text_init_show();		
    	void text_mod_show();	
    };
     
    файл Functions.cpp
     
    #include "stdafx.h"
    #include "Functions.h"
    #include<fstream>
    #include <windows.h>
    #include<map>
    #include<string>
    #include<iostream>
    #include<set>
    using namespace std;
     
    void Parameters::add_func_param(string func_param){
    	func_set.insert(func_param);
    }
    void Parameters::getparam(){
    	ifstream param_open("Parameters.txt");
    	if(!param_open) {
            cerr << "File error." << endl;
            return;
        } else cout<<"File is opened"<<endl;
    	string str1, str2;
    	while(!param_open.eof()){	
    		param_open>>str1>>str2;
    		param.insert(make_pair(str1, str2));
    	}
    	param_open.close();
    }
    void Parameters::showparam(){
    for(std::map<std::string, std::string>::iterator it=param.begin(); it!=param.end(); ++it)
    std::cout<<it->first<<"\t "<<it->second<<std::endl;
    	}
     
    	Logger::Logger(string outcome){
    		ofstream logfile("Report.log");
    		if(!logfile) {
    			cerr << "File error." << endl;
    			 return;
    		 }
    	logfile.clear();
    	cout<<outcome<<"\n"; 
    	logfile<<outcome;
    	logfile.close();
    	}
     
     
    ReportGenerate::ReportGenerate(){
    	par.add_func_param("current_date");
    	par.getparam();
    	init_Generate();
    		};
    void ReportGenerate::init_Generate(){
    ifstream initial("Template.txt");
     
    if(!initial) {
            cerr << "File error." << endl;
            return;
        }
     
    //modified.clear();
     text_init="";
    string temp;
    //string text_init1;
    while(initial>>temp){
    	temp+=" ";
    	text_init+=temp;
    	string str=".";
    	if(!(temp.find_first_of('.')==string::npos))
    		text_init=text_init+"\n";
    		}
    initial.close();
    }
    void ReportGenerate:: mod_Generate(){
    	int size_text=text_init.size();
    	int i=0;
    	int first_pos=0;
    	int second_pos=0;
    	 text_mod=text_init;
    	 ofstream modified("New.txt");
    if(!modified) {
            cerr << "File error." << endl;
            return;
        }
    modified.clear();
     
     
    //modified<<text_mod;
    //modified.close();
    int k=par.param.size()+par.func_set.size();
    	while(i<size_text){
    		if(k==0) break;
    		if(text_mod.find_first_of('{',i)!=string::npos){
    			first_pos=text_mod.find_first_of('{', i);			
    				if(text_mod.find_first_of('}', first_pos)!=string::npos){
    					second_pos=text_mod.find_first_of('}', first_pos);
    					string string_param;					
    					if (text_mod[first_pos+1]!='*') string_param=text_mod.substr(first_pos+1, second_pos-first_pos-1);
    						else string_param=text_mod.substr(first_pos+2, second_pos-first_pos-2);
    					//Check whether parameter exists in a map
    					if(text_mod[first_pos+1]='*'&&!par.param.count(string_param)&&!par.func_set.count(string_param)){
    							string error= "Mandatory parameter can not be initialized. Report won't be generated";
    							Logger log(error);
    							modified.close();
    							return;
    					}
    					if(par.param.count(string_param)&&par.func_set.count(string_param)){
    							string error= "Functional parameter is in INI file. Report won't be generated";	
    							Logger log(error);
    							modified.close();
    							return;					
    					}
    					if(string_param=="current_date"){
    							SYSTEMTIME st;
    							GetLocalTime(&st);
    							int year=st.wYear;
    							int month=st.wMonth;
    							int day=st.wDay;
    							string_param=to_string(day);
    							string_param+=".";
    							string_param+=to_string(month);
    							string_param+=".";
    							string_param+=to_string(year);
    							//Replace parameter by it's value
    							text_mod.replace(first_pos, second_pos-first_pos+1, string_param);
    					}						
    					//Replace parameter by it's value
    					else
    						text_mod.replace(first_pos, second_pos-first_pos+1, par.param[string_param] );						
    					i=second_pos+1;
    					k--;
    				}
    		}
     
    	}
    modified<<text_mod;
    modified.close();
    string success= "Report has been generated successfully";
    Logger log(success);
    }
    void ReportGenerate::text_init_show(){	
    		cout<<text_init;
    	}
    void ReportGenerate::text_mod_show(){	
    		cout<<text_mod;
    	}
    файл Report.cpp
    #include "stdafx.h"
    #include "Functions.h"
     
    int _tmain(int argc, _TCHAR* argv[])
    {
    	ReportGenerate rep;
    	rep.mod_Generate();
    	getchar();
    	return 0;
    }

    Логирование идет в файл – Report.log, параметры считываются из файла Parametеrs.txt(имя-параметр-структура файла на каждой строке), отчет генерируется в файл New.txt, шаблон отчета считывается в строку из Template.txt

    Максим

Оставить комментарий

Ваш адрес email не будет опубликован.


*