Html转Doc的一种方案

寻技术 Html/CSS 2023年07月08日 124

  公司的合同相关部分提出需求要把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格式化就好了。

 

关闭

用微信“扫一扫”