内联 CSV 类
想快速为您的项目添加 CSV 功能?这个类正是为您准备的!
引言
CSV 有时用于存储循环数据,甚至可以轻松地与 Excel 或 Calc 等 Office 应用程序交换数据。创建或提取 CSV 并不是最难的任务,但没有人想浪费时间在上面。
使用这个类,您只需几行代码即可为您的项目添加 CSV 功能!
背景
我们都同意,如果您想以文本格式存储或共享数据,与 XML 或 JSON 等命名字段格式相比,CSV 不是最佳格式,但 CSV 并没有完全消失,仍然有一些优点。
优点
- 它仍然更“易于阅读”:没有字段名称和标记字符,CSV 文件比 XML 文件更容易理解。此外,它的表格格式也更容易阅读 => 一行就是一个行。
- 它易于编写代码:即使没有这个小类,您也可以使用 C/C++ 的标准库自己完成,使用诸如打印格式和字符串连接之类的函数。
- 它仍然很轻量:它永远不会像二进制那样轻量,但它仍然比 XML 或 JSON 更轻量,XML 或 JSON 集成了字段名称和标记字符,这些字符通常比数据本身更重。
- 它适用于所有表格应用程序。像 Excel 或 Calc 这样的应用程序将集成此格式。这意味着您可以轻松地与这类应用程序交换数据。
缺点
- 没有“命名字段”:这似乎显而易见,但如果没有命名字段,您必须从源到目标按相同的顺序格式化数据,否则会弄乱您的数据。这正是您在二进制文件中可能遇到的问题。
- 它是依赖于区域设置的:使用逗号作为分隔符基本上是一个好主意。不幸的是,逗号对于某些其他语言(例如我的母语)来说是小数点,这意味着您必须更改分隔符(我的语言使用分号),这会在语言之间造成不兼容。例如,在美国计算机上生成的 CSV 文件与法国计算机上的文件不兼容。
使用代码
这里介绍的 CSV 类由两个主要类组成
CSVTable
包含并管理整个 CSV 文档。CSVRow
管理 CSV 文档中的一行。CLocalStack
这个第三个类是代码中用于堆叠区域设置的辅助类,您无需在自己的代码中使用它。
下面的类图将向您展示所有类及其方法的概览。
所有代码都是内联编写的。这意味着您无需在项目中添加任何代码文件。只需在代码的开头包含 CSVTable.h
。
#include "CSVTable.h"
以下是如何创建 CSV 数据的简单方法
CCSVTable table;
CCSVRow* pRow;
double real(1.23);
int integer(123);
TCHAR* string = _T("My string");
//AddRow method will return pointer of created row...
pRow = table.AddRow()->AddField(_T("First row"));
pRow->AddField(_T("real data: %f"), real); //AddField can be use like a format print
//... this means you can cascade field
table.AddRow()->AddField(_T("Second row"))->AddField(_T("%05d"), integer)->AddField(string);
//AddFields method allow you to add multiple fields at once.
//This is very usefull for header containing no data...
pRow = table.AddRow();
//... last argument of this method must always be NULL
pRow->AddFields(_T("Column01"), _T("Column02"), _T("Column03"), _T("Column04"), NULL);
//You can use [] operator to access to a specific row...
table[0]->AddField(_T("integer data"), integer);
//... or use GetAt method
pRow = table.GetAt(0);
//Create CSV file...
table.SaveToFile(_T("C:\\test.csv"));
//... or copy it to clipboard...
table.CopyToClipboard();
//... or simply get text
TCHAR* szOutput = table.PublishCSV();
当然,这个类是双向的,这意味着您可以从 CSV 格式的文本中加载数据
CCSVTable tableImport;
//You can get data from file...
tableImport.LoadFromFile(_T("D:\\test.csv"));
//... or from clipboard...
tableImport.GetFromClipboard(); //=> return TRUE if there are CSV data in clipboard
//... or from text
tableImport.LoadTableFromText(szOutput);
管理 CSV 区域设置
上面的示例代码将根据计算机的区域设置创建 CSV 数据,这确保了您的进程与计算机上其他应用程序之间的互操作性。
但是,如果您像我一样在法国计算机上工作并将文件发送给美国合作伙伴,则可能会遇到与其他区域设置的不兼容问题。
如果您想自己管理 CSV 区域设置,可以在类构造函数中使用一些附加参数
//Specifying different locales for CSV data
CCSVTable tableFR(';', _T("French_France"));
CCSVTable tableUS(',', _T("English_United States"));
tableFR.AddRow()->AddField(_T("%0.2f"), real)->AddField(_T("%0.2f"), real+2.0);
tableUS.AddRow()->AddField(_T("%0.2f"), real)->AddField(_T("%0.2f"), real+2.0);
output << _T("French locales will return this row: ") << tableFR.PublishCSV() << _T("\n");
output << _T("US locales will return this row: ") << tableUS.PublishCSV() << _T("\n");
/* Output will be:
French locales will return this row: 1,23;3,23
US locales will return this row: 1.23,3.23 */
如您所见,我们可以指定分隔符字符和用于数字格式化的区域设置。
关于 CLocaleStack 类的几点说明
如果您深入研究代码,您会注意到CLocalStack
类,它会在每次调用 AddField 方法时都会堆叠区域设置。
事实上,区域设置是通过调用setlocale方法来管理的。调用此方法时,您的区域设置将设置为整个线程,这意味着如果您为法语格式的 CSV 调用 AddField 方法,则代码中的所有打印格式都将返回法语格式的字符串,这肯定不是您想要的!
这就是为什么在进入 AddField 时推送区域设置,然后在离开方法时弹出区域设置的原因,因此您的应用程序仍然会具有指定的区域设置。
关注点
创建 CSV 似乎微不足道,实际上也是如此。
但是,如果您不关心区域设置,如果您使用此格式存储数据,则可能会遇到很大的问题。
这个轻量级类不使用 MFC 类,这意味着它可以用于非 MFC 代码,但需要最少的 ATL。
它可以在 MBCS 或 UNICODE 环境中使用,但文件输出为 ASCII 格式。如果您想使用 UTF8 或 UTF16 输出,请随意更改。
关于示例项目
有 3 个示例项目来演示这些类的使用方法
- consoleSample 是一个非 MFC 应用程序,其中包含本文中使用的所有代码。
- CSVClipboardListener 一个基于对话框的 MFC 应用程序,它侦听剪贴板,预期 CSV 数据。此应用程序演示 CSV 导入。
- InfoDir 一个第二个基于对话框的应用程序,它将查找目录的内容并将所有文件/文件夹信息导出为 CSV 格式。