我使用的aspose-words版本为23.1
保存文件之前,使用aspose.word中的这个方法:
Document.updateFields()
目录格式出现错乱、省略号跑到前面、页码从某一页开始比实际多出一页或几页、页码全部变成一样:
更新域时会更新目录,但是页码可能会有偏差,原因是无法保证域的更新顺序,目录可能不是最后一个更新的,而在更新其他域时导致页码再次发生变化。而且这个更新方法不止会更新页码,还会导致样式和更新前发生改变。在更新文档域之前,我们首先需要更新一次初始的页码,调整为正确的页码,因为文件里面原有的页码可能就是错误的,最后可能出现页码不准确的情况。
这些问题是属于aspose-words的内部bug,在10.x的版本就存,过了n年之后,才在24.2版本修复,但是我升级之后进行验证,发现只解决了一部分问题,还是存在上面所说的省略号和页码全部一样的bug。
下面是24.2版本我使用的代码,保存文件流之前,我们可以自己去写更新域的逻辑,并且进行一些差错处理:
package com.zhou.wordreport.util;
import com.aspose.words.*;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Logger;
import java.awt.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import java.util.regex.Pattern;
/**
* word书签填充工具
* @author lang.zhou
* @since 2019/9/11
*/
@Slf4j
public class AsposeDocument extends Document{
private static final Logger LOGGER= Logger.getLogger(AsposeDocument.class);
private static final Pattern p = Pattern.compile("^.*NTP_(\\d)+$");
private static final int DOCX=SaveFormat.DOCX;
private static final int DOC=SaveFormat.DOC;
private static final int PDF=SaveFormat.PDF;
static {
//兼容linux字体
readFont();
}
@Getter
DocumentBuilder builder = null;
public AsposeDocument(String pathName) throws Exception {
this(FileUtils.getFile(pathName));
}
public AsposeDocument(File file) throws Exception {
this(FileUtils.readFileToByteArray(file));
}
public AsposeDocument(byte[] b) throws Exception {
this(new ByteArrayInputStream(b));
}
public AsposeDocument(InputStream b) throws Exception {
super(b);
builder = new DocumentBuilder(this);
}
public static void readFont(){
try{
String fontPath = "/home/aspose/fonts";
//保存为pdf时,linux系统有缺少字体,会出现中文乱码情况,读取从windows系统拷贝的字体,和linux系统本身的字体
FontSettings.getDefaultInstance().setFontsFolders(new String[]{fontPath,"/usr/share/fonts"},false);
}catch (Exception e){
LOGGER.error("读取字体文件失败,生成PDF时可能出现中文乱码!",e);
}
}
public AsposeDocument() throws Exception {
super();
builder = new DocumentBuilder(this);
}
public Set<String> getAllBookmark(){
Set<String> r = new HashSet<>();
BookmarkCollection collection = this.getRange().getBookmarks();
for (Bookmark bookmark : collection) {
r.add(bookmark.getName());
}
return r;
}
public static void main(String[] args) throws Exception {
new AsposeLicense().validate();
AsposeDocument util = new AsposeDocument("C:\\Users\\zhou\\Desktop\\目录更新异常的word.docx");
util.saveFile("C:\\Users\\zhou\\Desktop\\1.docx");
//util.saveFile("C:\\Users\\zhou\\Desktop\\1.pdf");
}
public void saveFile(String outputPath) throws Exception {
int saveFormat = DOCX;
String t=outputPath.toUpperCase(Locale.ENGLISH);
if(t.endsWith(".DOC")){
saveFormat = DOC;
}else if(t.endsWith(".PDF")){
saveFormat = PDF;
}
saveFile(outputPath,saveFormat);
}
/**
* 按格式保存文件
* @param outputPath 输出路径
* @param saveFormat 文件格式
*/
public void saveFile(String outputPath,int saveFormat) throws Exception {
SaveOptions options = SaveOptions.createSaveOptions(saveFormat);
this.update();
outputPath = outputPath.replace("..","");
File newFile = FileUtils.getFile(outputPath);
FileOutputStream fos = null;
try{
fos = FileUtils.openOutputStream(newFile);
this.save(fos,options);
fos.flush();
}finally {
IOUtils.closeQuietly(fos);
}
}
/**
* 如果页码是错的,先修正为正确的页码
*/
@SneakyThrows
private void fitInitPageNumber(FieldCollection fields){
for (int i = 0; i < fields.getCount(); i++) {
Field field = fields.get(i);
if(field.getType() == FieldType.FIELD_TOC){
FieldToc fieldToc = (FieldToc) field;
fieldToc.updatePageNumbers();
}else if(field.getType() == FieldType.FIELD_PAGE_REF){
FieldPageRef ref = (FieldPageRef) field;
ref.update(false);
}
}
}
@SneakyThrows
private boolean fitInitTocRef(FieldCollection fields){
//FieldType.FIELD_PAGE_REF => 目录中的每一个标题,这些标题如果设置成目录,则标题必须添加_Toc开头的隐藏标签(设置目录会自动添加标签),才能成功更新目录
//否则会报错“bookmark not defined”
//预处理所有目录标题,添加_Toc标签
Set<String> abs = this.getAllBookmark();
boolean isFixed = false;
for (int i = 0; i < fields.getCount(); i++) {
Field field = fields.get(i);
if(field.getType() == FieldType.FIELD_PAGE_REF){
FieldPageRef pageRef = (FieldPageRef) field;
String name = pageRef.getBookmarkName();
if (name.startsWith("_Toc")) {
if(!abs.contains(name)){
if(!isFixed){
log.warn("目录书签缺失,将进行自动修正");
}
isFixed = true;
//如果没有_Toc标签,手动添加一个
//log.info("{}-{}",name,pageRef.getResult());
//log.info("给【{}】添加标签:{}",pageRef.getResult(),name);
try{
builder.moveTo(pageRef.getStart());
builder.startBookmark(name);
builder.moveTo(pageRef.getEnd());
builder.endBookmark(name);
}catch (Exception e){
log.warn("给【{}】添加标签失败:{}",name,e.getMessage());
}
abs.add(name);
}
}
}
}
return isFixed;
}
/**
* 处理目录页码可能全部变成一样
*/
@SneakyThrows
private boolean isPageNumberSame(List<FieldPageRef> pageRefList){
//目录页码可能全部变成一样
String firstIndex = null;
String lastIndex = null;
for (int i = 0,j = pageRefList.size(); i < j; i++) {
FieldPageRef fieldPage = pageRefList.get(i);
if(i == 0){
firstIndex = fieldPage.getResult();
}
if(i == j - 1){
lastIndex = fieldPage.getResult();
}
}
if(Objects.equals(firstIndex, lastIndex)){
log.warn("模板目录异常,页码可能全部为{}",firstIndex);
return true;
}
return false;
}
/**
* 处理bookmark not defined的错误,但目录样式会改变
*/
@SneakyThrows
private boolean isTocBookmarkError(){
if (this.getRange().getText().toLowerCase(Locale.ENGLISH).contains("error! bookmark not defined")) {
log.warn("目录更新页码时出错:目录中的标题的标签丢失,尝试更新整个文档。请重新生成模板文件的目录保证此错误不会出现");
return true;
}
return false;
}
/**
* 更新文档域以及目录页码
*/
@SneakyThrows
public void update(){
//添加警告监听器
FontSubstitutionWarningCollector callback = new FontSubstitutionWarningCollector();
this.setWarningCallback(callback);
//this.updateFields();
FieldCollection fields = this.getRange().getFields();
try{
//修复目录引用书签
boolean isFixed = this.fitInitTocRef(fields);
//如果页码是错的,先修正为正确的页码
this.fitInitPageNumber(fields);
List<FieldToc> tocList = new ArrayList<>(fields.getCount());
List<FieldPageRef> pageRefList = new ArrayList<>(tocList.size());
//先更新文档全局
for (int i = 0; i < fields.getCount(); i++) {
Field field = fields.get(i);
if(field.getType() == FieldType.FIELD_TOC){
FieldToc fieldToc = (FieldToc) field;
tocList.add(fieldToc);
if(isFixed){
field.update();
}
}else if(field.getType() == FieldType.FIELD_PAGE_REF){
FieldPageRef pageRef = (FieldPageRef) field;
pageRefList.add(pageRef);
field.update();
}else{
field.update();
}
}
//最后更新页数
for (FieldToc fieldToc : tocList) {
fieldToc.updatePageNumbers();
}
//处理目录页码异常
if(this.isPageNumberSame(pageRefList) || this.isTocBookmarkError()){
this.updateFields();
}
log.debug("文件页数:{}",this.getPageCount());
}catch (Exception e){
log.error("自动修复目录时发生错误:",e);
this.updateFields();
}
}
}
package com.zhou.wordreport.util;
import com.aspose.words.IWarningCallback;
import com.aspose.words.WarningInfo;
import com.aspose.words.WarningType;
import lombok.extern.slf4j.Slf4j;
/**
* 收集并打印word文档中不存在的字体
* @author lang.zhou
* @since 2024/2/18 12:38
*/
@Slf4j
public class FontSubstitutionWarningCollector implements IWarningCallback {
public void warning(WarningInfo info) {
if (info.getWarningType() == WarningType.FONT_SUBSTITUTION){
//FontSubstitutionWarnings.warning(info);
log.warn("字体未找到:" + info.getDescription());
}/*else{
log.warn(info.getDescription());
}*/
}
//public WarningInfoCollection FontSubstitutionWarnings = new WarningInfoCollection();
}
注意:目录里面的每个标题,在文档中都会有一个引用, 这些引用都必须加上一个_Toc开头的隐藏书签,否则目录页码无法正常更新,手动更新目录时会出现"error! bookmark not defined"或者"标签未定义"的错误。
最近又遇到按上面的代码生成文件后,仍然出现目录中的页码比实际页码大,但是生成pdf时页码正常,经过对比word和pdf文件的内容,发现是由于word文件中存在表格,而表格刚好跨行了,设置的允许断行显示,所以实际占用的页面会减少,这个问题应该是aspose在计算目录页码时,没有考虑表格的跨页断行属性的原因。
经过验证,在表格属性里面把表格行设置不允许跨页显示就正常了
也可以在代码里面手动设置:
for (Row row : table.getRows()) {
//禁止行跨页断行
row.getRowFormat().setAllowBreakAcrossPages(false);
}
官方在2024年发布了新的版本,明确写了解决了目录页码错误的问题
上面代码无法解决问题,可以尝试升级到24.2版本试试,我升级之后问题就解决了 。jar包下载地址:
Aspose Repository Browser /java/repo/com/aspose/aspose-words/24.2/