作者:無式

链接:https://www.jianshu.com/p/ea7f62e3b23a

office的公式是什么(Office中数学公式用Java解析)(1)

公司正在做教育类产品,在遇到数学公式时,我们一般会使用latex表达式来做保存和渲染。

在其中一个项目上,遇到一个需求是要从office文档(Word或Excel)中导入题目内容至数据库,题目内容中就有可能包括数学公式,而在文档中编辑希望使用office的公式插件来写公式元素。

其实公司之前的产品已经使用.net实现过此功能,不过现在公司全面转型Java,我们也要研究出一个适用Java的解决方案。

office文档中的公式编辑器

mathtype插件

mathtype是一个第三方的数学公式插件,它能在Office文档中启用编辑,并生成一个带有公式矢量图的ole对象插入到文档中。

原来.net的方案就是使用此种方式,使用mathtype提供的c#库包来解析ole对象,抽取LaTeX表达式。

但在纯Java环境下就无法做到了。

office自带公式编辑器

从2007版开始,Office也自带了一个公式编辑器。

在2007版中Word与Excel之间不同的是,前者插入的公式对象是Office MathML节点,后者插入的还是ole。

到了2010版开始,两个产品的公式编辑器插入的都是Office MathML节点了,但是两者对公式对象中的默认文字编码处理不同。

这些不同点可以看出就算同样属于Office的产品,他们之间也是有很多不统一的地方。

公式表达式

LaTeX

LaTeX是一种基于ΤΕΧ的排版系统,它非常适用于生成高印刷质量的科技和数学类文档。

例如勾股定理用LaTeX表达:

a^{2} b^{2}=c^{2}

常用的LaTeX渲染组件是MathJax。

我们在项目中使用的便是LaTeX,所以本次研究就是如何将Office中的公式对象转换成LaTeX表达式。

Mathml

全称为数学标记语言(Mathematical Markup Language),是一种基于XML的标准,用来在互联网上书写数学符号和公式的置标语言。

例如一个表达式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> <msup> <mi>n</mi> <mrow> <mi>p</mi> <mo>-</mo> <mn>1</mn> </mrow> </msup> <mspace width=".2em"/> <mo>≡</mo> <mspace width=".2em"/> <mn>1</mn> <mspace width=".2em"/> <mo>(</mo> <mi>mod</mi> <mspace width=".2em"/> <mi>p</mi> <mo>)</mo> </math>

office的公式是什么(Office中数学公式用Java解析)(2)

Office MathML (OMML)

在office2007之后版本所编辑的公式对象便是OMML。OMML是office为了配合Office Open Xml制定的数学标记语言。

例如:

<m:oMathPara><!-- mathematical block container used as a paragraph --> <m:oMath><!-- mathematical inline formula --> <m:f><!-- a fraction --> <m:num><m:r><m:t>π</m:t></m:r></m:num><!-- numerator containing a single run of text --> <m:den><m:r><m:t>2</m:t></m:r></m:den><!-- denominator containing a single run of text --> </m:f> </m:oMath> </m:oMathPara>

转换关系

我们在项目中使用到的三者之间转换关系是:OMML -> MathML -> LaTex

Office在安装目录中提供了将OMML转为MathML的xsl工具:MML2OMML.XSL

MathML转LaTex使用网上找到另一个xsl工具mmltex.xsl。

Office文档Java解析

2007与之前的版本

用过一段Office的同学们都知道,Office文档分为word与wordx这两种类型,分别对应着2007之前与之后的版本格式。

2007之前版本使用的Office文档是二进制文件。而之后版本中x代表的意义是xml,表明新版的Office文档使用Office Open Xml规范定义文件格式。

如果我们把wordx文件的扩展名改为zip,就可以正常解压出Word文档包含的所有内容。

POI

相信用Java做过信息系统的同学都遇过生成统计Excel文档或解析Excel导入数据的功能。这时我们最常使用的开发库就是Apache POI。

POI支持二进制与Office Open Xml文档,可以满足我们大部分的Office文档解析需求。

解析公式实例

首先要说明我们的功能限制:只针对Office2010及以上的Office Open Xml文档,Word和Excel均可。 其中,Excel的公式数学字符需要转为普通字符,否则会出现Java无法识别的字符。

这里用Excel文档为例子来说明解析过程。

功能实现思路

这个功能的关键点在于如何获得Office文档中的公式节点(OMML),得到OMML后我们就可以使用上述的两个工具转换为LaTeX。

office的公式是什么(Office中数学公式用Java解析)(3)

获得OMML

既然我们知道Excel文档是一个xml,那只需要使用xml解析工具读出OMML节点就行了。

先用POI得到操作的XSSFSheet:

String basePath = "f:\\"; FileInputStream fis = new FileInputStream(basePath "math.xlsx"); OPCPackage pack = OPCPackage.open(fis); XSSFWorkbook workbook = new XSSFWorkbook(pack); XSSFSheet sheet = workbook.getSheetAt(0);

插入在Excel文档中的图片、公式及其他元素,它都是存放在一个叫drawing的单独xml文件中,其中的节点记录了元素摆放的位置信息。用POI得到drawing元素:

XSSFDrawing dr = sheet.getDrawingPatriarch(); CTDrawing drawing = dr.getCTDrawing(); CTOneCellAnchor[] oneCells = drawing.getOneCellAnchorArray(); //所有的图片、公式等元素

每个CTOneCellAnchor的xml里包含元素的位置信息,包括X坐标、Y坐标,所在行、所在列等,更重要的是图片或公式的描述节点。OMML节点名为m:oMathPara,这里我们就使用dom4j的xpath来获得OMML:

CTOneCellAnchor c = oneCells[0]; String xml = c.xmlText(); //得到xml串 //dom4j解析器的初始化 SAXReader reader = reader = new SAXReader(new DocumentFactory()); Map<String, String> map=new HashMap<String, String>(); map.put("xdr","http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"); map.put("m","http://schemas.openxmlformats.org/officeDocument/2006/math"); reader.getDocumentFactory().setXPathNamespaceURIs(map); //xml文档的namespace设置 InputSource source = new InputSource(new StringReader(xml)); source.setEncoding("utf-8"); Document doc = reader.read(source); Element root = doc.getRootElement(); Element e = (Element)root.selectSingleNode("//m:oMathPara"); //用xpath得到OMML节点 String omml = e.asXML(); //转为xml

转换OMML为Mathml及LaTeX

顺利得到OMML后,就可以使用xsl转换工具得到Mathml与LaTeX了。

这里先写一下xsl转换工具方法,使用javax.xml.transform工具包实现:

/** * <p>Description: xsl转换器</p> */ public static String xslConvert(String s, String xslpath, URIResolver uriResolver){ TransformerFactory tFac = TransformerFactory.newInstance(); if(uriResolver != null) tFac.setURIResolver(uriResolver); StreamSource xslSource = new StreamSource(MathmlUtils.class.getResourceAsStream(xslpath)); StringWriter writer = new StringWriter(); try { Transformer t = tFac.newTransformer(xslSource); Source source = new StreamSource(new StringReader(s)); Result result = new StreamResult(writer); t.transform(source, result); } catch (TransformerException e) { logger.error(e.getMessage(), e); } return writer.getBuffer().toString(); } /** * <p>Description: 将mathml转为latx </p> * @param mml * @return */ public static String convertMML2Latex(String mml){ mml = mml.substring(mml.indexOf("?>") 2, mml.length()); //去掉xml的头节点 URIResolver r = new URIResolver(){ //设置xls依赖文件的路径 @Override public Source resolve(String href, String base) throws TransformerException { InputStream inputStream = MathmlUtils.class.getResourceAsStream("/conventer/mml2tex/" href); return new StreamSource(inputStream); } }; String latex = xslConvert(mml, "/conventer/mml2tex/mmltex.xsl", r); if(latex != null && latex.length() > 1){ latex = latex.substring(1, latex.length() - 1); } return latex; } /** * <p>Description: office mathml转为mml </p> * @param xml * @return */ public static String convertOMML2MML(String xml){ String result = xslConvert(xml, "/conventer/OMML2MML.XSL", null); return result; }

至此我们就可以将OMML转成Mathml与LaTeX表达式了:

String mml = convertOMML2MML(omml); String latex = convertMML2Latex(mml);

一些心得体会

实现这个功能的时候,手上真的也没太多直接的资料可以参考,走过好几个弯路,网上查到的信息很多也是过时或者把话说一半的。

在与同事的交流下,使用不同思路,查阅许多api文档,再加上不断的尝试,也算完成了这个不算实用的功能。

就算你自己本身不够优秀,在一个好的团队也能不断推着你向前走。一个人最终能前行到多远,还是要看与你同行的人。

,