前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 操作 Office:POI word 之文档信息提取

Java 操作 Office:POI word 之文档信息提取

作者头像
程序员架构进阶
发布2022-04-27 08:35:21
3K0
发布2022-04-27 08:35:21
举报
文章被收录于专栏:架构进阶架构进阶

系列文章:

Java 操作 Office:POI 之 word 生成

Java 操作 Office:POI 之 word 图片处理

Java 操作 Office:POI word 之网络图片处理

Java 操作 Office:POI word 之表格格式

Apache POI详解及Word文档读取示例

楔子

工作忙碌,又是好久不见。最近频繁地在与文档开发打交道,除了之前做过的文档生成,最近又在调研文档内容提取、解析相关的内容。顺手整理下来,供各位开发同学参考。

一 背景

简单来说,就是有一些文档数字化的场景。包括对word、pdf格式的文档进行内容提取,之后做格式解析,并根据具体的业务需求,还会有文本识别提取关键内容的一些动作。说起来看似简单,但仔细分析,其中会涉及ocr(pdf文档内容识别)、nlp(文本内容解析,例如标题提取、关键字解析等)等等。最简单的考虑,假设我们只对word文档做解析实现,也需要支持office api的sdk,以及支持模板配置解析的规则来实现内容解析。

再进一步缩小范围,我们先细化需求,都需要解析哪些内容?是否是word中易于识别的格式?例如标题提取,表格内容提取。如果再进一步细化,表格也分为word原生表格和内嵌excel表格。本篇就将以一个典型场景为例,抛砖引玉,给出一个实现方案。后续可以在此基础上再做深入探讨。

二 基于apache poi的内容提取

关于apache poi,基础信息介绍、jar包依赖的引入方式已经在之前的系列文章:Apache POI详解及Word文档读取示例 中做了介绍,所以这里不再赘述。我们可以使用poi提供的api来读取word的doc 和 docx格式文档,并能够获取到每个段落的格式(style),判断是目录,正文,还是标题等。

这里再强调一下,因为doc 和 docx是两种完全不同的格式,所以我们考虑把word文档的文本内容转为统一的格式,来存储格式信息,便于后续的统一处理。

2.1 文本数据结构

一个简单的结构定义如下,其中titleLevel代表标题级别(标题1-->1,正文-->-1),style为格式的中文描述,type代表内容类型(默认为文本,其他有图片、表格等),text表示文本内容,content有些冗余,表示其他非文本格式的内容(例如图片存储base64编码)。

import lombok.Builder;
import lombok.Data;

/**
 * 段落风格和内容实体
 */
@Data
@Builder
public class StyleTextVO {
    private Integer titleLevel;
    private String style;
    private String type;
    private String text;
    private String content;
}

2.2 标题识别

一些常量定义:

/**
     * word doc的标题格式前缀,标题 styleName:标题 1,标题 2,...
     * 正文 为 "正文"
     */
    private static final String docTitlePrefix = "标题 ";

    private static final String docText = "正文";

    /**
     * word docx的标题格式前缀,标题 styleName:heading 1,heading 2,...;
     * 正文 为 null
     */
    private static final String docXTitlePrefix = "heading ";

    private static final String structTitlePrefix = "h";

    private static final String structText = "p";

2.2.1 doc文档内容解析

重点:1、文档读取方式:HWPFDocument;2、格式获取:通过Range获取所有段落的数量,并逐个遍历,再通过文档的StyleSheet,获取格式名;3、根据业务需要,对格式做一些基础转换

public List<StyleTextVO> readDoc(String path) throws Exception {
        List<StyleTextVO> styleTextVOList = new ArrayList<>();
        InputStream is = new FileInputStream(path);
        HWPFDocument doc = new HWPFDocument(is);
        Range r = doc.getRange();

        for (int i = 0; i < r.numParagraphs(); i++) {
            Paragraph p = r.getParagraph(i);
            int styleIndex = p.getStyleIndex();

            StyleSheet style_sheet = doc.getStyleSheet();
            StyleDescription style = style_sheet.getStyleDescription(styleIndex);
            String styleName = style.getName();
            int titleLevel = -1; // 默认为正文
            if (styleName.equals(docText)){
                styleName = structText;
            }else{
                titleLevel = styleIndex;
                styleName = styleName.replace(docTitlePrefix, structTitlePrefix);
            }

            StyleTextVO styleTextVO = StyleTextVO.builder()
                    .titleLevel(titleLevel).style(styleName).text(p.text()).build();
            styleTextVOList.add(styleTextVO);
        }

        doc.close();

        return styleTextVOList;
    }

2.2.2 docx文档内容解析

同2.2.1,差别在于通过XWPFDocument读取docx文档;通过paragraph.getStyleID()取得styleID。

/**
     * 读取docx内容
     * @param path
     * @throws Exception
     */
    public List<StyleTextVO> readDocX(String path) throws Exception {
        List<StyleTextVO> styleTextVOList = new ArrayList<>();

        InputStream is = new FileInputStream(path);
        XWPFDocument document = new XWPFDocument(is);

        List<XWPFParagraph> paragraphList = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphList){
            String styleId = paragraph.getStyleID();

            String styleName = "正文";
            int titleLevel = -1;
            if (!StringUtils.isEmpty(styleId)){
                try{
                    titleLevel = Integer.valueOf(styleId);
                    XWPFStyle style = document.getStyles().getStyle(styleId);
                    styleName = style.getName();
                }catch (Exception e){
                    log.error("不支持的标题格式, msg:{}", e.getMessage());
                }
            }

            if (styleId == null){
                styleName = structText;
            }else{
                styleName = styleName.replace(docXTitlePrefix, structTitlePrefix);
            }
            StyleTextVO styleTextVO = StyleTextVO.builder()
                    .titleLevel(titleLevel).style(styleName).text(paragraph.getText()).build();
            styleTextVOList.add(styleTextVO);
        }

        this.close(is);

        return styleTextVOList;
    }

2.3 表格提取

与上面相同,doc和docx的表格实体也有所不通,所以自定义表格数据结构如下:

import lombok.Data;
import lombok.ToString;

import java.util.List;

@ToString
@Data
public class WordTableVO {

    private int rowCnt;

    private int colCnt;

    private List<String> titleList;

    private List<List<String>> contentTable;
}

2.3.1 doc文档表格提取


   /**
     * 读取doc格式文档中的表格
     * @param in
     * @throws Exception
     */
    public List<WordTableVO> getTableFromDoc(FileInputStream in) throws Exception {
        List<WordTableVO> wordTableVOS = new ArrayList<>();
        POIFSFileSystem pfs = new POIFSFileSystem(in);
        HWPFDocument hwpf = new HWPFDocument(pfs);
        Range range = hwpf.getRange(); 
        TableIterator it = new TableIterator(range);
        while (it.hasNext()) {
            Table tb = it.next();
            WordTableVO wordTableVO = new WordTableVO();
            wordTableVO.setRowCnt(tb.numRows());
            wordTableVO.setContentTable(new ArrayList<>());
            for (int i = 0; i < tb.numRows(); i++) {
                TableRow tr = tb.getRow(i);
                if (i == 0){
                    wordTableVO.setColCnt(tr.numCells());
                }

                List<String> rowCellList = new ArrayList<>();

                for (int j = 0; j < tr.numCells(); j++) {
                    TableCell td = tr.getCell(j);
                    String lineValue = "";
                    for(int k = 0; k < td.numParagraphs(); k++){
                        Paragraph para = td.getParagraph(k);
                        String s = para.text();
                        // 去除特殊符号
                        if(null != s && !"".equals(s)){
                            s = s.substring(0, s.length()-1);
                        }
                        lineValue += s.replace(" ", "");
                    }
                    rowCellList.add(lineValue);
                }
                wordTableVO.getContentTable().add(rowCellList);
            }
            wordTableVOS.add(wordTableVO);
        }

        return wordTableVOS;
    }

2.3.2 docx文档表格提取

 /**
     * word 2007文档解析,表格提取
     * @param in
     * @throws Exception
     */
    public List<WordTableVO> getTableFromDocx(FileInputStream in) throws Exception{
        List<WordTableVO> wordTableVOS = new ArrayList<>();

        XWPFDocument xwpf = new XWPFDocument(in);
        Iterator<XWPFTable> it = xwpf.getTablesIterator();
        while(it.hasNext()){
            XWPFTable table = it.next();
            List<XWPFTableRow> rows = table.getRows();

            WordTableVO wordTableVO = new WordTableVO();
            wordTableVO.setRowCnt(rows.size());
            wordTableVO.setContentTable(new ArrayList<>());

            // 逐行读取表格
            for (int i = 0; i < rows.size(); i++) {
                XWPFTableRow  row = rows.get(i);

                List<XWPFTableCell> cells = row.getTableCells();
                if (i == 0){
                    wordTableVO.setColCnt(cells.size());
                }

                List<String> rowCellList = new ArrayList<>();

                for (int j = 0; j < cells.size(); j++) {
                    XWPFTableCell cell = cells.get(j);
                    rowCellList.add(cell.getText());
                }
                wordTableVO.getContentTable().add(rowCellList);
            }
            wordTableVOS.add(wordTableVO);
        }

        return wordTableVOS;
    }

三 接下来可以做什么?

说句废话,有了结构化数据,接下来自然是可以识别我们的业务。那么业务可能是做哪些?

首先,标题通常是重要信息的摘要,那么我们就可以根据标题进行定位,定位到制定的段落,并提取相关信息。再细化一点,如下是某个系统的文档:

我们希望提取到系统的功能清单,如果是批量或者动态的解析(非人工)该怎么做?显然,可以先定位到“系统功能清单”这个章节,然后提取表格信息;再通过表头来获取各列(模块、功能清单)的内容。关于如何定位到“系统功能清单”章节,简单的场景是通过字符串匹配,稍复杂一点,可以提供关键词表(字典),来进行模式匹配,表头处理也可以用这种模式。总之,我们有了基础工具和资料,之后就可以做很多事情了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员架构进阶 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 楔子
  • 一 背景
  • 二 基于apache poi的内容提取
    • 2.1 文本数据结构
      • 2.2 标题识别
        • 2.2.1 doc文档内容解析
        • 2.2.2 docx文档内容解析
      • 2.3 表格提取
        • 2.3.1 doc文档表格提取
        • 2.3.2 docx文档表格提取
    • 三 接下来可以做什么?
    相关产品与服务
    图片处理
    图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档