【问题标题】:Edit header in dotx/docx file编辑 dotx/docx 文件中的标题
【发布时间】:2020-03-27 18:19:29
【问题描述】:

我目前正在尝试从 dotx 格式的现有模板生成新的 docx 文件。我想更改标题中的名字、姓氏等,但由于某种原因我无法访问它们...... 我的方法如下:

 public void generateDocX(Long id) throws IOException, InvalidFormatException {

    //Get user per id
    EmployeeDTO employeeDTO = employeeService.getEmployee(id);

    //Location where the new docx file will be saved
    FileOutputStream outputStream = new FileOutputStream(new File("/home/user/Documents/project/src/main/files/" + employeeDTO.getId() + "header.docx"));

    //Get the template for generating the new docx file
    File template = new File("/home/user/Documents/project/src/main/files/template.dotx");
    OPCPackage pkg = OPCPackage.open(template);
    XWPFDocument document = new XWPFDocument(pkg);

    for (XWPFHeader header : document.getHeaderList()) {
        List<XWPFParagraph> paragraphs = header.getParagraphs();
        System.out.println("Total paragraphs in header are: " + paragraphs.size());
        System.out.println("Total elements in the header are: " + header.getBodyElements().size());
        for (XWPFParagraph paragraph : paragraphs) {
            System.out.println("Paragraph text is: " + paragraph.getText());
            List<XWPFRun> runs = paragraph.getRuns();
            for (XWPFRun run : runs) {
                String runText = run.getText(run.getTextPosition());
                System.out.println("Run text is: " + runText);
            }
        }
    }

    //Write the changes to the new docx file and close the document
    document.write(outputStream);
    document.close();
}

控制台中的输出是 1、null 或空字符串...我尝试了 hereherehere 的几种方法,但没有任何运气...

这是 template.dotx 里面的内容

【问题讨论】:

  • 我认为实际上那个包含名字、姓氏等的框是一个框架内容,或者至少当我点击它时 Libre Office 就是这么说的......

标签: java apache-poi apache-poi-4


【解决方案1】:

IBody.getParagraphsIBody.getBodyElements- 只获取直接在IBody 中的段落或正文元素。但是您的段落并不直接在其中,而是在单独的文本框或文本框架中。这就是为什么他们不能以这种方式得到。

由于*.docx 是一个ZIP 存档,包含用于文档、页眉和页脚的XML 文件,因此可以通过创建一个XmlCursor 来获取一个IBody 的所有文本运行,该XmlCursor 选择所有w:r @987654333 @元素。对于XWPFHeader,这可能看起来像这样:

 private List<XmlObject> getAllCTRs(XWPFHeader header) {
  CTHdrFtr ctHdrFtr = header._getHdrFtr();
  XmlCursor cursor = ctHdrFtr.newCursor();
  cursor.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//*/w:r");
  List<XmlObject> ctrInHdrFtr = new ArrayList<XmlObject>();
  while (cursor.hasNextSelection()) {
   cursor.toNextSelection();
   XmlObject obj = cursor.getObject();
   ctrInHdrFtr.add(obj);
  }
  return ctrInHdrFtr;
 }

现在我们有一个列表,其中包含该标题中的所有 XML 元素,它们是 Word 中的 text-run-elements。

我们可以有一个更通用的getAllCTRs,它可以从任何类型的IBody 中获取所有CTR 元素,如下所示:

 private List<XmlObject> getAllCTRs(IBody iBody) {
  XmlCursor cursor = null;
  List<XmlObject> ctrInIBody = new ArrayList<XmlObject>();

  if (iBody instanceof XWPFHeaderFooter) {
   XWPFHeaderFooter headerFooter = (XWPFHeaderFooter)iBody;
   CTHdrFtr ctHdrFtr = headerFooter._getHdrFtr();
   cursor = ctHdrFtr.newCursor();
  } else if (iBody instanceof XWPFDocument) {
   XWPFDocument document = (XWPFDocument)iBody;
   CTDocument1 ctDocument1 = document.getDocument();
   cursor = ctDocument1.newCursor();
  } else if (iBody instanceof XWPFAbstractFootnoteEndnote) {
   XWPFAbstractFootnoteEndnote footEndnote = (XWPFAbstractFootnoteEndnote)iBody;
   CTFtnEdn ctFtnEdn = footEndnote.getCTFtnEdn();
   cursor = ctFtnEdn.newCursor();
  }

  if (cursor != null) {
   cursor.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//*/w:r");
   while (cursor.hasNextSelection()) {
    cursor.toNextSelection();
    XmlObject obj = cursor.getObject();
    ctrInIBody.add(obj);
   }
  }
  return ctrInIBody ;
 }

现在我们有了IBody 中所有XML 元素的列表,它们是Word 中的文本运行元素。

我们可以像这样从它们中获取文本:

 private void printAllTextInTextRunsOfIBody(IBody iBody) throws Exception {
  List<XmlObject> ctrInIBody = getAllCTRs(iBody);
  for (XmlObject obj : ctrInIBody) {
   CTR ctr = CTR.Factory.parse(obj.xmlText());
   for (CTText ctText : ctr.getTList()) {
    String text = ctText.getStringValue();
    System.out.println(text);
   }
  }
 }

这可能显示了下一个挑战。因为Word 在创建文本运行元素时非常混乱。例如,您的占位符 &lt;&lt;Firstname&gt;&gt; 可以拆分为文本运行 &lt;&lt; + Firstname + &gt;&gt;。原因可能是格式不同或拼写检查或其他原因。甚至这也是可能的:&lt;&lt; + Lastname + &gt;&gt;; &lt;&lt; + YearOfBirth + &gt;&gt;。甚至这个:&lt;&lt;Firstname + &gt;&gt; &lt;&lt; + Lastname&gt;&gt;; &lt;&lt; + YearOfBirth&gt;&gt;。你看,用文本替换占位符几乎是不可能的,因为占位符可能被分成多个 tex-runs。

为避免这种情况,template.dotx 需要由知道自己在做什么的用户创建。

起初turn spell check off。语法检查也是如此。如果没有,所有发现的可能的拼写错误或语法违规都会在单独的文本运行中进行相应的标记。

其次确保整个占位符的格式相同。不同格式的文本也必须在单独的文本运行中。

我真的怀疑这是否能正常工作。但是你自己试试吧。

完整示例:

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;

import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlCursor;

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

public class WordEditAllIBodys {

 private List<XmlObject> getAllCTRs(IBody iBody) {
  XmlCursor cursor = null;
  List<XmlObject> ctrInIBody = new ArrayList<XmlObject>();

  if (iBody instanceof XWPFHeaderFooter) {
   XWPFHeaderFooter headerFooter = (XWPFHeaderFooter)iBody;
   CTHdrFtr ctHdrFtr = headerFooter._getHdrFtr();
   cursor = ctHdrFtr.newCursor();
  } else if (iBody instanceof XWPFDocument) {
   XWPFDocument document = (XWPFDocument)iBody;
   CTDocument1 ctDocument1 = document.getDocument();
   cursor = ctDocument1.newCursor();
  } else if (iBody instanceof XWPFAbstractFootnoteEndnote) {
   XWPFAbstractFootnoteEndnote footEndnote = (XWPFAbstractFootnoteEndnote)iBody;
   CTFtnEdn ctFtnEdn = footEndnote.getCTFtnEdn();
   cursor = ctFtnEdn.newCursor();
  }

  if (cursor != null) {
   cursor.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//*/w:r");
   while (cursor.hasNextSelection()) {
    cursor.toNextSelection();
    XmlObject obj = cursor.getObject();
    ctrInIBody.add(obj);
   }
  }
  return ctrInIBody ;
 }

 private void printAllTextInTextRunsOfIBody(IBody iBody) throws Exception {
  List<XmlObject> ctrInIBody = getAllCTRs(iBody);
  for (XmlObject obj : ctrInIBody) {
   CTR ctr = CTR.Factory.parse(obj.xmlText());
   for (CTText ctText : ctr.getTList()) {
    String text = ctText.getStringValue();
    System.out.println(text);
   }
  }
 }

 private void replaceTextInTextRunsOfIBody(IBody iBody, String placeHolder, String textValue) throws Exception {
  List<XmlObject> ctrInIBody = getAllCTRs(iBody);
  for (XmlObject obj : ctrInIBody) {
   CTR ctr = CTR.Factory.parse(obj.xmlText());
   for (CTText ctText : ctr.getTList()) {
    String text = ctText.getStringValue();
    if (text != null && text.contains(placeHolder)) {
     text = text.replace(placeHolder, textValue);
     ctText.setStringValue(text);
     obj.set(ctr);
    }
   }
  }
 }

 public void generateDocX() throws Exception {

  FileOutputStream outputStream = new FileOutputStream(new File("./" + 1234 + "header.docx"));

  //Get the template for generating the new docx file
  File template = new File("./template.dotx");
  XWPFDocument document = new XWPFDocument(new FileInputStream(template));

  //traverse all headers
  for (XWPFHeader header : document.getHeaderList()) {
   printAllTextInTextRunsOfIBody(header);

   replaceTextInTextRunsOfIBody(header, "<<Firstname>>", "Axel");
   replaceTextInTextRunsOfIBody(header, "<<Lastname>>", "Richter");
   replaceTextInTextRunsOfIBody(header, "<<ProfessionalTitle>>", "Skeptic");
  }  

  //traverse all footers
  for (XWPFFooter footer : document.getFooterList()) {
   printAllTextInTextRunsOfIBody(footer);

   replaceTextInTextRunsOfIBody(footer, "<<Firstname>>", "Axel");
   replaceTextInTextRunsOfIBody(footer, "<<Lastname>>", "Richter");
   replaceTextInTextRunsOfIBody(footer, "<<ProfessionalTitle>>", "Skeptic");
  }  

  //traverse document body; note: tables needs not be traversed separately because they are in document body
  printAllTextInTextRunsOfIBody(document);

  replaceTextInTextRunsOfIBody(document, "<<Firstname>>", "Axel");
  replaceTextInTextRunsOfIBody(document, "<<Lastname>>", "Richter");
  replaceTextInTextRunsOfIBody(document, "<<ProfessionalTitle>>", "Skeptic");

  //traverse all footnotes
  for (XWPFFootnote footnote : document.getFootnotes()) {
   printAllTextInTextRunsOfIBody(footnote);

   replaceTextInTextRunsOfIBody(footnote, "<<Firstname>>", "Axel");
   replaceTextInTextRunsOfIBody(footnote, "<<Lastname>>", "Richter");
   replaceTextInTextRunsOfIBody(footnote, "<<ProfessionalTitle>>", "Skeptic");
  }  

  //traverse all endnotes
  for (XWPFEndnote endnote : document.getEndnotes()) {
   printAllTextInTextRunsOfIBody(endnote);

   replaceTextInTextRunsOfIBody(endnote, "<<Firstname>>", "Axel");
   replaceTextInTextRunsOfIBody(endnote, "<<Lastname>>", "Richter");
   replaceTextInTextRunsOfIBody(endnote, "<<ProfessionalTitle>>", "Skeptic");
  }  


  //since document was opened from *.dotx the content type needs to be changed
  document.getPackage().replaceContentType(
   "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml",
   "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml");

  //Write the changes to the new docx file and close the document
  document.write(outputStream);
  outputStream.close();
  document.close();
 }

 public static void main(String[] args) throws Exception {
  WordEditAllIBodys app = new WordEditAllIBodys();
  app.generateDocX();
 }
}

顺便说一句:由于您的文档是从 *.dotx 打开的,因此需要将内容类型从 wordprocessingml.template 更改为 wordprocessingml.document。否则 Word 将不会打开生成的 *.docx 文档。见Converting a file with ".dotx" extension (template) to "docx" (Word File)

由于我对替换占位符文本方法持怀疑态度,因此我首选的方法是填写表格。见Problem with processing word document java。当然,这样的表单字段不能用于页眉或页脚。所以页眉或页脚应该从头开始创建。

【讨论】:

  • 感谢您的回复!我已经尝试了您提供的代码,但由于某种原因没有发生任何事情......实际上,应该在标题(printAllTextInTextRunsOfHeader)的运行中打印文本的方法根本没有打印任何内容。我发现第二个 for 循环实际上从未执行过。任何进一步的建议,或者您认为自己重新创建模板会更好(我不确定我是否会做得更好,但我可以尝试。 .) ?
  • @IvanNickSim:也许文本框甚至不在标题中,而是在文档中并且只放在标题上?请参阅我的答案中的补充。
  • 我会在一分钟内尝试你的更改。我检查了 docx 文件中的内容,并用图片更新了我的帖子。
  • @Alex 非常感谢你,伙计!修改完成后,现在名字、姓氏和职称都成功修改了!我想我也必须对页脚和其他页眉做同样的事情,因为它们也是不同的文档。我说得对吗?
  • @IvanNickSim:是的,你是对的。由于您已经解压缩了*.dotx,您已经看到了不同的文档部分。以下部分包含文本运行:document.xml 是文档正文,headerN.xmlfooterN.xml 是不同的页眉和页脚(默认,偶数,第一),endnotes.xml 包含尾注文本,footnotes.xml 包含脚注文本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多