笔者尝试生成任意格式的报表,期望功能强和技术门槛低。
发现除了基于 HTML 外,剩下可能的最佳方案就是基于 RTF。
基本做法是:用 Word 编辑报表模板,保存为可保留绝大多数 Word 格式的 RTF 文件,然后根据内部的关键字生成报表。
该方案实现需要对 RTF 规范了解,但一旦实现,其通用性、实用性极强,理论上可以解决所有 C/S类软件的报表问题。
已实际应用的函数代码如下:
///<summary>
/// 根据原始 RTF 代码和 "关键词-值" 映射生成用于打印的 RTF 代码,平台无关,其中关键词的格式为 [关键词]。
/// 其中预定义关键词如下:
/// [PAGEBREAK] 分页标记
/// [PAGECOUNT] 总页数
/// [PAGENUM] 当前页
/// [ID] 动态行及序号, mVal['[ID]'] 的值是动态行数量
/// [IMG:关键词.高度] 图片(图片数据输入的相关代码需自行修改完成)
///</summary>
///<include>string、map、filesystem、assert.h</include>
///<param name="mVal">欲替换文本内容 [...] 和替换文本内容的映射</param>
///<param name="sRtf">原始 RTF 文件内容,内部有欲替换标记 [...],从已编辑完成的 RTF 文件直接读取</param>
///<param name="nMode">
/// 0-正常
/// 1-套打,隐藏原模板内容,显示已被替换的内容
/// 2-模板,隐藏被替换内容,显示未被替换的内容
/// 3-原始
/// </param>
///<param name="bMark">是否以不同的背景色标记替换内容,替换成功时淡绿或失败时淡红</param>
///<param name="nMaxPagings">最多动态行数,原 RTF 内容中 [ID] 所在的表格行将复制出 mVal['[ID]'] 数量的多行,其中 [ID] 替换成序号,行数超过 nMaxPagings 时插入 [PAGEBREAK] 分页标记。</param>
///<param name="bFullPage">动态生成表格行时是否自动填充全页,例如:最多动态行数=10,mVal['[ID]']=5,生成的 RTF 第二页表格行数量为 5,将补足至 10</param>
std::string GetPrintRtf(std::map<std::wstring, std::wstring> mVal, std::string sRtf, unsigned int nMode, bool bMark, int nMaxPagings, bool bFullPage)
{
if (sRtf.find("{\\*\\generator") == std::string::npos || sRtf.find("\\viewkind4\\uc1") == std::string::npos) // 检查 RTF 文件格式
{
assert(false);
return "";
}
int nClrCount = 0; // 已定义颜色数量
for (size_t pos = sRtf.find("{\\colortbl"); (pos = sRtf.find("\\red", pos + 1)) != std::string::npos && pos < sRtf.find("{\\*\\generator"); )
nClrCount++;
// 无颜色定义表时加入
if (sRtf.find("{\\colortbl") == std::string::npos)
sRtf.insert(sRtf.find("{\\*\\generator"), "{\\colortbl ;}");
// 加入颜色定义(白色、淡红(错误)、淡绿(警告))
sRtf.insert(sRtf.find('}', sRtf.find("{\\colortbl")), "\\red255\\green255\\blue255;\\red255\\green160\\blue160;\\red160\\green255\\blue160;");
std::string sClrWhite = "\\cf" + std::to_string(nClrCount + 1); // 隐藏文本色(白色)
std::string sClrBkErr = "\\highlight" + std::to_string(nClrCount + 2); // 错误背景色(淡红)
std::string sClrBkInf = "\\highlight" + std::to_string(nClrCount + 3); // 警告背景色(淡绿)
char sTmp[128] = { 0 }; // 临时变量
const size_t DOCBGN = sRtf.find("\\viewkind4\\uc1"); // RTF 文档内容开始位置
// 1 像素透明空白图片 RTF 代码
const char* NULLIMG = "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000a49444154785e63000100000500019b3b067a0000000049454e44ae426082";
// 删除关键词内部全部样式定义
for (size_t b = 0, nBlankLV = 0, p =