Apache POI docx文件内容控件解析。

3

我正在尝试解析包含内容控件字段的docx文件(这些字段是使用窗口添加的,如下图所示,我的是另一种语言)

enter image description here

我正在使用APACHE POI库。我在这个问题上找到了如何做的方法。我使用了相同的代码:

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中打开并重新保存它。因此,如果文件来自Windows并被放入此代码中,则它不会查看这些内容控件字段。但是,如果我在LibreOffice中重新保存文件(使用相同的格式),它开始查看这些字段,尽管它会丢失一些数据(某些字段的标题和标记)。有人能告诉我可能的原因是什么,我该如何修复才能看到这些字段?或者也许有更简单的方法可以使用docx4j吗?不幸的是,关于如何在互联网上使用这两个库进行操作的信息很少,至少我没有找到。示例文件位于谷歌磁盘上。第一个文件无法正常工作,第二个文件可以正常工作(在打开Libre并将字段更改为其中之一后)。

1
当文件是使用Microsoft Word 2016生成时,对我有效。现在无法测试Office 365。稍后会尝试。你能否在某个地方上传一个出现问题的样本文件? - Axel Richter
@AxelRichter,是的,我稍后会上传示例文件并重新编辑问题,提前感谢! - Arzybek
@AxelRichter,刚刚添加了示例文件。 - Arzybek
1个回答

4
根据您上传的示例文件,您的内容控件位于表格中。您找到的代码仅直接从文档正文获取内容控件。
在Word中,表格是非常棘手的事情,因为表格单元格可能包含整个文档正文。这就是为什么表格单元格中的内容控件严格与主文档正文中的内容控件分开的原因。它们的ooxml类是CTSdtCell,而不是CTSdtRun或CTSdtBlock,在apache poi中,它们的类是XWPFSDTCell,而不是XWPFSDT。
如果只涉及读取内容,则可以回退到XWPFAbstractSDT,它是XWPFSDTCell以及XWPFSDT的抽象父类。因此,以下代码应该有效:
 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完全失去了与表格和表格行的联系。

没有一种适当的方法可以直接从XWPFTable获取XWPFSDTCell。因此,如果需要获取与其表格连接的XWPFSDTCell,则还需要解析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.getTableICellsXWPFTableRow 中获取 XWPFSDTCell。这将得到一个包含所有 ICellList,其中 XWPFSDTCell 也实现了该接口。

因此,以下代码将获取所有表格中的 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);
      }
     }
    }
   }
  }
...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接