java使用Aspose.word保存word更新目录页码报错、页码不对以及样式错乱解决

我使用的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/

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值