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