最近在处理excel导入(类似石墨,google文档,腾讯云等导入excel)简单来讲就是将excel文件(.xlsx  .xls)格式的文件转化为我们在线文档的处理格式。

excel中一个强大的工具就是其各种各样的公式,如下:

列举一个通用的例子

很简单,B1单元格内的值等于A1格子的值

而excel强大的地方在于,我们点击B1单元格右下角,在B列里 向下拖动

当当当,一排数据都出来了 它们都是干什么的呢?

它们会参考B1(也就是一开始我们下拉的单元格)内的公式(即=A1) 这里我们定义

=     是B1单元格的公式模板

A1   是B1单元格内的参考坐标

而观察B2到B11 我们发现 不变的是  =A

变的是     1,2,3,4,.......(即行坐标)

反应比较快,或者对做优化(时间,空间)有经验的小伙伴可能猜到,或者开始怀疑,excel内部怎么存储这些函数呢。

可能有的小伙伴还是不太明白,这么问,excel内部是B1格子内存储=A1,B2里存储=A2,B3里存储=A3 .......以此类推,这样的存储方式呢,还是excel自己会发现规律,做进一步的简化,保留一个模板,类似函数公式去参考这个模板,替换其中的参考坐标,以减少存储占用的空间呢?

空想没啥用,实践见真招,我们看看excel内部是怎么存储的,不就结了!

怎么看excel内部是如何存储公式的呢,这里小编给大家科普一下,xls结尾的excel,实际存储格式是二进制,我们不太好搞定,但是新的xlsx,其内部存储格式是xml的。

我们用解压软件打开excel(.xlsx结尾),解压出来,目录结构如下:

根目录 excel就是我们解压的excel的名称。

各个目录做什么用的,大家可以自行google,这里比较关键,小编比较关注的是 excel -> xl -> worksheets -> sheet1.xml  以及 excel -> xl -> styles.xml

sheet1.xml文件对应着我们excel中的页签。1就对应第一个页签

styles.xml对应整个excel各个页签中涉及到的单元格内的格式

从中我们也看出来,excel不会傻傻的把一个格子中的所有信息都存储一份,而是把通用的 设为默认,特殊的 抽取出来,单独建立一个文件,而在sheet页签xml文件读取格式的时候,根据一个唯一标识符,去styles文件中读取自己的格式,极大的节省了存储空间。

言归正传,让我们看看excel内部,这种相似公式是怎么存储的,打开sheet1.xml文件

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac xr xr2 xr3"xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision"xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2"xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xr:uid="{00000000-0001-0000-0000-000000000000}"><dimension ref="A1:B11"/><sheetViews><sheetView tabSelected="1" workbookViewId="0"><selection activeCell="B1" sqref="B1:B11"/></sheetView></sheetViews><sheetFormatPr baseColWidth="10" defaultColWidth="8.83203125" defaultRowHeight="15"/><sheetData><row r="1" spans="1:2"><c r="A1"><v>1</v></c><c r="B1"><f>A1</f><v>1</v></c></row><row r="2" spans="1:2"><c r="B2"><f t="shared" ref="B2:B11" si="0">A2</f><v>0</v></c></row><row r="3" spans="1:2"><c r="B3"><f t="shared" si="0"/><v>0</v></c></row><row r="4" spans="1:2"><c r="B4"><f t="shared" si="0"/><v>0</v></c></row><row r="5" spans="1:2"><c r="B5"><f t="shared" si="0"/><v>0</v></c></row><row r="6" spans="1:2"><c r="B6"><f t="shared" si="0"/><v>0</v></c></row><row r="7" spans="1:2"><c r="B7"><f t="shared" si="0"/><v>0</v></c></row><row r="8" spans="1:2"><c r="B8"><f t="shared" si="0"/><v>0</v></c></row><row r="9" spans="1:2"><c r="B9"><f t="shared" si="0"/><v>0</v></c></row><row r="10" spans="1:2"><c r="B10"><f t="shared" si="0"/><v>0</v></c></row><row r="11" spans="1:2"><c r="B11"><f t="shared" si="0"/><v>0</v></c></row></sheetData><phoneticPr fontId="1" type="noConversion"/><pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
</worksheet>

关键的地方在这里

这里先简单解释一下,excel中 formula(公式)的类型分为好几种,常见的有normal,shared等等 详情可以看微软官方文档解释,链接如下:

https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cellformulavalues?view=openxml-2.8.1

一般我们在excel的一个单元格(xml文件里对应<c>)里插入一个公式(xml对应<f>),他的类型(xml里对应<f t=>)可以看到我们最初在B1插入的公式类型就是shared,ref属性表示,可以参考此单元格公式的单元格坐标范围(B2到B11),所以B2到B11内,并没有展示其具体的公式,而是有一个(B1也有)si的属性,类似一个唯一标识,标识参考的公式。

这里直接给大家一个结论,可以自己去做尝试。excel中,多个页签(sheet)中,单元格内出现相同的字段(string),excel解压后会有一个sharedString.xml的文件,来存储各个页签中相同的字段(不自带标识字段,默认xml里的顺序下标来标识),但是页签内的共享公式(shared formula),不会跨表格共享,只会在自己的页签内做共享,所以sheet1页签内存在共享公式,会有si=0,shhet2页签内有共享公式,也会存在si=0。

由于小编目前的系统不支持解析shared公式,所以需要对shared类型的公式进行解析。

首先,shared formula类型公式可以简单分为两种,列参考和行参考(在excel中表现就是:列参考是以一个公式单元格为起始点,向垂直方向拉生成公式,行类似)。参考中首先要确认,哪些是可变量,哪些是不变量。无论是行参考还是列参考,参考的公式模板不会变。即上方例子中的最简单公式=。上方例子是列参考,列参考中,列坐标(A)不会变,变化的是行坐标(1,2,3......)同样(大家可以自己去试),行参考中,行坐标不会变化,列坐标会变化。

分为两类我们可以简单的构建一个工厂模式。

SharedFormula 接口 提供基本的访问函数和继承类要用的到基本属性

SharedFormulaFactory  工厂类,根据传入参数返回对应的SharedFormula 接口继承类

SharedFormulaRow 列参考类(这里虽然类名是Row,但表示的是行发生变化,类名以变化对象来命名的)继承SharedFormula 接口

SharedFormulaColum 行参考类 继承SharedFormula 接口

这里先举较为简单的列参考来说明算法大致思路(行参考其实难度也不是很大,主要是excel中,行参考变化的是列,而excel中中自带的列是以A,B,C....Y,Z,AA,AB,...,AZ,BA...这种形式来排序的,为了简单计算,我们需要对其做一个大写字母下标到阿拉伯数字的转换)。

SharedFormula接口如下:

//匹配参考公式中的参考坐标
String PATTERN_STRING = "[A-Z]+[0-9]+";Pattern PATTERN = Pattern.compile(PATTERN_STRING);void setCellNormalFormula(Cell sharedFormulaCell);

前两行代码是用java自带的正则表达式去提取出公式中的坐标(即公式中的入参)

SharedFormulaFactory  工厂类如下:

    public static SharedFormula getSharedFormula(Formula formula) {String rowOrColString = getSharedFormulaRefSameIsRowOrCol(formula.getRef());if (Character.isDigit(rowOrColString.charAt(0))) {//列变换return new SharedFormulaColum(formula, formula.getRef().split(":")[1]);}//行变换return new SharedFormulaRow(formula, formula.getRef().split(":")[0]);}private static String getSharedFormulaRefSameIsRowOrCol(String formulaRef) {String[]formulaRefs = formulaRef.split(":");int startRowIndex = getCellRowIndex(formulaRefs[0]);int endRowIndex = getCellRowIndex(formulaRefs[1]);if (formulaRefs[0].substring(0, startRowIndex).equals(formulaRefs[1].substring(0, endRowIndex))) {return formulaRefs[0].substring(0, startRowIndex);}return formulaRefs[0].substring(startRowIndex);}static int getCellRowIndex(String indexString) {int index = 0;for (char c : indexString.toCharArray()) {if (Character.isUpperCase(c)) {index++;}}return index;}

Formula 对应xml文件中的<f>内的值,ref是当前单元格的坐标。

SharedFormulaRow类,需要如下基本类型

//当前坐标与参考公式中的参考坐标偏移值
private List<Integer> rowDeviationList;
//参考公式分隔(去除参考坐标)片段
private List<String> formulaWithoutRefList;
//参考公式中的参考列坐标(只包含列坐标 行偏移 参考列坐标不变)
private List<String> formulaRefColList;

这里对几个属性做一个简单解释,我们上面的例子中,只是一个最简单的公式,而公式中的参数,不仅仅会有一个入参,像 =A1 这种简单公式,公式模板是=,入参只有一个A1,但是对于复杂公式,例如sum(A1 + B1+C1),有多个入参,我们需要从整个公式中,拆出公式模板和入参。所以才有了上图中,第二个和第三个list属性。而第一个属性,我们参考的单元格并不一定定就是和我们当前单元格在一行或者一列的,例如D1单元格内公式是sum(A2 + B2+C2),那从D1下拉单元格,D2=sum(A3 + B3+C3),同理D3=sum(A4 + B4+C4),所以存在一个参考坐标的偏移值。我们的主要步骤就是,先对参考的公式,进行拆解,分为formulaWithoutRefList和formulaRefColList,然后遍历formulaRefColList里的坐标和被参考单元格的下标,计算出rowDeviationList。然后,我们出入参考单元格坐标,计算出该单元格的公式。

公式拆解与合并过程如下

    public void formulaDismantle(String formulaText, String startRefIndex) {Matcher matcher = PATTERN.matcher(formulaText);int substringStartIndex = 0;while (matcher.find()) {formulaWithoutRefList.add(formulaText.substring(substringStartIndex, matcher.start()));String formulaTextRef = formulaText.substring(matcher.start(), matcher.end());formulaRefColList.add(getFormulaTextCol(formulaTextRef));rowDeviationList.add(getRow(formulaTextRef) - getRow(startRefIndex));substringStartIndex = matcher.end();}formulaWithoutRefList.add(formulaText.substring(substringStartIndex));}public String formulaCombine(String cellRef) {StringBuilder stringBuilder = new StringBuilder();for (int i = 0; i < formulaRefColList.size(); i++) {stringBuilder.append(formulaWithoutRefList.get(i));stringBuilder.append(formulaRefColList.get(i));stringBuilder.append(getRow(cellRef) + rowDeviationList.get(i));}stringBuilder.append(formulaWithoutRefList.get(formulaWithoutRefList.size() - 1));return stringBuilder.toString();}private int getRow(String indexString) {@Overridepublic void setCellNormalFormula(Cell sharedFormulaCell) {int formulaTextRow = getRow(sharedFormulaCell.getRef()) + rowDeviation;String formulaText = this.formulaTextCol + formulaTextRow;Formula formula = sharedFormulaCell.getFormula();formula.setFormulaType(CellFormulaType.normal);formula.setText(formulaText);formula.setText(formulaCombine(sharedFormulaCell.getRef()));sharedFormulaCell.setFormula(formula);}

而在行参考中,需要另加一个字母转换数字的函数

    private int getIntIndexFromAlphabet(String alphabet) {int len = alphabet.length();int sum = 0;int multi = 1;for (int i = len - 1; i >= 0; i--) {char c = alphabet.charAt(i);sum += multi * (c - ALPHABET_HRAD_LETTER + 1);multi *= ALPHABET_NUM;}return sum;}private String getAlphabetIndexFromInt(int index) {StringBuilder stringBuilder = new StringBuilder();while (index != 0) {stringBuilder.append((char) (index % ALPHABET_NUM - 1 + ALPHABET_HRAD_LETTER));index /= alphabetNum;}return stringBuilder.reverse().toString();}

在最外层,我们遍历excel中的单元格(cell)

    private List<SharedFormula> sharedFormulaList = Lists.newArrayList();private void checkAndSetsharedFormula(Cell cell) {Formula formula = cell.getFormula();if (Objects.nonNull(formula) && formula.getFormulaType() == CellFormulaType.shared) {String formulaRef = formula.getRef();if (Objects.nonNull(formulaRef)) {sharedFormulaList.add(SharedFormulaFactory.getSharedFormula(formula));formula.setFormulaType(CellFormulaType.normal);} else {sharedFormulaList.get(formula.getSi()).setCellNormalFormula(cell);}}}

我们根据当前formula中是否存在si和是否有具体的公式,来进行判断。

整个算法经过后续测试,在简单excel的导入上,该过程耗时1ms,在导入大量公式excel中(表格中存在公示>3w),耗时在10ms以内。

excel shared formula处理相关推荐

  1. VS之Excel文件操作

    Excel是微软办公套装软件的一个重要的组成部分,它可以进行各种数据的处理.统计分析和辅助决策操作,广泛地应用于管理.统计财经.金融等众多领域. 您可以使用 Excel 创建工作簿(电子表格集合)并设 ...

  2. 超大数据量的xlsx格式的excel文件的读取和解析,解决了POI方式的内存溢出和性能问题

    在之前的博文< POI读取并解析xlsx格式的excel文件>中,在小数据量的情况下是可以轻松愉快的处理的,但是当excel文件的数据量达到百万级的时候, InputStream is = ...

  3. 夏普比率excel_在Excel中计算比率

    夏普比率excel In Excel, if you divide 2 by 8, the result is 0.25. If you format the cell as a fraction, ...

  4. excel模糊查找公式_Excel查找公式挑战20171026

    excel模糊查找公式 Here's an Excel Lookup Formula challenge to get your brain fired up. Can you solve it wi ...

  5. 熊猫压缩怎么使用_将Excel与熊猫一起使用

    熊猫压缩怎么使用 Excel is one of the most popular and widely-used data tools; it's hard to find an organizat ...

  6. 浅谈Excel开发:二 Excel 菜单系统

    在开始Excel开发之前,需要把架子搭起来.最直接的那就是Excel里面的菜单了,他向用户直观的展现了我们的插件具有哪些功能.菜单出来之后我们就可以实现里面的事件和功能了.Excel菜单有两种形式,一 ...

  7. hlookup函数使用教程_星期五的Excel函数:HLOOKUP查找当前价格

    hlookup函数使用教程 On Day 10 of the 30 Excel Functions in 30 Days series, we looked at the Excel HLOOKUP ...

  8. excel查找一列重复项_列中最后一项的Excel查找公式

    excel查找一列重复项 How can you get the last number in an Excel column? I needed to do that in one of my sa ...

  9. Matlab【基础】【02】 将APP打包生成可执行EXE文件

    1 执行命令:>> deploytool 选择[Application Compiler] [选择加号,添加你的应用源码] [点击,Package]进行打包 然后,生成了如下的目录结构: ...

  10. ExcelJS 使用帮助文档

    ExcelJS 使用帮助文档 个人备忘,原文地址:https://github.com/exceljs/exceljs/blob/master/README_zh.md 安装 npm install ...

最新文章

  1. Vue笔记(五)——Token生命周期
  2. HTTP/2 与 WEB 性能优化(一)
  3. __proto__(隐式原型)与prototype(显式原型)
  4. python压缩文件夹下的所有文件_python压缩文件夹内所有文件为zip文件的方法
  5. hibernate 集合类(Collections)映射
  6. IT人士有哪些保健建议
  7. python输入三角形三边处理成三个实数_Python之路:(三)数据处理
  8. mysql的默认sid_默认实例(SID)已经设置,空实例默认连接时却连接不上?
  9. Notepad++插件总结
  10. 《大规模Web服务开发技术》
  11. 使用OClint进行iOS项目的静态代码扫描
  12. 中央处理器属于计算机外部设备吗,不属于计算机外部设备的是
  13. python io密集型应用案例-Python中单线程、多线程和多进程的效率对比实验实例
  14. 【Arduino】开发入门教程【六】数据类型转换函数
  15. Swift是Android的未来么?
  16. python的类,复现assert和eval成功失败原因
  17. 关于php网络爬虫phpspider
  18. Python名片管理系统
  19. ggplot画世界地图
  20. ImportError: cannot import name '_validate_leng问题解决

热门文章

  1. 详解WAF与极风云WAF
  2. linux-netstat的用法-netstat12种网络连接状态
  3. CC2640R2FRSMR低功耗M3内核蓝牙MCU
  4. P2504 [HAOI2006]聪明的猴子
  5. 成果展示 | 大数据应用开发平台DWF
  6. dwf怎么合成一个_图纸集批量发布单页dwf的方法——院办质量小组
  7. LOW逼三人组(三)----插入排序
  8. mac电脑显示器分辨率显示异常
  9. vc 判断哪个按键 被按下 消息 按键 状态
  10. 清空html输入框,jquery清空textarea等输入框