65.9K
CodeProject 正在变化。 阅读更多。
Home

内联 CSV 类

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.46/5 (10投票s)

2016年5月25日

CPOL

4分钟阅读

viewsIcon

16571

downloadIcon

1176

想快速为您的项目添加 CSV 功能?这个类正是为您准备的!

引言

CSV 有时用于存储循环数据,甚至可以轻松地与 Excel 或 Calc 等 Office 应用程序交换数据。创建或提取 CSV 并不是最难的任务,但没有人想浪费时间在上面。

使用这个类,您只需几行代码即可为您的项目添加 CSV 功能!

背景

我们都同意,如果您想以文本格式存储或共享数据,与 XML 或 JSON 等命名字段格式相比,CSV 不是最佳格式,但 CSV 并没有完全消失,仍然有一些优点。

优点

  • 它仍然更“易于阅读”:没有字段名称和标记字符,CSV 文件比 XML 文件更容易理解。此外,它的表格格式也更容易阅读 => 一行就是一个行。
  • 它易于编写代码:即使没有这个小类,您也可以使用 C/C++ 的标准库自己完成,使用诸如打印格式和字符串连接之类的函数。
  • 它仍然很轻量:它永远不会像二进制那样轻量,但它仍然比 XML 或 JSON 更轻量,XML 或 JSON 集成了字段名称和标记字符,这些字符通常比数据本身更重。
  • 它适用于所有表格应用程序。像 Excel 或 Calc 这样的应用程序将集成此格式。这意味着您可以轻松地与这类应用程序交换数据。

缺点

  • 没有“命名字段”:这似乎显而易见,但如果没有命名字段,您必须从源到目标按相同的顺序格式化数据,否则会弄乱您的数据。这正是您在二进制文件中可能遇到的问题。
  • 它是依赖于区域设置的:使用逗号作为分隔符基本上是一个好主意。不幸的是,逗号对于某些其他语言(例如我的母语)来说是小数点,这意味着您必须更改分隔符(我的语言使用分号),这会在语言之间造成不兼容。例如,在美国计算机上生成的 CSV 文件与法国计算机上的文件不兼容。

使用代码

这里介绍的 CSV 类由两个主要类组成

  1. CSVTable 包含并管理整个 CSV 文档。
  2. CSVRow 管理 CSV 文档中的一行。
  3. 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 个示例项目来演示这些类的使用方法

  1. consoleSample 是一个非 MFC 应用程序,其中包含本文中使用的所有代码。
  2. CSVClipboardListener 一个基于对话框的 MFC 应用程序,它侦听剪贴板,预期 CSV 数据。此应用程序演示 CSV 导入。
  3. InfoDir 一个第二个基于对话框的应用程序,它将查找目录的内容并将所有文件/文件夹信息导出为 CSV 格式。
© . All rights reserved.