zoukankan      html  css  js  c++  java
  • 解析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("±", "&#x00B1;")
    					.replaceAll("∑","&sum;");
    			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 = 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),总结到此,一起加油

  • 相关阅读:
    form查询相关表
    获取datagrid更新初始值、新值
    数据库约束查询
    强名称工具(来着.NET)
    使用IE插件不能打开的解决
    导入导出报错
    List批量任务多线程执行工具类
    在C#中使用NHibernate框架查询数据
    使用bat文件顺序执行多个应用程序
    用C#实现抽象工厂模式
  • 原文地址:https://www.cnblogs.com/wadmwz/p/10460033.html
Copyright © 2011-2022 走看看