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

ExcelFormat 库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (117投票s)

2009年9月20日

CPOL

9分钟阅读

viewsIcon

1347642

downloadIcon

49774

ExcelFormat 允许使用 C++ 读取、写入和编辑 XLS (BIFF8 格式) 文件。

example1.png

引言

C++ 项目 BasicExcel 已经存在多年。它对于以 .xls 格式读取和写入 Excel 工作表非常有用。然而,它只包含非常基本的功能。缺少诸如使用字体进行文本格式化等功能,并且尚不支持显示格式和公式。本文涵盖了这些附加功能。新的派生项目 ExcelFormat 基于旧的 BasicExcel 代码,并且只添加了我自己需要的功能。如果您想了解我在哪里使用了新库,您可以查看 服务管理器卸载管理器 的导出功能。

XLS 格式

此库处理 BIFF8 XLS 文件格式的 Excel 文件。有关格式及其所有内部结构的详细文档,有两份文档可用

兼容性

您可以在 MS Windows 上使用 VC++ 6.0 SP3 或更高版本的 MSVC 编译器来使用该代码。还支持在 MacOSX 或 Linux 上使用 GCC,但存在一些限制。可能它甚至可以在 Sun Solaris 等其他环境中运行,但尚未测试。以下是各种环境及其状态的表格

小型 XLS 文件大型 XLS 文件
WIN32,使用 Windows API IStorage好的好的
WIN64,使用 Windows API IStorage好的好的
MacOS X / Linux 32 位,使用 BasicExcel CompoundFile 实现好的问题
MacOS X / Linux 64 位,使用 BasicExcel CompoundFile 实现好的问题

上述问题是由于底层 BasicExcel 库的 CompoundFile 实现不完整。我已经修复了 BasicExcel 代码中关于 RKValue 格式的数字和 64 位兼容性的一些部分,但当读取或写入使用 XBAT 或 SBAT 条目的大型 XLS 文件时,仍然会出现问题。

Using the Code

要使用新的格式化功能,首先创建一个 XLSFormatManager 对象,如 example1() 函数中所示,并将其附加到现有的 BasicExcel 对象

void example1(const char* path)
{
    BasicExcel xls;
 
    // create sheet 1 and get the associated BasicExcelWorksheet pointer
    xls.New(1);
    BasicExcelWorksheet* sheet = xls.GetWorksheet(0);
 
    XLSFormatManager fmt_mgr(xls);

您可以在源代码文件 Examples.cpp 中找到本文的所有示例。

要定义自定义字体,请创建一个 ExcelFont 对象并设置任何所需的属性,例如,粗体字的字体粗细

ExcelFont font_bold;
font_bold._weight = FW_BOLD;	// 700=bold, 400=normal

Excel 单元格的格式可以通过 CellFormat 对象定义,该对象包含所选字体和更多属性

CellFormat fmt_bold(fmt_mgr);
fmt_bold.set_font(font_bold);

准备好 CellFormat 后,您可以通过调用 SetFormat() 来选择 Excel 单元格的字体和显示设置

// Create a table containing a header row in bold and four rows below.
int col, row = 0;
 
for(col=0; col<10; ++col) {
    BasicExcelCell* cell = sheet->Cell(row, col);
 
    cell->Set("TITLE");
    cell->SetFormat(fmt_bold);
}
 
while(++row < 4) {
    for(int col=0; col<10; ++col)
        sheet->Cell(row, col)->Set("text");
}
 
++row;

文本颜色通过在 ExcelFont 中设置颜色索引来指定,例如

ExcelFont font_red_bold;
font_red_bold._weight = FW_BOLD;
font_red_bold._color_index = EGA_RED;
 
CellFormat fmt_red_bold(fmt_mgr, font_red_bold);
fmt_red_bold.set_color1(COLOR1_PAT_SOLID); 		// solid background
fmt_red_bold.set_color2(MAKE_COLOR2(EGA_BLUE,0));	// blue background

 
CellFormat fmt_green(fmt_mgr, ExcelFont().set_color_index(EGA_GREEN));
 
for(col=0; col<10; ++col) {
    BasicExcelCell* cell = sheet->Cell(row, col);
 
    cell->Set("xxx");
    cell->SetFormat(fmt_red_bold);
 
    cell = sheet->Cell(row, ++col);
    cell->Set("yyy");
    cell->SetFormat(fmt_green);
}

ExcelFormat.h 包含用于在枚举 EXCEL_COLORS 中定义基本调色板颜色的常量,您可以在调用 ExcelFont()::set_color_index() 时使用它们。宏 MAKE_COLOR2 接受两个颜色索引来指定图案线和图案背景颜色。作为调用 CellFormat::set_color1()CellFormat::set_color2() 的快捷方式,您还可以使用 CellFormat::set_background() 来定义具有纯背景颜色或着色图案的单元格。

在内存中创建和格式化 Excel 单元格后,您所要做的就是将新的 Excel 工作表保存为文件

    xls.SaveAs(path);
}

这足以快速概述如何使用新的 ExcelFormat 对象。在源代码文件 ExcelFormat.cpp 中,您可以找到更多示例。

ExcelFont

有九个字体属性可用

struct ExcelFont
{
    ExcelFont()
 
    ...
 
    wstring _name;
    short   _height;
    short   _weight;
    short   _options;
    short   _color_index;
    short   _escapement_type;
    char    _underline_type;
    char    _family;
    char    _character_set;
 
    ...
 
};

有关详细信息,请查看 XLS 文件格式文档

单元格格式

除了所选的 ExcelFont 和显示格式之外,还提供以下 CellFormat 属性

struct CellFormat
{
    CellFormat(XLSFormatManager& mgr)
 
    ...
 
    char    _alignment;
    char    _rotation;
    char    _text_props;
    int     _borderlines;
    int     _color1;
    short   _color2;
 
    ...
 
};

example2() 中,您可以看到如何更改字体和字号

ExcelFont font_header;
font_header.set_weight(FW_BOLD);
font_header.set_underline_type(EXCEL_UNDERLINE_SINGLE);
font_header.set_font_name(L"Times New Roman");  // font face "Times New Roman"

 
font_header.set_color_index(EGA_BLUE);
font_header._options = EXCEL_FONT_STRUCK_OUT;
 
CellFormat fmt_header(fmt_mgr, font_header);
fmt_header.set_rotation(30); // rotate the header cell text 30° to the left
 

int row = 0;
 
for(int col=0; col<10; ++col) {
    BasicExcelCell* cell = sheet->Cell(row, col);
 
    cell->Set("TITLE");
    cell->SetFormat(fmt_header);
}

example3() 函数演示了如何使用 CellFormat::set_format_string()BasicExcelCell::SetFormat() 来定义文本、数字和日期格式 string。格式 string 有一些预定义的常量

#define XLS_FORMAT_GENERAL      L"General"
#define XLS_FORMAT_TEXT         L"@"
#define XLS_FORMAT_INTEGER      L"0"
 
#define XLS_FORMAT_DECIMAL      L"0.00"
#define XLS_FORMAT_PERCENT      L"0%"
 
 
#define XLS_FORMAT_DATE         L"M/D/YY"
 
#define XLS_FORMAT_TIME         L"h:mm:ss"
#define XLS_FORMAT_DATETIME     L"M/D/YY h:mm"

但是,您可以使用任何有效的 Excel 格式 string 来定义自定义显示格式。

example4() 展示了如何在一个工作表中使用多种不同的字体和颜色

example4.png

公式

现在,在读取和写入 Excel 工作表时,公式将得到保留。您甚至可以通过调用 BasicExcelCell::SetFormula() 将公式存储到 Excel 单元格中。但是,目前,您要么必须从现有单元格中复制一个 Worksheet::CellTable::RowBlock::CellBlock::Formula 对象,要么必须自行定义,这相当棘手,因为 Excel 使用包含 RPN 标记的预解析公式。

关注点

对于那些对背景信息感兴趣的人,我想提供一个关于自前身库 BasicExcel 以来发生的变化的描述。

条件编译

存在两种不同的 BasicExcel 实现,一种用于 VC++ 6.0 版本,另一种用于更新的编译器。ExcelFormat 现在通过使用条件编译来处理这些差异,从而合并了这两个代码库。这大部分是通过此代码片段(以及使用新定义的宏进行的修改)在头文件中完成的

#if _MSC_VER<=1200    // VC++ 6.0

#pragma warning(disable: 4786)
 
#define LONGINT __int64
#define LONGINT_CONST(x) x
#define COMPOUNDFILE
 
#else    // newer Microsoft compilers

#define LONGINT long long
#define LONGINT_CONST(x) x##LL
#define COMPOUNDFILE CompoundFile::
 
#ifdef _DEBUG
#define _ITERATOR_DEBUG_LEVEL 0	// speedup iterator operations while debugging
#endif
 
#endif

为了区分使用 MSVC 的 MS Windows 环境和使用 GCC 的其他环境,会测试宏 _MSC_VER 是否存在。这在访问复合二进制文件格式时在 Windows API 和 BasicExcel 的 CompoundFile 实现之间进行切换。

#pragma warning 语句禁用了 VC++ 6.0 的编译器警告,原因是对象文件中存在长的编译器内部名称,这在使用 STL 类进行非平凡操作时会发生。

紧接着这些定义之后,是适用于 VS 2005 及更高版本的这些预处理器语句

#if _MSC_VER>=1400    		// VS 2005
#define _CRT_SECURE_NO_WARNINGS    	//MF
#define _SCL_SECURE_NO_WARNINGS    	//MF
 
#endif

它们禁用了 VC++ 安全库警告,因为 BasicExcel 代码尚未为这些新的运行时库添加做好准备。顺便说一下,MF 注释标记了我对旧 BasicExcel 代码的添加和修复。

此外,我通过修复数据类型消除了一些编译器警告。通常,这是从 int 等整型类型到 C 运行时库类型 size_t 的更改。

要在使用 GCC 时切换 32 位和 64 位模式,请使用编译器选项 -m32-m64,如源代码下载中的 Makefile 示例所示。在 VC++ 环境中,您可以在项目设置中选择目标环境。

新功能

除了上面描述的新 API,我必须在 BasicExcel 中添加代码以实现这些新功能

  • 从 XLS 文件读取和写入 Formula 结构
  • 定义、读取和写入 XLS 文件中的 FORMAT 结构
  • 获取/设置所有各种单元格类型的 BasicExcelCell 对象的 XF 索引值

格式存储结构

Excel 单元格在 BIFF8 文件格式中的格式信息使用所谓的 XF 索引存储。这指的是 XF(“扩展格式”)记录,该记录由以下成员组成

XF {
    short       fontRecordIndex     // FORMAT index
    short       formatRecordIndex   // FONT index

    short       protectionType
    char        alignment
    char        rotation
    char        textProperties
    char        usedAttributes
    int         borderLines
    int         colour1
    short       colour2
}

除了少数直接关联的属性(指定 Excel 单元格的对齐、旋转等)之外,还有两个索引值:fontRecordIndexformatRecordIndex。它们用于定义字体和显示格式描述。从整体来看,这种基于两级索引的格式体系结构能够以较小的文件大小和较低的内存使用率实现单元格格式化,因为 Excel 工作表中通常只使用少数几种不同的字体和显示格式。字体索引指向一个具有以下属性的 FONT 记录

FONT {
    short       height
    short       options
    short       colourIndex
    short       weight
    short       escapementType
    char        underlineType
    char        family
    char        characterSet
    char        unused
    SmallString name
}

第三个索引是一个特殊的索引。此格式索引与一个只包含索引本身和显示格式文本表示的记录相关联

FORMAT {
    short       index
    LargeString fmtstring
}

XLSFormatManager 管理这三个格式子结构,使用 C++ 结构 CellFormatExcelFont 来格式化 Excel 单元格

struct CellFormat  ->  XF {FORMAT index, FONT index, XF attributes}
 
struct ExcelFont   ->  FONT {FONT attributes}

当调用 CellFormat::set_font(const ExcelFont& font) 时,管理器类会搜索已注册的匹配字体描述。如果还没有,则会创建一个新的 FONT 记录存储在 Excel 工作表中。当调用 CellFormat::set_format_string(const wstring& fmt_str) 时,管理器类会搜索已注册的匹配显示格式 string。如果还没有,则会创建一个新的 FORMAT 记录存储显示格式 string。当通过调用 BasicExcelCell::SetFormat(const CellFormat& fmt)CellFormat 应用于单元格对象时,也会使用相同的策略:管理器类会搜索已注册的匹配 XF 描述,该描述具有相同的字体和格式索引以及匹配的 XF 属性。如果还没有,则会创建一个新的 XF 记录存储在 Excel 工作表中。最终,这将生成一个 FORMATFONTXF 记录列表,这些记录存储在 Excel 工作簿文件的标题中。每个单元格都通过存储一个相关的 XF 索引进行格式化,该索引确定所有单元格格式属性、字体和显示格式。

有关更多实现细节,请查阅源代码中的 ExcelFormat.hExcelFormat.cppBasicExcel.hppBasicExcel.cpp

内存使用

为了限制内存使用,使用引用计数 SmartPtr 来管理公式存储所需的堆结构。每个单元格只包含一个可选填充的指向 struct Formula 的指针。对于文本或数字单元格,不需要公式信息。因此智能指针保持为空,只包含值 NULL

在以下部分中,您可以看到 struct RefCnt 作为 struct Formula 的基础以及模板 struct SmartPtr 的实现,用于保存引用计数的堆对象

// reference counter for SmartPtr managed objects
struct RefCnt
{
    // On construction the reference counter
    // is initialized with an usage count of 0.
    RefCnt()
     :    _ref_cnt(0)
    {
    }
 
    int    _ref_cnt;
};
 
 
// reference counting smart pointer
template<typename T> struct SmartPtr
{
    // default constructor
    SmartPtr()
     :  _ptr(NULL)
    {
    }
 
    // The initialized SmartPtr constructor increments the reference counter
 
          // in struct RefCnt.
    SmartPtr(T* p)
     :  _ptr(p)
    {
        if (p)
            ++_ptr->_ref_cnt;
    }
 
    // The copy constructor increments the reference counter.
    SmartPtr(const SmartPtr& other)
     :    _ptr(other._ptr)
    {
        if (_ptr)
            ++_ptr->_ref_cnt;
    }
 
    // The destructor decreases the reference counter and
 
    // frees the managed memory as the counter reaches zero.
    ~SmartPtr()
    {
        if (_ptr) {
            if (!--_ptr->_ref_cnt)
                delete _ptr;
        }
    }
 
    // The assignment operator increments the reference counter.
 
    SmartPtr& operator=(T* p)
    {
        if (_ptr) {
            if (!--_ptr->_ref_cnt)
                delete _ptr;
 
            _ptr = NULL;
        }
 
        if (p) {
            _ptr = p;
 
            ++_ptr->_ref_cnt;
        }
 
        return *this;
    }
 
     // operator bool() to check for non-empty smart pointers
 
    operator bool() const {return _ptr != NULL;}
 
     // operator!() to check for empty smart pointers
    bool operator!() const {return !_ptr;}
 
     // operator->() to access the managed objects
 
    T* operator->() {return _ptr;}
    const T* operator->() const {return _ptr;}
 
     // Dereference pointed memory
 
    T& operator*() {return *_ptr;}
    const T& operator*() const {return *_ptr;}
 
 
private:
    T* _ptr;
};

字符串转换

有一些新的 string 转换函数:stringFromSmallString()stringFromLargeString()wstringFromSmallString()wstringFromLargeString() 使用 narrow_string()/widen_string() 用于将内部 Excel string 结构转换为 STL string 类,反之亦然。您可以使用它们访问 BasicExcel 的内部数据存储。

历史

  • 2009年9月20日 - ExcelFormat 1.0 版(BasicExcel 2.0 版)
  • 2009年9月28日 - 2.0 版
    • 添加新章节“格式化存储结构”
  • 2009年10月4日 - 2.1 版
    • 更新源代码,添加单元格和字体属性的宏和常量
  • 2009年11月7日 - 2.2 版
    • 修复了 VS2008 读取带公式字段的工作表时的问题
    • 添加了 BasicExcel::Close()CellFormat::get/set_text_props()get/set_borderlines()
  • 2010年1月12日 - 2.3 版:(由 Ami Castonguay 和 Martin Fuchs)
    • 修复了公式数据结构的引用计数
    • 支持共享公式
    • 支持合并单元格
    • 即使单元格为空也保存格式
    • 刷新 fstream 而不是关闭后再打开,以防止与病毒扫描程序结合使用时出现竞争条件
    • 支持读取 MacOS Numbers.app 导出的 XLS 文件
  • 2010年11月15日 - 2.4 版
    • 添加第二个 set_borderlines() 重载
    • 添加 ExcelFont::set_italic()CellFormat::set_wrapping()
    • 处理 COLINFO
    • 杂项修复
  • 2011年1月1日 - 2.5 版
    • 在加载 XLS 文件时,为意外的高行/列值动态分配内存
    • Load()SaveAs() 的 Unicode 重载
    • 调整以适应 OpenOffice Calc 写入的 RKValues
  • 2011年2月3日 - 3.0 版
    • 使用 Windows API 访问复合文档文件
    • 减少内存消耗并提高速度
    • 64 位可移植性
    • 从公式单元格返回当前值字符串
© . All rights reserved.