其实在很早之前,我就对MySQL的表格输出很感兴趣并想用C++实现,主要是因为它支持中文输出。因此前一段时间我实现了第一版的MyPrettyTable(简化版的Python的库PrettyTable?),然而令人失望的是它在含有中文汉字的情况下会发生混乱而无法对齐,于是我放弃了。不过昨天在写代码时偶然看到之前那个被搁置的project,于是不甘心的我再次重新实现了这个功能…其实就是利用的setw和left
Python中的PrettyTable
用C++写MyPrettyTable的代码格式是按照Python中的PrettyTable。因此MyPrettyTable相对来说比较简单
中文汉字处理问题
由于UTF-8编码是变长编码,通常汉字占三个字节,而英文占一个字节
在终端下显示汉字占两个字符宽,而英文字母只占一个字符宽
比如 string s = "好";
在Linux下 s.size() = 3 在Windows下 s.size() = 2
再比如下面的代码
string s1 = "你好世界" ; string s2 = "你好世" ; string s3 = "你好" ;int size = s1.size (); cout << size << endl; cout.fill ('.' ); cout << left << setw (size) << "" << endl; cout << left << setw (size) << s1 << endl; cout << left << setw (size) << s2 << endl; cout << left << setw (size) << s3 << endl;
在Windows下
8 ........ // 8 你好世界 // 2 x4=8 你好世.. // 2 x3+2 =8 你好.... // 2 x2+4 =8
然而在Linux下面却是
12 ... ... ... ... 你好世界xxxx 你好世... xxx 你好... ... xx
因此在Windows下,输出中文能够很好的对齐,但在Linux下就不是这样了。
Windows下的显示中文字符宽恰好是两个英文字母,而Linux下显示中文字符宽等于两个英文字母,但是编码时却是占3个字节!
当我将上面Linux下没有对齐的内容用 x 填充后发现 不足的填充 x 的个数等于中文汉字的个数 !
于是为了解决这个不对齐问题,我在处理setw()是采用了WIN32/__linux__宏区分不同的平台
padding_left (this ->m_ColumnsContent_max_len[i] + _get_Chinese_len(str));#ifdef WIN32 padding_left (this ->m_ColumnsContent_max_len[i]);#elif __linux__ padding_left (this ->m_ColumnsContent_max_len[i] + _get_Chinese_len(str));#endif
此时中文汉字的处理问题得以解决,但是如果还有其他一些中文标点符号呢?或者说怎么判断是否存在中文或中文标点符号呢?
因此我定义一个Sign.h头文件用于存放那些特殊的符号,以下为对应中文标点符号的unicode码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #ifndef PRETTYTABLE_SIGN_H #define PRETTYTABLE_SIGN_H enum { PC1=0x3002 u, PC2=0xFF1F u, PC3=0xFF01 u, PC4=0xFF0C u, PC5=0x3001 u, PC6=0xFF1B u, PC7=0xFF1A u, PC8=0x300C u, PC9=0x300D u, PC10=0x2018 u, PC11=0x2019 u, PC12=0xFF08 u, PC13=0xFF09 u, PC14=0x3014 u, PC15=0x3015 u, PC16=0x3010 u, PC17=0x3011 u, PC18=0x2014 u, PC19=0x2026 u, PC20=0x2013 u, PC21=0xFF0E u, PC22=0x300A u, PC23=0x300B u, PC24=0x3008 u, PC25=0x3009 u, };enum class Sign { PT_PLUS='+' , PT_H ='-' , PT_V ='|' , PT_DOT= '.' , PT_EQUAL= '=' , PT_SIG_1= '@' , PT_SIG_2= '#' , PT_SIG_3= '$' , PT_SIG_4= '%' , PT_SIG_5= '\\' , PT_SIG_6='*' , PT_SIG_7='/' };#endif
判断一个字符串中是否存在中文且存在多少个中文汉字或标点符号,我们需要将其转化为wchar_t 指针类型的字符串,函数StringToWString讲一个string类型字符串转化为wstring类型字符串。需要注意的是在Windows下mbstowcs是不安全的,需要替换为mbstowcs_s,不过这里我简单的禁止了该错误
#ifdef WIN32 #pragma warning (disable:4996) #endif wstring PrettyTable::StringToWString (const string & str) { size_t nLen=str.length ()*2 ; setlocale (LC_CTYPE,"" ); wchar_t *wcs=new wchar_t [nLen]; wmemset (wcs,0 ,nLen); size_t s; mbstowcs (wcs,str.c_str (),nLen); wstring w=wcs; delete []wcs; return w; }
判断字符串中中文汉字和标点符号数目
int PrettyTable::_get_Chinese_len(const string& str){ wstring ss=StringToWString (str); int sum=0 ; for (int i = 0 ; i <ss.length () ; ++i) { wchar_t wcs=ss.c_str ()[i]; if ((wcs>=0x4E00 u&&wcs<=0x9FFF u) ||wcs==PC1||wcs==PC2||wcs==PC3||wcs==PC4||wcs==PC5||wcs==PC6||wcs==PC7||wcs==PC8||wcs==PC9 ||wcs==PC10||wcs==PC11||wcs==PC12||wcs==PC13||wcs==PC14||wcs==PC15||wcs==PC16||wcs==PC17 ||wcs==PC18||wcs==PC19||wcs==PC20||wcs==PC21||wcs==PC22||wcs==PC23||wcs==PC24||wcs==PC25){ sum++; } } return sum; }
在添加一个Header时,我就获取其表头(列)的数目,并添加到 this->m_multiColumns
中,后续再每添加一行row时同时还要将row转化为column并在添加到this->m_multiColumns
中,为的是 获取每一列中字符串的最大宽度 使得整个表格对齐。
比如我要画一个表格头,我是按照一行一行去输出相应的数据并重定向到内部的 stringstream m_innerOss
注意的是,每个表头内边距为1个空格,因此我还定义了一个
#define PADDING_LEFT_RIGHT 2
表示内边距的大小
+----+ ------------------+----------+ ----------+---------------------+ | xx | xx | xx | xx | xx | +----+------------------+----------+----------+---------------------+
添加Rows & _draw_rows
这里我想说的是
set()中可以有两种写法
setw () = MaxSizeStr + String.Size () - StringCapacity (String)setw () = MaxSizeStr + Length (Chinese hans)
也就是说 setw()应该设置的值为 每一个this->m_multiColumns 中最大字符串宽度+中文汉字/标点符号数目
这个 StringCapacity 函数就是获取字符串中显示的字符宽度 <= string.size()
int PrettyTable::StringCapacity (const string & raw_str) {#ifdef WIN32 return raw_str.size ();#elif __linux__ return raw_str.size () - this ->_get_Chinese_len(raw_str);#endif }
结尾
我已经把代码放在 Github 上了,可以去看看代码,虽然写得有点糟… 不过核心部分还是利用C++的setw和left。
这个project算是到此结束了吧,不过以后有没有可能再继续添加一些东西就不知道了…
图片展示如下