Office中數(shù)學(xué)公式用Java解析

公司正在做教育類產(chǎn)品,在遇到數(shù)學(xué)公式時(shí),我們一般會(huì)使用latex表達(dá)式來做保存和渲染。
在其中一個(gè)項(xiàng)目上,遇到一個(gè)需求是要從office文檔(WordExcel)中導(dǎo)入題目內(nèi)容至數(shù)據(jù)庫,題目內(nèi)容中就有可能包括數(shù)學(xué)公式,而在文檔中編輯希望使用office的公式插件來寫公式元素。
其實(shí)公司之前的產(chǎn)品已經(jīng)使用.net實(shí)現(xiàn)過此功能,不過現(xiàn)在公司全面轉(zhuǎn)型Java,我們也要研究出一個(gè)適用Java的解決方案。

office文檔中的公式編輯器

mathtype插件

mathtype是一個(gè)第三方的數(shù)學(xué)公式插件,它能在Office文檔中啟用編輯,并生成一個(gè)帶有公式矢量圖的ole對象插入到文檔中。
原來.net的方案就是使用此種方式,使用mathtype提供的c#庫包來解析ole對象,抽取LaTeX表達(dá)式。
但在純Java環(huán)境下就無法做到了。

office自帶公式編輯器

從2007版開始,Office也自帶了一個(gè)公式編輯器。
在2007版中WordExcel之間不同的是,前者插入的公式對象是Office MathML節(jié)點(diǎn),后者插入的還是ole。
到了2010版開始,兩個(gè)產(chǎn)品的公式編輯器插入的都是Office MathML節(jié)點(diǎn)了,但是兩者對公式對象中的默認(rèn)文字編碼處理不同。
這些不同點(diǎn)可以看出就算同樣屬于Office的產(chǎn)品,他們之間也是有很多不統(tǒng)一的地方。

公式表達(dá)式

LaTeX

LaTeX是一種基于ΤΕΧ的排版系統(tǒng),它非常適用于生成高印刷質(zhì)量的科技和數(shù)學(xué)類文檔。
例如勾股定理用LaTeX表達(dá):

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

常用的LaTeX渲染組件是MathJax。
我們在項(xiàng)目中使用的便是LaTeX,所以本次研究就是如何將Office中的公式對象轉(zhuǎn)換成LaTeX表達(dá)式。

Mathml

全稱為數(shù)學(xué)標(biāo)記語言(Mathematical Markup Language),是一種基于XML的標(biāo)準(zhǔn),用來在互聯(lián)網(wǎng)上書寫數(shù)學(xué)符號和公式的置標(biāo)語言。
例如一個(gè)表達(dá)式:

<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 MathML (OMML)

office2007之后版本所編輯的公式對象便是OMMLOMMLoffice為了配合Office Open Xml制定的數(shù)學(xué)標(biāo)記語言。
例如:

<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>

轉(zhuǎn)換關(guān)系

我們在項(xiàng)目中使用到的三者之間轉(zhuǎn)換關(guān)系是:OMML -> MathML -> LaTex
Office在安裝目錄中提供了將OMML轉(zhuǎn)為MathMLxsl工具:MML2OMML.XSL
MathML轉(zhuǎn)LaTex使用網(wǎng)上找到另一個(gè)xsl工具mmltex.xsl。

Office文檔Java解析

2007與之前的版本

用過一段Office的同學(xué)們都知道,Office文檔分為wordwordx這兩種類型,分別對應(yīng)著2007之前與之后的版本格式。
2007之前版本使用的Office文檔是二進(jìn)制文件。而之后版本中x代表的意義是xml,表明新版的Office文檔使用Office Open Xml規(guī)范定義文件格式。
如果我們把wordx文件的擴(kuò)展名改為zip,就可以正常解壓出Word文檔包含的所有內(nèi)容。

POI

相信用Java做過信息系統(tǒng)的同學(xué)都遇過生成統(tǒng)計(jì)Excel文檔或解析Excel導(dǎo)入數(shù)據(jù)的功能。這時(shí)我們最常使用的開發(fā)庫就是Apache POI。
POI支持二進(jìn)制與Office Open Xml文檔,可以滿足我們大部分的Office文檔解析需求。

解析公式實(shí)例

首先要說明我們的功能限制:只針對Office2010及以上的Office Open Xml文檔,WordExcel均可。 其中,Excel的公式數(shù)學(xué)字符需要轉(zhuǎn)為普通字符,否則會(huì)出現(xiàn)Java無法識別的字符。
這里用Excel文檔為例子來說明解析過程。

功能實(shí)現(xiàn)思路

這個(gè)功能的關(guān)鍵點(diǎn)在于如何獲得Office文檔中的公式節(jié)點(diǎn)(OMML),得到OMML后我們就可以使用上述的兩個(gè)工具轉(zhuǎn)換為LaTeX。

獲得OMML

既然我們知道Excel文檔是一個(gè)xml,那只需要使用xml解析工具讀出OMML節(jié)點(diǎn)就行了。
先用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文檔中的圖片、公式及其他元素,它都是存放在一個(gè)叫drawing的單獨(dú)xml文件中,其中的節(jié)點(diǎn)記錄了元素?cái)[放的位置信息。用POI得到drawing元素:

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

每個(gè)CTOneCellAnchorxml里包含元素的位置信息,包括X坐標(biāo)、Y坐標(biāo),所在行、所在列等,更重要的是圖片或公式的描述節(jié)點(diǎn)。OMML節(jié)點(diǎn)名為m:oMathPara,這里我們就使用dom4jxpath來獲得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設(shè)置

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("http://m:oMathPara");    //用xpath得到OMML節(jié)點(diǎn)
String omml = e.asXML();    //轉(zhuǎn)為xml

轉(zhuǎn)換OMML為Mathml及LaTeX

順利得到OMML后,就可以使用xsl轉(zhuǎn)換工具得到MathmlLaTeX了。
這里先寫一下xsl轉(zhuǎn)換工具方法,使用javax.xml.transform工具包實(shí)現(xiàn):

/**    
 * <p>Description: xsl轉(zhuǎn)換器</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轉(zhuǎn)為latx </p>
 * @param mml
 * @return
 */
public static String convertMML2Latex(String mml){
    mml = mml.substring(mml.indexOf("?>")+2, mml.length()); //去掉xml的頭節(jié)點(diǎn)
    URIResolver r = new URIResolver(){  //設(shè)置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轉(zhuǎn)為mml </p>
 * @param xml
 * @return
 */
public static String convertOMML2MML(String xml){
    String result = xslConvert(xml, "/conventer/OMML2MML.XSL", null);
    return result;
}

至此我們就可以將OMML轉(zhuǎn)成MathmlLaTeX表達(dá)式了:

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

一些心得體會(huì)

實(shí)現(xiàn)這個(gè)功能的時(shí)候,手上真的也沒太多直接的資料可以參考,走過好幾個(gè)彎路,網(wǎng)上查到的信息很多也是過時(shí)或者把話說一半的。
在與同事的交流下,使用不同思路,查閱許多api文檔,再加上不斷的嘗試,也算完成了這個(gè)不算實(shí)用的功能。
就算你自己本身不夠優(yōu)秀,在一個(gè)好的團(tuán)隊(duì)也能不斷推著你向前走。一個(gè)人最終能前行到多遠(yuǎn),還是要看與你同行的人。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,525評論 19 139
  • 轉(zhuǎn)自鏈接 目錄 1.認(rèn)識NPOI 2.使用NPOI生成xls文件 2.1創(chuàng)建基本內(nèi)容 2.1.1創(chuàng)建Workboo...
    腿毛褲閱讀 11,138評論 1 3
  • 使用首先需要了解他的工作原理 1.POI結(jié)構(gòu)與常用類 (1)創(chuàng)建Workbook和Sheet (2)創(chuàng)建單元格 (...
    長城ol閱讀 8,727評論 2 25
  • 工具: 獲多福細(xì)紋水彩紙190g原白 紅輝水溶72色/綠輝油性 鉛筆 橡皮 色卡 線稿 上色過程
    k小白閱讀 714評論 0 8
  • - “我們先在表面涂一層草莓味的表面麻醉,等感覺麻木以后我們再注射局部麻醉劑。大約需要等5分鐘。讓我?guī)湍惆岩巫酉葥u...
    K媽南卡隨筆閱讀 793評論 0 0

友情鏈接更多精彩內(nèi)容