确保Excel文档安全的有效加密策略和方法
753
2022-05-29
一个问题记录
获取标题列表编号的时候与实际值不符,查了一下发现aspose 19.8的发行说明里有一条,
而我项目里面使用的是aspose版本是19.7
具体参考:
Implemented an option that allows specifying whether to work with the original or revised version of a document
doc文档分析回顾
NAME检查:[分析:]Name会出现在两种类型的地方:第一种,遍历所有段落,当段落样式名包含"标题",由于Name是不包含中文的,使用[\u4e00-\u9fa5]+过滤掉包含中文的,然后进一步处理;另一种,出现在表头为"标示"的表格中
检查NAME包含多表格名称是否出现2个及以上连续_(下划线),如有可自动删除,只保留一个[分析:]当段文字中包含两个下划线时继续处理(para.getRange().replace) [注意:]para.getText()与para.toString(SaveFormat.TEXT).trim()的不同之处在于,举个例子,当你获取一个标题的文字的时候,前者只会获得标题编号后的文字,而后者则会包含标题的编号
检查NAME包含多表格名称字符间不能出现空格,如有可自动删除[分析:]当段文字中包含两个空格时继续处理
检查NAME多格式大小写X和x不能混用,比如x,只能一种;[分析:]当段文字中包含"Xx"或者"xX"时继续处理
ID检查:[分析:]Name会出现在两种类型的地方:第一种,Paragraph以"标示[:/:]"或"标识[:/:]"开始的非表头格式的后面部分;另一种,出现在表头为"标示"的表格中的第一列除了第一行的所有单元格内容
检查“标识”或"ID"关键词下是否缺失16进制或10进制ID(0x----)[分析:]当不包含"0x"的时候,正则查找[0-9]+获得其十进制的值后,String.format("0x%X(%d)",dex,dex)进行替换
检查关键词"标识"是否正确,其他别字(如标示)均报错,可自动修改为"标识"[分析:]使用para.getRange().replace进行归一化处理
检查“标识:0x1234()”,结尾不能出现其他符号,如有可自动删除多余符号[分析:]首先将中文括号归一化为英文括号,然后当字段包含括号时,如果包含"(0x"则可能是(0xccc)111这种错误样式,采用正则"\\((0x[A-Z0-9]+)\\)([0-9]+)"进行匹配,替换为m.group(1)+"("+m.group(2)+")",忽略包含顿号和短横的多ID并列的情况,由于前面已经移除了空格,所以现在只要判断括号后面有没有部分标点,使用正则"\\)[,.;,。;]+"进行查找;另外一种情况字段不包含括号,同样忽略包含顿号或者横杆的范围并列的情况,原来只包含十六进制的情况,采用"(0x[A-Z0-9]+)[,.;,。;]+"查找并消除后面的其他标点,然后通过"0x([A-Z0-9]+)"替换成"0x"+m.group(1)+"("+Integer.toString(hex)+")"补上十进制的字段
检查16进制ID是否大小写混用,如“0xAbC7”,可自动修改为大写[分析:]0x不要求大写,首先将"0X"替换成"0x",移除"0x"后自身与其转大写不相等则继续处理,for(Run run:para.getRuns()) {String ot = run.getText();run.setText(ot.toUpperCase());},最后把"0X"替换成"0x"
支持检查格式:word[分析:]限定到doc
报错机制:在错误处进行标记(批注形式)[分析:]添加批注的逻辑前面我是调用的时候即时就添加了,但是其实这样会带来一些问题,因为comment中也是添加了Paragraph的,这样就有可能影响一些分析后来的改进是不即时添加,
//先记录 public static void addComment(Document doc,int[] commentId,Paragraph para,String text){ CComment ccomment = new CComment(para,text); pendingComment.put(commentId[0],ccomment); commentId[0]+=1; } //最后集中添加批注 public static void addComment(Document doc) { for(Map.Entryentry:pendingComment.entrySet()) { Node[] runs = entry.getValue().Paragraph.getChildNodes(NodeType.RUN, true).toArray(); NodeCollection coms = entry.getValue().Paragraph.getChildNodes(NodeType.COMMENT, true); if(coms.getCount()>0) { Comment oldComment = (Comment)coms.get(0); oldComment.getFirstParagraph().getRuns().add(new Run(doc, "and err:"+entry.getValue().text.replace('\r', '\0').replace('\n', '\0'))); }else { if(runs==null || runs.length==0) { Comment comment = new Comment(doc, "amrf000", "DH", new Date()); entry.getValue().Paragraph.appendChild(comment); comment.getParagraphs().add(new Paragraph(doc)); comment.getFirstParagraph().getRuns().add(new Run(doc, "err:"+entry.getValue().text.replace('\r', '\0').replace('\n', '\0'))); }else { Comment comment = new Comment(doc, "amrf000", "DH", new Date()); /*CommentRangeStart start = new CommentRangeStart(doc, entry.getKey()); CommentRangeEnd end = new CommentRangeEnd(doc, entry.getKey()); runs[0].getParentNode().insertBefore(start,runs[0]); runs[runs.length-1].getParentNode().insertAfter(end,runs[runs.length-1]); end.getParentNode().insertBefore(comment,end);*/ entry.getValue().Paragraph.appendChild(comment); comment.getParagraphs().add(new Paragraph(doc)); comment.getFirstParagraph().getRuns().add(new Run(doc, "err:"+entry.getValue().text.replace('\r', '\0').replace('\n', '\0'))); } } }
支持文件夹下多word文档检查(至少5个)[分析:]这个没什么好说的在Swingworker中遍历处理就行,如果要采用并行提升处理效率的话,下一步在分析
表格检查[分析:]doc.getChildNodes(NodeType.TABLE, true)获取文档中的所有表格,通过工具类PageNumberFinder,finder.GetPage(table.getFirstChild())获取表格的起始页码
表格下空行检查检查文档中的表格下是否有且只有一个空行[分析:]table.getPreviousSibling();table.getNextSibling();获取表格相邻两个节点,如果next是段落并且toString(SaveFormat.TEXT).trim()不为空则异常;next.getNextSibling(),获取下下个节点,如果连续两个都为空,则异常
表格标题行重复属性未设置表格表头是否设置标题行重复属性[分析:]通过finder.GetPage(table.getLastChild())获取表格结束页码,当开始不等于结束页时即表格是跨页的,这个时候如果table.getFirstRow().getRowFormat().getHeadingFormat()为false则标题行重复属性未设置
表注位置检查表注位于表格上方[分析:]首先前面检测表后非空行已经一定程度上包含了这一点,另外当没有表注的时候也是要求判定正常的,遍历所有字段,如果字段格式为"Figure Description"或"Table Description"并且非空串,获取相邻的段落,如果为空则位置异常
图形检查[分析:]doc.getChildNodes(NodeType.SHAPE, true)获取所有图形
图注位置检查图注位于图片上方[分析:]如果前一个段落样式为"Figure Description"则为图注,finder.GetPage(pre)!=finder.GetPage(shape)则图形段落与上下同页未勾选;如果下一行是段落且段落内容为空,并且下下段落不为空(包含Shape也为不空)则正常,ImageData ida = shape.getImageData()如果ida.getTitle()不是说明和警告等则图后面非空行;遍历所有字段,如果字段格式为"Figure Description"或"Table Description"并且非空串,获取相邻的段落,如果为空则位置异常
标识文本[分析:]如果ida.getTitle()等于说明或者警告等
说明样式检查"检查文档中“说明”“窍门”上、下无空行,内容不跨页;[分析:]shape.getParentNode().getPreviousSibling()或者shape.getParentNode().getNextSibling()获取前后的段落,如果是说明类的上下任意为空则异常,如果是警告类的上面为空或警告后不为空则异常,如果警告类的后面的空行多于1则异常
“警告”“注意”“危险”上空1行,下不空行,内容不跨页"[分析:]循环获取后面的节点如果样式不等于"Notes Text"/"Notes Heading"/"Notes Text List"/"CAUTION Text"/"CAUTION Heading"/"CAUTION Text List"则该区段结束,获取结束的页码如果不等于开始则跨页了,如果结束后面的一个字段为空则异常(包含除了Heading的段落)
标题
标题不带标点符号1~3级标题不带标点符号(/~-+&除外)[分析:]样式名包含标题且Pattern.compile("[\\pP]+").matcher(para.text.replaceAll("/|~|-|&|_|\\.", "")).find()则异常
Block Label样式标题检查Block Label样式标题下必须要有内容
标题编号检查检查文档中标题编号方式是否与模板相同,具体包括1~4级标题、Item step样式,图号,表号是否连续 [分析:]para.Paragraph.getListFormat().getListLevel().getNumberStyle()!=NumberStyle.ARABIC则异常
文档内容
重复或错字词检查检查重复的字和词,如的的、功能功能[分析:]遍历错词作为匹配条件查找
成对符号检查检查中文状态的圆括号()和书名《》号是否成对出现[分析:]在段落中分别对各个符号进行while(m.find()) {++op;}计数,应该成对的不相等时则异常
数值范围单位检查文档中表示数值范围时,数字带有国际单位和表示量的数词时,两个数字都要加上单位,例如:错误“1~/-5Hz”,正确“1Hz~5Hz”[分析:]正则"\\d+[~|-]\\dHz"
空白页检查页面不应有多余空白页[分析:]通过工具类PageNumberFinder将所有段落分页缓存起来,doc.getPageCount()获取总页数,从1开始遍历段落缓存,循环该页段落,叠加段落正文直到trim()非空或者都循环完(当段落包含shape子时,正文也不为空),循环结束,如果叠加结果为空则异常
文档样式
页眉页脚检查[分析:]doc.getChildNodes(NodeType.HEADER_FOOTER, true)获取所有的页眉页脚,finder.GetPage(para.getParentNode())获取父节点的页码,页眉页脚是对于section的,获取的页码也是section的
封面无页眉页脚(文档前2页)[分析:],通过isHeader()可以获取当前section属于页眉的,页脚偶数页取HeaderFooterType.FOOTER_EVEN,奇数页取HeaderFooterType.FOOTER_PRIMARY,如果页码小于等于2时对应的页眉页脚非空则异常
前言及正文页眉处为1级标题名称[分析:]通过h.getRange().getFields()遍历,当类型为FieldType.FIELD_STYLE_REF时并且样式不为"Heading1 no Number"或"Contents"或"1"时,即前言及正文页眉处不为1级标题名称
前言及目录(包含表目录,图目录)页脚的页码使用希腊数字i、ii、iii….[分析:]section.getPageSetup().getPageNumberStyle()获取页码数字样式,正文前面的章节应该是NumberStyle.LOWERCASE_ROMAN后面的章节应该是NumberStyle.ARABIC
第1章节开始,页码使用数字1、2、3…"[分析:]通过doc.getChildNodes(NodeType.SECTION, true)获取所有章节,section.getBody().getFirstParagraph()获取章节正文第一个段落的样式名,如果等于"标题1"说明后面的其他章节包括本章是doc的正文部分,正文的第一个章节的页码需要重新计数检查section.getPageSetup().getRestartPageNumbering()是否为true
图表跨页检查检查文档中图表标注和图表本身是否在同一页[分析:]如果表前段落样式为"Table Description",finder.GetPage(pre)!=finder.GetPage(table.getFirstChild())则表格的属性段落与上下同页未勾选,同理对于图片前段落为"Figure Description"且finder.GetPage(shape)!=finder.GetPage(pre)则其与上下同页未勾选
链接[分析:]doc.getRange().getFields()获取所有域,field.getStart().getParentParagraph()可以获得域的父段落
链接正确性检查检查文档中所有链接是否正确(包含总目录、章节目录、文中的超链接及交叉引用)[分析:]field.isDirty()说明改域未更新,对于FieldType.FIELD_REF,获取其引用getBookmarkName()==>doc.getRange().getBookmarks().get(bookmarkName)如果结果为null说明引用的目标已经被删除了
归档检查
修订标记归档时不允许出现修订标记[分析:]doc.getRevisions().getCount()>0则原文存在修订标记,doc.stopTrackRevisions()doc.setTrackRevisions(false)可以关闭修订
批注归档时不允许出现批注[分析:]doc.getChildNodes(NodeType.COMMENT, true).getCount()>0则原文存在批注
=============================================================================
背景:
java环境下可以使用:
jacob是java调用com;
poi属于独立解析;
docx4j只支持docx,需要先将doc转换成docx转换;
试了试直接在doc里面写一些vba,感觉分析效率太低了;
考虑到jacob很这种方式机制上比较相似,目前考虑先使用poi看看能不能达到预期;
==>由于poi不方便在doc格式下添加批注(docx可以)
后来采用了aspose,这个是个有些商业属性的包
关于去除aspose功能限制,首先运行下面的初始化代码:
public static void doInit(){ InputStream is = xxxx.class.getClassLoader().getResourceAsStream("\\license.xml"); License aposeLic = new License(); try { aposeLic.setLicense(is); } catch (Exception e) { e.printStackTrace(); } }
运行后可以定位到报错的类和方法
我不是用的javassist进行修改,aspose最近的版本javassit修改结果有问题,
使用jd-gui打开后找到对应的类复制出来,在项目中的对应于该类文件创建一个一样的类,粘贴进去,修改掉一些语法错误,
将target中生成的.class用压缩工具拷贝回jar中对应的位置,
删除jar中的META-INF文件夹
参考:
aspose.word.19.3
aspose.words
Aspose-words 18.8
下面这个是个很有意思的问题:
现象p.getRange().replace似乎越界了,也像连续replace导致迭代器失效,下面这个方法可以解决这个问题
https://forum.aspose.com/t/range-replace-how-to-replace-first-occurence-only/40275
https://webcache.googleusercontent.com/search?q=cache:0WkLaYf_T4oJ:https://forum.aspose.com/t/range-replace-how-to-replace-first-occurence-only/40275+&cd=2&hl=zh-CN&ct=clnk
https://docs.aspose.com/display/wordsnet/Find+and+Replace
https://apireference.aspose.com/java/words/com.aspose.words/IReplacingCallback
https://blog.csdn.net/lqzkcx3/article/details/71414693
https://blog.csdn.net/WuLex/article/details/81702812
其他参考:
https://forum.aspose.com/t/multiple-lines-of-text-in-replace-command/126960
https://webcache.googleusercontent.com/search?q=cache:8jdDEL02sLQJ:https://docs.aspose.com/display/wordsjava/Find%2Band%2BReplace+&cd=2&hl=zh-CN&ct=clnk
https://forum.aspose.com/t/identifying-the-paragraph-and-edit-replace-delete/53097/2
https://www.cnblogs.com/ggjucheng/p/3423731.html
https://blog.csdn.net/weixin_34409703/article/details/85974476
测试doc内容
0xDA00 0xDA01 0xDA02 0xDA03 0xDA04# 0xDA05 0xDA06(1256);. dddd 0xDA00 0xDA01(7788) dddd 0xDA02(3256) 0xDA03 0xDA04 0xDA05
处理
class ReplaceFirstOccourance implements IReplacingCallback{
记录两个比较低级的问题
InputStream is=xxx.class.getClassLoader().getResourceAsStream("license.xml");
文件放在了resources目录下,在eclipse中调试启动可以正常找到到,打包成可执行jar找不到这个文件了,
需要将resources路径添加到Source中,这样打包后才会出现在类路径里
java 反射传递变长参数的例子
class PWorker extends SwingWorker
jar运行路径下如果放了和jar中类路径相同的.class文件,会导致java.lang.vertify的异常
附件: aspose-words-19.8-jdk17.zip 12.36M 下载次数:1次
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。