公司的合同相关部分提出需求要把html转doc文本,合同模块是现成的,基于现有合同模块初步考虑了一下可以采取以下三种方案
1.合同模块本身有合同的html展示,可以通过前端,基于html的Doc来直接生成doc文档。
2.公司的合同html文本是基于一套自己设计的机制,把合同条款存在数据库,以组合节点做拼接成html来做html文本动态生成的,一种可行方案是把这套机制作为数据来源,基于doc相关的开放接口包来从代码生成doc文档
3.把现在有的html文本用第三方包转化成doc,补充上缺失的信息
公司当前采取的是第1种方案,用jQuery的wordExport生成的doc。这种doc实际的存储方式还是用html语法,如果只是需要doc文档展示用这种方案是没问题的,但是如果要解析doc内容,貌似是没法直接解析的。这也是目前需要进行转换的原因。
如果要生成内容准确和丰富的doc,用第二种方案来做会比较好。第二种方案的缺点在于实现复杂,需要找一个第三方生成doc的包熟悉接口才能展开,而且当前合同数据存储时,内容中已经内嵌了html文本,用这种方案的话,需要再做内容过滤或者是从数据源的角度,额外规定一个适合于内容填充符合doc生成规则的数据来源。
第三种方案的话,也是需要找第三方包来支持的。缺点在于不确定第三方包转换限制和精确程度如何。做了几个简单和有代表性的样例测试,基于docx4j来做html转doc是可行的。不过缺点在于docx4j对于源html的语法要求很严格,很多在html里面支持的可选语法在docx4j转换时就不支持。
调试过程中统计到的语法错误至少有:
* 属性值缺失引号,比如item_id=123这种
* input标签缺少结束符,比如<input ... > ,并没有对于的/或</input>
* 错误的属性,比如 甲方,这种
数据库里面的合同条目有8400+条,一条一条调试太过于费时,所以考虑追加一步html格式化以及过滤的步骤,选取的是google的owasp-java-html-sanitizer。初步测试是可行的。
相关依赖包为:
<!-- docx4j 的pom依赖 --> <!-- https://mvnrepository.com/artifact/org.docx4j/docx4j --> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j</artifactId> <version>6.1.1</version> </dependency> <!-- owasp-java-html-sanitizer的pom依赖 --> <!-- https://mvnrepository.com/artifact/com.googlecode.owasp-java-html-sanitizer/owasp-java-html-sanitizer --> <dependency> <groupId>com.googlecode.owasp-java-html-sanitizer</groupId> <artifactId>owasp-java-html-sanitizer</artifactId> <version>20181114.1</version> </dependency>
规则过滤的关键代码
/** * * Description: 添加过滤规则<br> * * @author XXX <br> * @taskId <br> * @param configs * @return <br> */ private PolicyFactory loadFilterConfigs(Map<String, List<String>> configs) { HtmlPolicyBuilder builder = new HtmlPolicyBuilder(); Set<String> labelConfigs = configs.keySet(); for (String label : labelConfigs) { builder.allowElements(label);//设置支持的标签 List<String> attributes = configs.get(label);// 如果没属性要求返回空数组 log.debug("load label:" + label + " and attributes are :" + attributes); if (CollectionUtils.isEmpty(attributes)) { continue; } for (String attribute : attributes) {// 配置属性 if (StringUtil.isEmpty(attribute)) { continue; } builder.allowAttributes(attribute).onElements(label); } } return builder.toFactory(); }
这里补充说明一下,owasp-java-html-sanitizer支持对于单个标签定制过滤规则:
builder.allowElements(new ElementPolicy() { @Override public String apply(String elementName, List<String> attrs) {// 配置属性值过滤 System.out .println("filter :" + elementName + " attrs:" + attrs ); if (attrs != null) { int itemIdIndex = attrs.indexOf("item_id"); if (itemIdIndex < 0) {// 不包含item_id return elementName; } String item_id = attrs.get(itemIdIndex + 1); if (attrs.contains(item_id)) {// 需要过滤 return null; } return elementName; } return elementName; } }, label);
我们合同生成doc有一个需求是把没有选定的checkbox不生成到doc中,我上面代码的意图是,想先收集哪些块是没有选定的,然后通过上面的规律规则把没有选定的块过滤掉。但是实际测试结果是,这种规则过滤只对当前标签单层有效,对于嵌套的子标签并不会继承。比如:
<div item_id='123'> <input type='checkbox' item_id='123' /> <p>test</p> <div>
这种通过上面这种思路简单配置时,<p>标签的内容并不会受到item_id的影响而过滤。这与实际的需求不符合。
当前公司基于节点的模式,做节点解析来剔除节点是很简单的,所以上面方案测试不成功就没有继续往下深入,对于内容筛选的需求就直接基于节点来处理,owasp-java-html-sanitizer只专注于做html格式化就好了。