Apache POI docx文件内容控件解析

Apache POI docx file content control parse

我正在尝试解析包含内容控制字段的 docx 文件(使用 window 添加,参考图片,我的是另一种语言)

我正在使用库 APACHE POI。我找到 this question 了解如何操作。我使用了相同的代码:

import java.io.FileInputStream;

import org.apache.poi.xwpf.usermodel.*;

import java.util.List;
import java.util.ArrayList;

import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.apache.xmlbeans.XmlCursor;
import javax.xml.namespace.QName;

public class ReadWordForm {

 private static List<XWPFSDT> extractSDTsFromBody(XWPFDocument document) {
  XWPFSDT sdt;
  XmlCursor xmlcursor = document.getDocument().getBody().newCursor();
  QName qnameSdt = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "sdt", "w");
  List<XWPFSDT> allsdts = new ArrayList<XWPFSDT>();
  while (xmlcursor.hasNextToken()) {
   XmlCursor.TokenType tokentype = xmlcursor.toNextToken();
   if (tokentype.isStart()) {
    if (qnameSdt.equals(xmlcursor.getName())) {
     if (xmlcursor.getObject() instanceof CTSdtRun) {
      sdt = new XWPFSDT((CTSdtRun)xmlcursor.getObject(), document); 
//System.out.println("block: " + sdt);
      allsdts.add(sdt);
     } else if (xmlcursor.getObject() instanceof CTSdtBlock) {
      sdt = new XWPFSDT((CTSdtBlock)xmlcursor.getObject(), document); 
//System.out.println("inline: " + sdt);
      allsdts.add(sdt);
     }
    } 
   }
  }
  return allsdts;
 }

 public static void main(String[] args) throws Exception {

  XWPFDocument document = new XWPFDocument(new FileInputStream("WordDataCollectingForm.docx"));

  List<XWPFSDT> allsdts = extractSDTsFromBody(document);

  for (XWPFSDT sdt : allsdts) {
//System.out.println(sdt);
   String title = sdt.getTitle();
   String content = sdt.getContent().getText();
   if (!(title == null) && !(title.isEmpty())) {
    System.out.println(title + ": " + content);
   } else {
    System.out.println("====sdt without title====");
   }
  }

  document.close();
 }
}

问题是这段代码在我的 docx 文件中看不到这些字段,直到我在 LibreOffice 中打开它并 re-save 它。因此,如果文件来自 Windows 并被放入此代码中,则它看不到这些内容控制字段。但是如果我 re-save LibreOffice 中的文件(使用相同的格式)它开始看到这些字段,即使它丢失了一些数据(某些字段的标题和标签)。有人能告诉我这可能是什么原因吗,我该如何解决才能看到这些字段?或者有更简单的方法使用 docx4j 吗?不幸的是,网上没有太多关于如何使用这两个库的信息,至少我没有找到。

示例文件位于 google disk. 第一个不起作用,第二个起作用(在 Libre 中打开并将字段更改为其中一个选项后)。

根据您上传的示例文件,您的内容控件位于 table 中。您找到的代码仅直接从文档主体获取内容控件。

表格是 Word 中的怪兽,因为 table 每个单元格可能包含整个文档正文。这就是 table 单元格中的内容控件与主文档正文中的内容控件严格分开的原因。他们的 ooxml class 是 CTSdtCell 而不是 CTSdtRunCTSdtBlock 而在 apache poi 中他们的 class 是 XWPFSDTCell XWPFSDT.

如果只是阅读内容,那么可以回退到 XWPFAbstractSDT,它是 XWPFSDTCellXWPFSDT 的抽象父 class .所以下面的代码应该可以工作:

 private static List<XWPFAbstractSDT> extractSDTsFromBody(XWPFDocument document) {
  XWPFAbstractSDT sdt;
  XmlCursor xmlcursor = document.getDocument().getBody().newCursor();
  QName qnameSdt = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "sdt", "w");
  List<XWPFAbstractSDT> allsdts = new ArrayList<XWPFAbstractSDT>();
  while (xmlcursor.hasNextToken()) {
   XmlCursor.TokenType tokentype = xmlcursor.toNextToken();
   if (tokentype.isStart()) {
    if (qnameSdt.equals(xmlcursor.getName())) {
//System.out.println(xmlcursor.getObject().getClass().getName());
     if (xmlcursor.getObject() instanceof CTSdtRun) {
      sdt = new XWPFSDT((CTSdtRun)xmlcursor.getObject(), document); 
//System.out.println("block: " + sdt);
      allsdts.add(sdt);
     } else if (xmlcursor.getObject() instanceof CTSdtBlock) {
      sdt = new XWPFSDT((CTSdtBlock)xmlcursor.getObject(), document); 
//System.out.println("inline: " + sdt);
      allsdts.add(sdt);
     } else if (xmlcursor.getObject() instanceof CTSdtCell) {
      sdt = new XWPFSDTCell((CTSdtCell)xmlcursor.getObject(), null, null); 
//System.out.println("cell: " + sdt);
      allsdts.add(sdt);
     }
    } 
   }
  }
  return allsdts;
 }

但是正如您在代码行 sdt = new XWPFSDTCell((CTSdtCell)xmlcursor.getObject(), null, null) 中看到的那样,XWPFSDTCell 完全失去了与 table 和 table 行的连接。

没有直接从 XWPFTable 获取 XWPFSDTCell 的正确方法。因此,如果需要将 XWPFSDTCell 连接到它的 table,那么还需要解析 XML。这可能看起来像这样:

 private static List<XWPFSDTCell> extractSDTsFromTableRow(XWPFTableRow row) {
  XWPFSDTCell sdt;
  XmlCursor xmlcursor = row.getCtRow().newCursor();
  QName qnameSdt = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "sdt", "w");
  QName qnameTr = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "tr", "w");
  List<XWPFSDTCell> allsdts = new ArrayList<XWPFSDTCell>();
  while (xmlcursor.hasNextToken()) {
   XmlCursor.TokenType tokentype = xmlcursor.toNextToken();
   if (tokentype.isStart()) {
    if (qnameSdt.equals(xmlcursor.getName())) {
//System.out.println(xmlcursor.getObject().getClass().getName());
     if (xmlcursor.getObject() instanceof CTSdtCell) {
      sdt = new XWPFSDTCell((CTSdtCell)xmlcursor.getObject(), row, row.getTable().getBody()); 
//System.out.println("cell: " + sdt);
      allsdts.add(sdt);
     }
    } 
   } else if (tokentype.isEnd()) {
    //we have to check whether we are at the end of the table row
    xmlcursor.push();
    xmlcursor.toParent();  
    if (qnameTr.equals(xmlcursor.getName())) {
     break;
    }
    xmlcursor.pop();
   }
  }
  return allsdts;
 }

然后像这样从文档中调用:

...
  for (XWPFTable table : document.getTables()) {
   for (XWPFTableRow row : table.getRows()) {
    List<XWPFSDTCell> allTrsdts = extractSDTsFromTableRow(row);
    for (XWPFSDTCell sdt : allTrsdts) {
//System.out.println(sdt);
     String title = sdt.getTitle();
     String content = sdt.getContent().getText();
     if (!(title == null) && !(title.isEmpty())) {
      System.out.println(title + ": " + content);
     } else {
      System.out.println("====sdt without title====");
      System.out.println(content);
     }
    }
   }
  }
...

使用当前 apache poi 5.2.0 可以通过 XWPFTableRow.getTableICells. This gets al List of ICells 从 XWPFTableRow 获取 XWPFSDTCell,这是 XWPFSDTCell 也实现的接口。

所以下面的代码将从 table 中获取所有 XWPFSDTCell 而无需低级 XML 解析:

...
  for (XWPFTable table : document.getTables()) {
   for (XWPFTableRow row : table.getRows()) {
    for (ICell iCell : row.getTableICells()) {
     if (iCell instanceof XWPFSDTCell) {
      XWPFSDTCell sdt = (XWPFSDTCell)iCell;
//System.out.println(sdt);
      String title = sdt.getTitle();
      String content = sdt.getContent().getText();
      if (!(title == null) && !(title.isEmpty())) {
       System.out.println(title + ": " + content);
      } else {
       System.out.println("====sdt without title====");
       System.out.println(content);
      }
     }
    }
   }
  }
...