Skip to content

Officia.Cells — Excel 转 PDF 与公式引擎 ​

能力摘要(AI 友好):OfficiaCells 把 Excel 转 PDF/CSV,.xlsx 与 .xls 两种格式都收(按文件内容识别,不看扩展名),内置公式计算引擎(evaluateFormula / evaluateCell / recalculate),并可解析 RFC 4180 的 CSV。门面静态方法,纯 JDK 实现。

引入 ​

xml
<dependency>
  <groupId>plus.ruoyi</groupId>
  <artifactId>officia-all</artifactId>
  <version>1.0.0</version>
</dependency>

这是唯一需要引的依赖:Maven Central 上只发布 officia-all 这一个构件, 它是含全部能力模块与授权客户端的单一 jar,且无任何第三方传递依赖。

支持的输入格式 ​

所有吃 Excel 字节的方法都按文件内容判断格式,调用方不必先区分:

格式支持说明
.xlsx(OOXML)是ECMA-376 §18
.xls(Excel 97-2003 二进制)是依 MS-XLS 规范自研解析,仅未加密的 BIFF8
加密的 .xls否明确拒绝并点名加密方式(XOR 混淆 / RC4)
BIFF5 / BIFF7(Excel 5.0/95)否明确拒绝,提示另存为较新格式

.xls 里的图片、图表、条件格式、数据验证暂不解析(xlsx 侧同样未实现)。

Excel → PDF ​

java
import plus.ruoyi.officia.cells.OfficiaCells;

byte[] pdf = OfficiaCells.toPdf(excelBytes);                // xlsx 或 xls;字节 / InputStream / File 重载
ConvertResult r = OfficiaCells.convert(excelBytes, ConvertOptions.defaults());
int pages = r.getPageCount();

单元格用的字体照原样用:宋体、微软雅黑的格连数字和英文也用自己的字形,Arial、Calibri 这类西文字体里的汉字 自动换成中文字体。服务器上没装的字体会换成相近的字体——版式和本机不一样时,打开字体替换诊断查是哪款字体被换掉了 (用法与 Words 相同,见 字体替换诊断):

java
ConvertResult r = OfficiaCells.convert(excelBytes, new ConvertOptions().setReportFontSubstitutions(true));
r.getFontSubstitutions().forEach(s -> System.out.println(s.describe()));

按每张表的打印设置出页:纸张(A4、A3、Letter 等及自定义宽高)、方向、页边距、缩放比例、 「调整为 N 页宽 × M 页高」、水平居中都照文件里的设置;表比纸宽时像 Excel 打印那样把列分到后面几页 (先打完左边一批列的所有行,再打右边一批),手动插入的列分页符照样分页;右边那批列下面整页空白的, 与 WPS 打印一样照样出页。没设打印设置的表按 A4 纵向、100%。.xls 与 .xlsx 相同(1.1.5 起; 1.1.4 及之前一律 A4 横向、100%,宽表被压进一页宽、逐字折行)。 边界:打印区域、打印标题行、手动行分页符、垂直居中、页眉页脚还不认;按列分页时跨页的合并格,字只画在左边那一页。

列宽按工作簿的默认字体换算:Excel 的列宽以「默认字体里数字的最大宽度」为单位,同样写着宽 10, 默认字体是宋体 12 的表一列 60pt,Calibri 11 的表一列 52.5pt;没设宽度的列宋体表 54pt、Calibri 表 48pt。 宋体、黑体这类中文字体按字体本身的规则算,服务器上装没装都一样;等线(新版中文 Excel 的默认字体)在装了等线的机器上(Windows 自带) 与 Excel 一致,没装时按替换字体量,个别字号可能差一个像素。1.1.5 起照这个口径排,一页放几列、在哪儿分页更接近 Excel / WPS; 1.1.4 及之前一律按 Calibri 11 换算,宋体、等线的表列偏窄。

一格里分段设了不同格式的文字(富文本,比如标题里半截黑字半截橙字)按段出:各段用自己的字体、字号、加粗和颜色。 .xls 的富文本目前只取纯文本。

工作表关了「零值」显示的(Excel 选项里「在具有零值的单元格中显示零」),转出的 PDF 里 0 显示为空白,与 Excel 打印一致; .xlsx 与 .xls 都照此处理。

上标、下标照文件出(「0.35M³」「H₂O」这类单位与化学式):整格设的与富文本里某一段设的都算,.xls 也读得出。 画法与 Word 转 PDF 相同:字号缩到 0.65、基线抬高(下标压低),上标后面的字仍按原字号的宽度往后排,所以右边留白略宽。

CSV 互转 ​

java
byte[] pdf = OfficiaCells.csvToPdf("Name,Dept\nAlice,R&D\nBob,Product"); // CSV 文本 → PDF
byte[] pdf2 = OfficiaCells.csvToPdf(csvBytes);              // CSV 字节 → PDF
String csv = OfficiaCells.toCsv(excelBytes);                // Excel → CSV(xlsx 或 xls)
List<List<String>> rows = OfficiaCells.parseCsv(csvText);   // 解析 CSV(RFC 4180,处理引号/转义)

公式计算引擎 ​

Officia 自研公式引擎(Tokenizer → Parser → 求值),支持单元格 / 区域引用、算术与比较运算、文本连接, 公式链递归求值并检测循环引用,语义对齐 Excel。

支持的函数(85 个) ​

分类函数
数学(16)SUM SUMIF SUMIFS SUMPRODUCT SUBTOTAL PRODUCT ABS ROUND ROUNDUP ROUNDDOWN CEILING FLOOR INT MOD POWER SQRT
统计(13)AVERAGE AVERAGEIF AVERAGEIFS MAX MIN MEDIAN COUNT COUNTA COUNTIF COUNTIFS SMALL LARGE RANK
逻辑(7)IF IFERROR AND OR NOT TRUE FALSE
信息(9)ISERROR ISERR ISNA ISNUMBER ISTEXT ISLOGICAL ISBLANK N NA
文本(13)CONCATENATE LEFT RIGHT MID LEN TRIM UPPER LOWER TEXT FIND SEARCH SUBSTITUTE VALUE
查找与引用(14)VLOOKUP HLOOKUP LOOKUP INDEX MATCH CHOOSE OFFSET INDIRECT TRANSPOSE HYPERLINK ROW COLUMN ROWS COLUMNS
日期时间(13)DATE YEAR MONTH DAY TODAY NOW HOUR MINUTE SECOND WEEKDAY WEEKNUM EOMONTH DATEDIF

清单外的函数(如 RANDBETWEEN、GETPIVOTDATA)会求值失败——重算工作簿时这类格保留文件里 Excel 算好的结果。另有几处如实的边界:

  • 跨表引用(Sheet2!A1、'表 名'!A1:B2,表名不分大小写)与整列 / 整行引用(客户信息!A:B、$1:$1)支持;
  • 数组公式(Excel 里按 Ctrl+Shift+Enter 输入的 {=MAX(IF(C:C=A2,B:B))},xlsx 里带 t="array")按数组逐元素计算;SUMPRODUCT、LOOKUP 的参数本身就按数组算,不必按数组公式输入。同样的写法作为普通公式时走隐式交集(ECMA-376 §18.17.2):把多格区域交给只收单个值的地方,取与公式同一行(区域是一列时)或同一列(区域是一行时)的那一格,没有交点是 #VALUE!;
  • 定义名称(=SUM(销量))照常计算:同名时本表的局部名称优先于整本的;名称里的计算式按数组公式算;未定义的名称是 #NAME?,IFERROR 兜得住。读 xlsx 时名称一并读进 Workbook.getNames()、写回时原样保留;.xls 的名称公式暂未读出,用到它的格保留缓存值;
  • OFFSET、INDIRECT(含 R1C1 写法 INDIRECT("R[-1]C",FALSE))返回的是引用,可以放在 SUM、COUNTIF、ROWS、INDEX 等要区域的参数里;在普通公式里直接取一个多格区域的值时同样走隐式交集;
  • SUBTOTAL 的 1–11 含隐藏行、101–111 跳过隐藏行,区域里本身是 SUBTOTAL 的格不重复计入。筛选掉的行与手动隐藏的行在文件里分不出来,1–11 会把筛选掉的行也算进去;
  • 公式里的数组常量({1,2;3,4},如个税速算 MAX(A1*{0.03,0.1}-{0,210},0))与直接写的错误值(#REF!)都能算;VLOOKUP / HLOOKUP / MATCH 查数组常量暂不支持;
  • 表格与结构化引用照常计算([MS-XLSX] §2.2.2):表1[金额](数据行)、表1[[#This Row],[单价]](本行)、表1[[#Totals],[金额]]、表1[[单价]:[数量]]、#All / #Headers 等;写在表格里的公式可以省略表名。读 xlsx 时表格一并读进 Sheet.getTables()、写回时原样保留(差异格式编号、筛选条件、排序状态不保留);
  • 不支持三维引用(Sheet1:Sheet3!A1)与外部工作簿引用([1]Sheet1!A1);INDEX 取整行 / 整列(行号或列号为 0)只在数组公式里可用;
  • 日期函数按 1900 日期系统计算(ECMA-376 §18.17.4.1);YEAR / MONTH / DAY 对序列值 0–60 沿用 Excel 的读法(空格是 1900 年 1 月 0 日,MONTH(空格) 为 1);date1904 的工作簿暂不换算;
  • 条件函数(COUNTIF、SUMIFS 等)里空格只配「空」条件、不当 0 比大小;条件引用的是空格时按 0;文本 "1001" 与数字条件 1001 相等;
  • 求值出错时写真实的错误码(#N/A、#DIV/0!、#REF!、#NUM!、#NAME?、#VALUE!)。

独立公式求值 ​

第一个参数是"单元格 → 值"的数据表,第二个是公式(或单元格引用):

java
Map<String, String> sheet = Map.of("A1", "10", "A2", "20", "A3", "3");
Object v = OfficiaCells.evaluateFormula(sheet, "=A1+A2*A3");  // → 70

直接求文件里的单元格 ​

java
Object c = OfficiaCells.evaluateCell(excelBytes, "C3");       // xlsx 或 xls,求 Sheet 的 C3

重算整个工作簿 ​

java
Map<String, Object> values = OfficiaCells.recalculate(excelBytes);    // 单元格引用 → 计算结果

两者都是实算——按公式依赖递归求值,不信文件里的缓存值。.xls 的公式在文件中是 二进制 RPN token,解析时已还原成公式文本,因此与 .xlsx 走同一套求值口径。

.xls 的还原覆盖率实测 93.2%(18 份真实文件、5951 条公式),已包含共享公式(往下拖那种)、 数组公式、定义名称与自定义函数调用。还原不出的那部分回落文件里的缓存值,不会报错, 也不会产出一条错误的公式。

日期与数字格式 ​

单元格的数字格式会照着格式代码呈现:源表里设的是 yyyy"年"m"月"d"日",导出的就是 2024年1月1日。内置日期格式(编号 14 等)按中文区域给 2026/3/18 形态,与中文版 Excel / WPS 一致。

边界:mmm / dddd 给英文月份与星期名(Jan–Dec / Monday),中文名暂未支持; [h]:mm 这类累计时长按普通时刻渲染。

不自动换行的单元格 ​

设了「不自动换行」的文字格排成一行,与 Excel / WPS 一致:超出列宽时铺到空着的邻格上 (常规 / 左对齐往右、右对齐往左、居中往两边),遇到有内容的格、合并区或表格边就停,停处之外裁掉。 行高不会因此被撑高。「有内容」看的是值:带框线底纹但没值的格照样铺过去,公式算出空串的格挡得住。

边界:两端 / 分散 / 填充 / 跨列居中对齐与旋转过的文字不铺。

数字放不下时 ​

与 Excel / WPS 一致(1.1.5 起):

  • 常规格式先减小数位、再改用科学计数,最多显示 11 位。3.14159265358979 随列变窄依次显示成 3.141593、3.1416、3.14;123456789012 显示成 1.23E+11、1E+11。都放不下才整格显示 #。
  • 日期、0.00、百分比这类明确格式放不下,直接整格显示 #,不会截掉一半让人看错。
  • 数字不折行(勾了「自动换行」也一样);合并格按合并后的总宽算;字号大的格放得下的位数相应少; 文本格式(@)里的数按文本处理,像文字一样铺到空着的邻格。

只影响转 PDF:toCsv 照旧导出完整的值。1.1.4 及之前是右边一路空着就铺过去、否则在格内逐位折行。

常规对齐按值类型摆:没设水平对齐的格,数字、日期靠右,TRUE / FALSE 与错误值居中,文字靠左, 整列金额的小数点对得齐,与 Excel / WPS 一致(1.1.5 起;1.1.4 及之前一律靠左)。

行高与基线按表格口径:格子按它声明的字体排行,Calibri 格里的汉字只借中文字体的字形, 不改行高和基线,同一行的中西文对得齐;没设行高的行接近 Excel / WPS 的默认行高 (1.1.5 起;1.1.4 及之前按文字处理口径,含中文的行几乎高出一倍)。

隐藏的行、列不打印:与 Excel / WPS 打印一致,转 PDF 时跳过隐藏的行和列(含筛选掉的行、折叠的分组); 合并区跨过隐藏列时只占可见的部分。toCsv 照旧导出隐藏行列里的数据(1.1.5 起; 1.1.4 及之前隐藏的行列也画进 PDF)。

边界:单独一列就比纸还宽时,那一页的表格会被等比缩小,按原始列宽放得下的数字不会在缩小后的格里改成 #。

读工作簿模型 ​

java
Workbook wb = OfficiaCells.readWorkbook(excelBytes);          // xlsx 或 xls

比转换保留得多:公式原文、样式表、合并区、行列尺寸、定义名称、表格都在,适合自行处理数据的场景。

旧方法名已废弃(行为不变)

evaluateXlsxCell 改用 evaluateCell,recalculateXlsx 改用 recalculate。 旧名标了 @Deprecated 但内部只是委托,既有代码不受影响。

方法一览 ​

方法说明
toPdf(excel) / convert(excel, options)Excel → PDF(含富结果)
csvToPdf(csv)(String / byte[])CSV → PDF
toCsv(excel)Excel → CSV
parseCsv(csv)解析 CSV(RFC 4180)
readWorkbook(excel)读工作簿模型(公式原文 + 样式)
evaluateFormula(sheet, formula)用数据表求公式
evaluateCell(excel, cellRef)求文件某单元格(实算)
recalculate(excel)重算工作簿(实算)
evaluateXlsxCell / recalculateXlsx已废弃,等价于上面两个

表中 excel 处传 .xlsx 或 .xls 字节均可。

常见问题 ​

  • 公式返回类型:数值返回 Long/Double,文本返回 String(Object 承载,按需转型)。
  • 未授权水印:评估态输出带水印并限页,加载 officia.lic 后消失(见 授权与加载)。
  • .xls 报「暂不支持加密」:该文件设了打开密码。注意工作表「保护」不是加密,内容照常可读。
  • .xls 的公式没重算:用到了尚未支持的结构(模拟运算表、数组常量、跨簿名称、跨表引用)时, 会回落到文件里的缓存值而不是报错。需要精确重算请先另存为 .xlsx。