解析word公式的解決方案(office插入和wps插入不同的解決方案)
這幾天在公司的項目有個需求就是數學公式的導入,而對於word來說,插入的公式xml格式,需要轉換為mathML,借用插件MathJax來進行展示,而對於wps插入的公式來說,獲取到的是一個wmf圖片,wmf是無法在頁面上進行展示的,所以思路就是將wmf轉換為png圖片. 這個在網上的資料有很多,是先轉換為svg,再轉換為png,但是我在實際操作過程中發現很多問題,就是公式的一些特殊符號展示不出來,所以在這總結下解決辦法,最後有兩種解決方案,一個是硬編碼,一個是借助 第三方來實現.
使用的是poi解析word
先說一下office插入的公式解決方案
思路就是讀取出來的xml,先進行轉換為mathML,然後直接在頁面上展示就可以,直接代碼實現:
// 進行轉換的過程中需要借助這個文件,網上搜索就可以,或者使用everything這個軟件全盤搜一下,一般來說本機安裝office就會有這個文件,找到就可以 private static File stylesheet = new File("src/main/resources/OMML2MML.XSL"); private static TransformerFactory tFactory = TransformerFactory.newInstance(); private static StreamSource stylesource = new StreamSource(stylesheet); /** * 獲取MathML * @param ctomath * @return * @throws Exception */ static String getMathML(CTOMath ctomath) throws Exception { Transformer transformer = tFactory.newTransformer(stylesource); Node node = ctomath.getDomNode(); DOMSource source = new DOMSource(node); StringWriter stringwriter = new StringWriter(); StreamResult result = new StreamResult(stringwriter); transformer.setOutputProperty("omit-xml-declaration", "yes"); transformer.transform(source, result); String mathML = stringwriter.toString(); stringwriter.close(); mathML = mathML.replaceAll("xmlns:m=\"http://schemas.openxmlformats.org/officeDocument/2006/math\"", ""); mathML = mathML.replaceAll("xmlns:mml", "xmlns"); mathML = mathML.replaceAll("mml:", ""); return mathML; } /** * 返回公式的集合 * @param document * @return */ public static Map<Integer,String> mml2Html(XWPFDocument document){ Map<Integer,String> result = new HashMap<>(); try{ //storing the found MathML in a AllayList of strings List<String> mathMLList = new ArrayList<String>(16); //getting the formulas out of all body elements for (IBodyElement ibodyelement : document.getBodyElements()) { if (ibodyelement.getElementType().equals(BodyElementType.PARAGRAPH)) { XWPFParagraph paragraph = (XWPFParagraph)ibodyelement; for (CTOMath ctomath : paragraph.getCTP().getOMathList()) { mathMLList.add(getMathML(ctomath)); } for (CTOMathPara ctomathpara : paragraph.getCTP().getOMathParaList()) { for (CTOMath ctomath : ctomathpara.getOMathList()) { mathMLList.add(getMathML(ctomath)); } } } else if (ibodyelement.getElementType().equals(BodyElementType.TABLE)) { XWPFTable table = (XWPFTable)ibodyelement; for (XWPFTableRow row : table.getRows()) { for (XWPFTableCell cell : row.getTableCells()) { for (XWPFParagraph paragraph : cell.getParagraphs()) { for (CTOMath ctomath : paragraph.getCTP().getOMathList()) { mathMLList.add(getMathML(ctomath)); } for (CTOMathPara ctomathpara : paragraph.getCTP().getOMathParaList()) { for (CTOMath ctomath : ctomathpara.getOMathList()) { mathMLList.add(getMathML(ctomath)); } } } } } } } document.close(); for (int i = 0; i < mathMLList.size(); i++) { // 替換特殊符號(由於頁面上無法直接展示特殊符號,所以需要進行替換,將特殊符號替換為html可以認識的標簽(https://www.cnblogs.com/xinlvtian/p/8646683.html)) String s = mathMLList.get(i) .replaceAll("±", "±") .replaceAll("∑","∑"); s = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">" + s + "</math>"; result.put(i,s); } return result; }catch (Exception e){ e.printStackTrace(); } return result; } /** * 獲取所有的公式 * @param xwpfDocument * @return */ public static Map<Integer,String> getFormulaMap(XWPFDocument xwpfDocument){ Map<Integer, String> result = new HashMap<>(); // 獲取到公式的Map集合 Map<Integer, String> mml2Html = WordDocument.mml2Html(xwpfDocument); Set<Map.Entry<Integer, String>> entries = mml2Html.entrySet(); // 遍歷所有段落,獲取所有包含公式的段落 List<XWPFParagraph> paragraphs = xwpfDocument.getParagraphs(); int j = 0; for (int i = 0; i < paragraphs.size(); i++) { XWPFParagraph xwpfParagraph = paragraphs.get(i); CTP ctp = xwpfParagraph.getCTP(); String xmlText = ctp.xmlText(); if(xmlText.contains("<m:oMath>")){ StringBuilder sb = new StringBuilder(); sb.append(xwpfParagraph.getParagraphText()); sb.append(mml2Html.get(j++)); result.put(i,sb.toString()); } } return result; } public static void main(String[] args) throws Exception { XWPFDocument xwpfDocument = new XWPFDocument(new FileInputStream("C:\\Users\\wz157\\Desktop\\題目批量導入模板 (1).docx")); // 這個就能獲取到所有公式了,Integer表示的是第幾個公式,String表示公式轉化後的mathML,借助mathJax可以在頁面上進行展示 Map<Integer, String> formulaMap = getFormulaMap(xwpfDocument); // 接下來就看自己公司的業務了,我們是將這個東西直接存入數據庫,到時候展示的時候直接拿出來就可以了 // 前臺展示的時候需要註意,將mathJax下載下來裏面有實例,其實就是添加<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">,就可以展示了,這個絕對是可行的,如果不可以請檢查頁面上的這個js文件是否引入正確. }
上面就是office插入公式進行轉換的解決方案,尤其註意後面的樣式,一定要正確,就絕對沒有問題,建議下載源碼包,裏面的test下有何mml的示例.
wps插入公式的解決方案
第一種,使用第三方,這個需要去官方先註冊一下,獲取到一個api key,網站是https://cloudconvert.com/,註冊完成之後點擊上面導航的API,進入頁面點擊API Console,就可以找到字節的apikey了,下面我使用自己的apikey作為實例.
這裏只說轉換,不說poi讀取word,其實也很簡單,獲取對象,getAllPictures()方法,獲取所有圖片,使用picture.suggestFileExtension()獲取圖片後綴,看後綴是否是wmf結尾的就可以了.下面直接說轉換.
// Create service object
// CloudConvertService service = new CloudConvertService("<api key>");
CloudConvertService service = new CloudConvertService("OtyvB1mgwMzVQsFYdN663Ue80fKXjrlR3D5T6Je1vqmHs93dkC1n8sWum6JHVnZx");
// Create conversion process
ConvertProcess process = service.startProcess("wmf", "png");
// Perform conversion
process.startConversion(new File("C:\\Users\\wz157\\Desktop\\1.wmf"));
// Wait for result
ProcessStatus status;
waitLoop: while (true) {
status = process.getStatus();
switch (status.step) {
case FINISHED: break waitLoop;
case ERROR: throw new RuntimeException(status.message);
}
// Be gentle
Thread.sleep(200);
}
// Download result
service.download(status.output.url, new File("C:\\\\Users\\\\wz157\\\\Desktop\\\\output.png"));
// Clean up
process.delete();
對了,首先要先引入依賴:
<dependency>
<groupId>org.aioobe.cloudconvert</groupId>
<artifactId>client</artifactId>
<version>1.1</version>
</dependency>
這就解決了,是不是很簡單,但是這個因為訪問的是國際網站,大家懂得,比較慢,但是也不是慢的不能接受,親自實踐,一張圖片大概幾秒的時間.
還有需要註意,如果使用過程出現NoSuch...Method,這個方法的出現只能證明jar包沖突,排除一下就可以.但是需要找到那個沖突,我在使用過程中和公司的父項目的jar沖突,所以直接將父項目中存在的jar排出了下,如下
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
第二種方案,就是硬編碼,借助的是wmf2svg這個jar,這個需要版本是0.9.8,比較高的版本,這個需要註意.
/**
* 圖片轉化成base64字符串
*
* @param imgFile
* @return
*/
public static String GetImageStr(String imgFile) {// 將圖片文件轉化為字節數組字符串,並對其進行Base64編碼處理
InputStream in = null;
byte[] data = null;
// 讀取圖片字節數組
try {
in = new FileInputStream(imgFile);
data = new byte[in.available()];
in.read(data);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
// 對字節數組Base64編碼
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data);// 返回Base64編碼過的字節數組字符串
}
/**
* 將svg字符串轉換為png
*
* @param svgCode svg代碼
* @param pngFilePath 保存的路徑
* @throws TranscoderException svg代碼異常
* @throws IOException io錯誤
*/
public static void convertToPng(String svgCode, String pngFilePath) throws IOException,
TranscoderException {
File file = new File(pngFilePath);
FileOutputStream outputStream = null;
try {
file.createNewFile();
outputStream = new FileOutputStream(file);
convertToPng(svgCode, outputStream);
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 將svgCode轉換成png文件,直接輸出到流中
*
* @param svgCode svg代碼
* @param outputStream 輸出流
* @throws TranscoderException 異常
* @throws IOException io異常
*/
public static void convertToPng(String svgCode, OutputStream outputStream)
throws TranscoderException, IOException {
try {
// Base64解碼
BASE64Decoder decoder = new BASE64Decoder();
byte[] bytes = decoder.decodeBuffer(svgCode);
for (int i = 0; i < bytes.length; ++i) {
if (bytes[i] < 0) {// 調整異常數據
bytes[i] += 256;
}
}
// 根據上面byte[]數組 生成 png 圖片
PNGTranscoder t = new PNGTranscoder();
TranscoderInput input = new TranscoderInput(new ByteArrayInputStream(bytes));
TranscoderOutput output = new TranscoderOutput(outputStream);
t.transcode(input, output);
outputStream.flush();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 尤其註意main方法,這裏僅僅是作為演示,在我們項目中完全是使用流進行傳遞,二進制+img標簽寫入數據庫的
public static void main(String[] args) throws IOException, TranscoderException {
String wmf = "C:\\Users\\wz157\\Desktop\\1.wmf";
String svg = "C:\\Users\\wz157\\Desktop\\222.svg";
// 這一步很重要
Main.main(new String[] {"-debug", "-replace-symbol-font", wmf, svg});
String strImg = GetImageStr(wmf);
// convertToPng(strImg, "C:\\Users\\wz157\\Desktop\\s.jpeg");
convertToPng(strImg, "C:\\Users\\wz157\\Desktop\\s.png");
}
加入依賴(應該就是下面的依賴):
<!-- wmf轉碼svg -->
<dependency>
<groupId>net.arnx</groupId>
<artifactId>wmf2svg</artifactId>
<version>0.9.8</version>
</dependency>
<!-- svg轉碼png -->
<dependency>
<groupId>org.codeartisans.thirdparties.swing</groupId>
<artifactId>batik-all</artifactId>
<version>1.8pre-r1084380</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.9.1</version>
</dependency>
這個在使用過程中也可能出現jar包沖突,是因為低版本導致的,也是因為我們父項目中的版本是0.9.5,所以版本比較低,也出現了那個找不到方法的問題,一樣的解決方案.
上面就是提供的word解析的方案,可以拿來直接使用,如果有問題或者哪沒有弄通可以聯系我(本人qq: 2585700076,微信是:wz15713598138),總結到此,一起加油
解析word公式的解決方案(office插入和wps插入不同的解決方案)