📜 ⬆️ ⬇️

Outputting tabular data to the console, file or MS Excel in the style of C ++ threads

The note suggests a set of C ++ classes (performance tested in VS2008 and VS 2013; only C ++ 03 and STL is used) for outputting tabled formatting data to the std :: ostream stream. As is distributed.

st << "#" << "Property" << "Value" << "Unit"; enum {nr = 10}; for (int i = 0; i < nr; i++) { st << i + 1 << "Prop" << i << "Unit"; } 

The result will look like this:



Already in the process of preparing the notes, I found a similar project bprinter (I didn’t make a detailed comparison; there is a dependency on Boost :: Spirit, which is not always convenient), which is the equivalent of my file
')
StreamTable.h
 #ifndef __STREAM_TABLE_H #define __STREAM_TABLE_H #undef max #undef min #include <string> #include <vector> #include <algorithm> #include <iostream> //    #define CRLF "\n" //    //#define CRLF std::endl /** *        *     C++ */ class StreamTable { public: std::ostream &os_; StreamTable(std::ostream &os = std::cout, char delimRow = ' ', char delimCol = ' ') : borderExtOn_(true), delimRowOn_(true), delimRow_(delimRow), delimColOn_(true), delimCol_(delimCol), os_(os), colIndex_(0), firstCell_(1) {} virtual ~StreamTable() {} virtual std::ostream &os() const { return os_; } //  ? void MakeBorderExt(bool on) { borderExtOn_ = on; } //   void SetDelimRow(bool delimOn, char delimRow = ' ') { delimRowOn_ = delimOn; if (delimRowOn_) delimRow_ = delimRow; else if (!delimColOn_) MakeBorderExt(false); } //   void SetDelimCol(bool delimOn, char delimCol = ' ') { delimColOn_ = delimOn; if (delimColOn_) delimCol_ = delimCol; else if (!delimRowOn_) MakeBorderExt(false); } int AddCol(int colWidth, bool visible = true) { colWidth_.push_back(colWidth); visible_.push_back(visible); return colWidth_.back(); } void SetVisible(int col, bool flg) { visible_[col - 1] = flg; } void SetCols(int colCount, int colWidth = 0) { Clear(); for (int ic = 0; ic < colCount; ic++) { AddCol(colWidth); } } virtual void Clear() { colWidth_.clear(); visible_.clear(); colIndex_ = 0; firstCell_ = 1; } void AddEmptyRow() { for (int ic = 0; ic < (int)colWidth_.size(); ic++) { *this << ""; } } template <typename T> StreamTable &operator << (const T &obj) { Push(obj); return *this; } StreamTable &operator << (const std::string &s) { colWidth_[colIndex_] = std::max(colWidth_[colIndex_], (int)s.size() + 1); Push(s); return *this; } StreamTable &operator << (const char *s) { colWidth_[colIndex_] = std::max(colWidth_[colIndex_], (int)strlen(s) + 1); Push(s); return *this; } protected: int colIndex_; private: bool borderExtOn_; bool delimRowOn_; char delimRow_; bool delimColOn_; char delimCol_; std::vector<int> colWidth_; bool firstCell_; std::vector<int> visible_; template <typename T> void Push(const T &obj) { if (firstCell_) { if (borderExtOn_) MakeRowBorder(); firstCell_ = 0; } if (visible_[colIndex_]) { DelimCol(); os_.width(colWidth_[colIndex_]); os_.fill(' '); os_ << /*std::setiosflags(std::ios::left) << */obj; } if (++colIndex_ == (int)colWidth_.size()) { DelimCol(); if (delimRowOn_) MakeRowBorder(); else os_ << CRLF; colIndex_ = 0; } } void MakeRowBorder() { os_ << CRLF; DelimCol(); int ic; for (ic = 0; ic < (int)colWidth_.size(); ic++) { if (visible_[ic]) { os_.width(colWidth_[ic] + 1); os_.fill(delimRow_); DelimCol(); } } os_ << CRLF; } void DelimCol() { if (delimColOn_ && (borderExtOn_ || colIndex_)) os_ << delimCol_; else os_ << ' '; } //   StreamTable &operator = (const StreamTable &); }; #endif // __STREAM_TABLE_H 


The StreamTable class allows displaying table data line by line in the C ++ stream format. It accepts a link to std :: ostream in the constructor, so besides std :: cout (by default) you can write to the file by passing std :: ofstream &. It is useful when forming a log file with the results of the calculation.

 #include <sstream> #include "StreamTable.h" void TestStreamTable1() { StreamTable st(std::cout); st.AddCol(5); st.AddCol(15); st.AddCol(10); st.AddCol(10); //,      //st.Clear(); //st.SetCols(4, 10); //st.SetVisible(1, false);//   st.MakeBorderExt(true); st.SetDelimRow(true, '-');//st.SetDelimRow(false);// -  st.SetDelimCol(true, '|');//st.SetDelimCol(false);// -  //     st << "#" << "Property" << "Value" << "Unit"; enum {nr = 10}; for (int i = 0; i < nr; i++) { st << i + 1 << "Prop" << i << "Unit"; } } 

To enable the use of StreamTable for uploading to MS Excel, a special excelstream stream and an auxiliary class for working with MS Excel were implemented:

MSExcel.h file
 #pragma once #include <sstream> #import "C:\Program Files (x86)\Common Files\Microsoft Shared\Office12\MSO.DLL" \ rename("RGB","_RGB")\ rename("DocumentProperties", "_DocumentProperties")\ rename("SearchPath","_SearchPath") #import "C:\Program Files (x86)\Common Files\Microsoft Shared\VBA\VBA6\VBE6EXT.OLB" #import "C:\Program Files (x86)\Microsoft Office\Office12\EXCEL.EXE" \ rename("DialogBox","_DialogBox") \ rename("RGB","_RGB") \ rename("CopyFile", "_CopyFile") \ rename("ReplaceText", "_ReplaceText") \ no_auto_exclude #define THROW(msg) throw std::exception(msg); /** * */ class ExcelLoader { public: virtual ~ExcelLoader() { Close(); } Excel::_ApplicationPtr excel_; Excel::_WorksheetPtr sheet_; Excel::RangePtr range_; //  Excel,         void Detach() { if (!excel_) return; range_.Detach(); range_ = 0; sheet_.Detach(); sheet_ = 0; excel_.Detach(); excel_ = 0; ::CoUninitialize(); } //       LoadExcel void Close() { if (!excel_) return; try { excel_->DisplayAlerts[0] = false; excel_->Quit(); excel_->DisplayAlerts[0] = true; Detach(); //todo:  MS Excel 2010 ,  CoUninitialize     std::system("taskkill /F /IM Excel.exe"); //   EXCEL while (FindWindow("XLMAIN", NULL)) {}; } catch (_com_error &er) { THROW(er.ErrorMessage()); } } //     LoadExcel void Save() { if (!excel_) return; excel_->DisplayAlerts[0] = false; try { excel_->Save(); } catch (_com_error &er) { THROW(er.ErrorMessage()); } excel_->DisplayAlerts[0] = true; } // Excel      ws_index (>= 1)   fname void LoadExcel(const std::string &fname, int ws_index) { if (FAILED(::CoInitialize(NULL))) THROW("CoInitialize failure"); if (FAILED(excel_.CreateInstance("Excel.Application"))) { std::stringstream ss; ss << "CreateInstance failed: " << GetLastError(); std::string msg = ss.str(); THROW(msg.c_str()); } excel_->Visible[0] = TRUE; Excel::_WorkbookPtr book = excel_->Workbooks->Open(fname.c_str()); if (!book) THROW(std::string("Can't open ").append(fname).append(": Workbooks->Open method failed").c_str()); sheet_ = excel_->ActiveSheet; if (ws_index < 1 || excel_->Sheets->Count < ws_index) THROW("ws_index_ must be in [1, Sheets.Count]"); sheet_ = excel_->Sheets->Item[ws_index]; if (!sheet_) THROW("Failed to get a pointer to the active sheet"); range_ = sheet_->Cells; if (!range_) THROW("Failed to get a pointer to the cells on the active sheet"); } }; 


ExcelStream.h file
 #pragma once #include <sstream> #include "MSExcel.h" typedef char CharT; typedef std::char_traits<CharT> TraitsT; class excel_stringbuf : public std::basic_stringbuf<CharT, TraitsT> { public: static const char colDelim = '\t'; static const char rowDelim = '\n'; virtual ~excel_stringbuf() { sync(); } void SetRange(Excel::RangePtr pRange, int irow_offset, int icol_offset) { rng_ = pRange; //        irow_ = irow_offset; icol_offset_ = icol_offset; icol_ = icol_offset; } Excel::RangePtr GetRange() const { return rng_; } int &CurRow() { return irow_; } int &CurCol() { return icol_; } int sync() { output_string(str().c_str()); str(std::basic_string<CharT>());//   return 0; } protected: Excel::RangePtr rng_; int irow_; int icol_offset_; int icol_; //    isspace       bool IsSpace(char c) const { return (c == colDelim) || (c == rowDelim) || (c == '\r'); } /** *   s   Excel. *          '\t', *     - '\n'. * :   "1\t2\n3\t4 5"     * (1,1) = 1 * (1,2) = 2 * (2,1) = 3 * (2,2) = 4 5, *  (i,j) -   * todo:     / */ void output_string(const std::string &s); }; void excel_stringbuf::output_string(const std::string &s) { //    ,    (\n \t) std::string::const_iterator be = s.begin(); std::string::const_iterator en = s.end(); std::string::const_iterator it = be; while (it != en) { bool dump = false; bool isTab = false; bool isEnd = false; //       , //       (iRow, iCol) if (*it == colDelim) { isTab = true; dump = !IsSpace(*be); } else if (*it == rowDelim) { isEnd = true; dump = !IsSpace(*be); } else { //     ,    be if (IsSpace(*be)) be = it; if (it + 1 == en) { //       dump = true; //    ,  //it     it = en; } } if (dump) { //        [be, it) const std::string &item = s.substr(be - s.begin(), it - be); rng_->Item[irow_][icol_] = _variant_t(item.c_str()); } //       if (isTab) { icol_++; be = it; } else if (isEnd) { irow_++; icol_ = icol_offset_; be = it; } if (it == en) { //   break; } else it++; } } /** *     Excel     STL  */ class excelstream : public std::basic_ostream<CharT, TraitsT> { public: excelstream(Excel::RangePtr &rng, int irow_offset = 1, int icol_offset = 1) : std::basic_ostream<CharT, TraitsT>(&buf_) { buf_.SetRange(rng, irow_offset, icol_offset); } virtual ~excelstream() { flush(); } private: excel_stringbuf buf_; }; 


Attention, since in MSExcel.h is used #import, then for compilation it is necessary to register the actual paths to MSO.DLL, VBE6EXT.OLB, EXCEL.EXE in accordance with the installed version of MS Office.

 #include <sstream> #include "StreamTable.h" #include "ExcelStream.h" void TestStreamTable2() { // ExcelLoader xls; xls.LoadExcel("C:/log.xlsx", 1); excelstream os(xls.range_, 1, 1);//    StreamTable st(os); st.SetCols(4);//todo:   Excel-    st.MakeBorderExt(false);//,      st.SetDelimRow(false);//,      st.SetDelimCol(true, excel_stringbuf::colDelim);//, .. excel_stringbuf       colDelim //  st << "#" << "Property" << "Value" << "Unit"; enum { nr = 10 }; for (int i = 0; i < nr; i++) { st << i + 1 << "Prop" << i << "Unit"; } os.flush(); xls.Detach();// Detach  os  st  } 

The excelstream stream in the StreamTable works in such a way that its buffer parses the unloading string, in which the character '\ t' is interpreted as a transition to a column to the right, and '\ n' - as a character of transition to the next line. The example below is similar to TestStreamTable2 in result and shows the principle of forming such a string.

 #include <sstream> #include "ExcelStream.h" void TestStreamTable3() { ExcelLoader xls; xls.LoadExcel("C:/log.xlsx", 1); excelstream os(xls.range_, 1, 1); os << "#\tProperty\tValue\tUnit\n"; std::stringstream ss; enum { nr = 10 }; for (int i = 0; i < nr; i++) { os << i + 1 << "\tProp\t" << i << "\tUnit\n"; } os.flush(); xls.Detach();// Detach  os  st  } 


File with all examples of main.cpp
 #include <sstream> #include "StreamTable.h" #include "ExcelStream.h" void TestStreamTable1() { StreamTable st(std::cout); st.AddCol(5); st.AddCol(15); st.AddCol(10); st.AddCol(10); //,      //st.Clear(); //st.SetCols(4, 10); //st.SetVisible(1, false);//   st.MakeBorderExt(true); st.SetDelimRow(true, '-');//st.SetDelimRow(false);// -  st.SetDelimCol(false, '|');//st.SetDelimCol(false);// -  //     st << "#" << "Property" << "Value" << "Unit"; enum {nr = 10}; for (int i = 0; i < nr; i++) { st << i + 1 << "Prop" << i << "Unit"; } } void TestStreamTable2() { // ExcelLoader xls; xls.LoadExcel("C:/log.xlsx", 1); excelstream os(xls.range_, 1, 1);//    StreamTable st(os); st.SetCols(4);//todo:   Excel-    st.MakeBorderExt(false);//,      st.SetDelimRow(false);//,      st.SetDelimCol(true, excel_stringbuf::colDelim);//, .. excel_stringbuf       colDelim //  st << "#" << "Property" << "Value" << "Unit"; enum { nr = 10 }; for (int i = 0; i < nr; i++) { st << i + 1 << "Prop" << i << "Unit"; } os.flush(); xls.Detach();// Detach  os  st  } void TestStreamTable3() { ExcelLoader xls; xls.LoadExcel("C:/log.xlsx", 1); excelstream os(xls.range_, 1, 1); os << "#\tProperty\tValue\tUnit\n"; std::stringstream ss; enum { nr = 10 }; for (int i = 0; i < nr; i++) { os << i + 1 << "\tProp\t" << i << "\tUnit\n"; } os.flush(); xls.Detach();// Detach  os  st  } void main() { try { TestStreamTable1(); TestStreamTable2(); TestStreamTable3(); } catch(std::exception &ex) { std::cout << ex.what(); } } 



In conclusion, I note that with regard to excelstream, his work was not checked as a polymorphic pointer to std :: ostream, so in this part the class should be refined.

Source: https://habr.com/ru/post/346058/


All Articles