Замените текстовые шаблоны внутри.docx (Apache POI, Docx4j или другие)

Я хочу сделать замены в документе MS Word (.docx) с помощью регулярного выражения (java RegEx):

Example: 
 …, с одной стороны, и %SOME_TEXT% именуемое в дальнейшем «Заказчик», в 
 лице  %SOME_TEXT%   действующего на основании %SOME_TEXT% с другой стороны, 
 заключили настоящий Договор о нижеследующем: …

Я пытался получить текстовые шаблоны (например, % SOME_TEXT%), используя Apache POI - XWPF и заменить текст, но замена не гарантируется, потому что POI разделяет запуски => Я получаю что-то вроде этого (System.out.println(run.getText(0))):

…
, с одной стороны, и 
%
SOME_TEXT
%

именуемое 
в дальнейшем «Заказчик», в лице

%
SOME
_
TEXT
%

пример кода:

FileInputStream fis = new FileInputStream(new File("document.docx"));
XWPFDocument document = new XWPFDocument(fis);
List<XWPFParagraph> paragraphs = document.getParagraphs();
paragraphs.forEach(para -> {
    para.getRuns().forEach(run -> {
        String text = run.getText(0);
        if (text != null) {
           System.out.println(text);
           // text replacement process
           // run.setText(newText,0);
        }
    });
});

Я нашел много похожих вопросов (например, " Замена текста в Apache POI XWPF "), но не нашел ответа на мою проблему (ответ здесь: " Разделенная текстовая строка в объекте Apache POI XWPFRun " предлагает неудобное решение).

Я пытался использовать docx4j и этот пример => " docx4j найти и заменить ", но docx4j работает аналогично.

Для docx4j, см. Stackru.com/questions/17093781/… - JasonPlutext

Я пытался использовать docx4j => documentPart.variableReplace(mappings);, но замена не гарантируется ( plutext / docx4j).

Вы использовали VariablePrepare? /questions/18417353/docx4j-ne-zamenyaet-peremennyie/18417361#18417361 - JasonPlutext

Да, нет результатов:

WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File("test.docx"));
HashMap<String, String> mappings = new HashMap<>();
VariablePrepare.prepare(wordMLPackage);//see notes
mappings.put("SOME_TEXT", "XXXX");
wordMLPackage.getMainDocumentPart().variableReplace(mappings);
wordMLPackage.save(new File("out.docx"));

Ввод \ вывод текста:

Input:
…, с одной стороны, и ${SOME_TEXT} именуемое в дальнейшем «Заказчик» ...
Output:
…, с одной стороны, и SOME_TEXT именуемое в дальнейшем «Заказчик» ...

Чтобы увидеть свои прогоны после VariablePrepare, включите ведение журнала уровня INFO для VariablePrepare или просто System.out.println(wordMLPackage.getMainDocumentPart().getXML())

Я понимаю, что шаблоны были разделены на разные прогоны, но главный вопрос темы, как не разделять шаблоны на разные прогоны. я использую System.out.println(wordMLPackage.getMainDocumentPart().getXML()) И пила:

<w:r>
   <w:t xml:space="preserve">, с одной стороны, и </w:t>
</w:r>
<w:r><w:t>$</w:t></w:r>
<w:r><w:t>{</w:t></w:r>
<w:r>
    <w:rPr>
       <w:rFonts w:eastAsia="Times-Roman"/>
          <w:color w:val="000000" w:themeColor="text1"/>
          <w:lang w:val="en-US"/>
    </w:rPr>
    <w:t>SOME</w:t>        <!-- First part of template: "SOME" -->
</w:r>
<w:r>
    <w:rPr>
        <w:rFonts w:eastAsia="Times-Roman"/>
        <w:color w:val="000000" w:themeColor="text1"/>
    </w:rPr>
    <w:t>_</w:t>           <!-- Second part of template: "_"   -->
</w:r>
<w:r>
    <w:rPr>
        <w:rFonts w:eastAsia="Times-Roman"/>
        <w:color w:val="000000" w:themeColor="text1"/>
        <w:lang w:val="en-US"/>
    </w:rPr>
    <w:t>TEXT</w:t>        <!-- Third part of template: "TEXT" -->
</w:r>
<w:r>
    <w:rPr>
        <w:rFonts w:eastAsia="Times-Roman"/>
        <w:color w:val="000000" w:themeColor="text1"/>
    </w:rPr>
    <w:t>}</w:t>
</w:r>

, что шаблон расположен в разных тегах xml и я не понимаю, ПОЧЕМУ...

Пожалуйста, помогите мне найти удобный подход для замены текста.....

1 ответ

Решение

Как вы видите, подход "делать замены в документе MS Word (.docx) с использованием регулярного выражения (java RegEx)" не очень хорош, поскольку вы никогда не можете быть уверены, что заменяемый текст будет вместе в одном текстовом прогоне. Лучший подход - использовать поля (поля слияния или поля формы) или элементы управления содержимым в Word.

Моими любимыми для таких требований остаются старые добрые поля формы в Word,

Первое преимущество состоит в том, что даже без защиты документа не будет возможности форматировать части содержимого поля формы по-разному и, таким образом, разбивать содержимое поля формы на разные серии (но см. Примечание 1). Второе преимущество заключается в том, что из-за серого фона поля формы хорошо видны в содержимом документа. И еще одним преимуществом является возможность применения защиты документа, так что возможно только заполнение полей формы, даже в графическом интерфейсе Word. Это действительно хорошо для сохранения таких договорных документов от нежелательных изменений.

(Примечание 1): по крайней мере Word предотвращает форматирование отдельных частей содержимого поля формы и, таким образом, разбивает содержимое поля формы на разные серии. Другое программное обеспечение для обработки текста (Writer например) может не соблюдать это ограничение, хотя.

Так что у меня будет шаблон Word примерно так:

Серые поля - это старые добрые формы. Word по имени Text1, Text2 а также Text3, Блоки текстовых полей выглядят так:

<xml-fragment w:rsidR="00833656" 
  ...
 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" 
 ... >
  <w:rPr>
    <w:rFonts w:eastAsia="Times-Roman"/>
    <w:color w:themeColor="text1" w:val="000000"/>
    <w:lang w:val="en-US"/>
  </w:rPr>
    <w:fldChar w:fldCharType="begin">
      <w:ffData>
        <w:name w:val="Text1"/>
        <w:enabled w:val="0"/>
        <w:calcOnExit w:val="0"/>
        <w:textInput>
          <w:default w:val="<введите заказчика>"/>
        </w:textInput>
      </w:ffData>
    </w:fldChar>
  </xml-fragment>
</xml-fragment>

Тогда следующий код:

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

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

import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.SimpleValue;
import javax.xml.namespace.QName;

public class WordReplaceTextInFormFields {

 private static void replaceFormFieldText(XWPFDocument document, String ffname, String text) {
  boolean foundformfield = false;
  for (XWPFParagraph paragraph : document.getParagraphs()) {
   for (XWPFRun run : paragraph.getRuns()) {
    XmlCursor cursor = run.getCTR().newCursor();
    cursor.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//w:fldChar/@w:fldCharType");
    while(cursor.hasNextSelection()) {
     cursor.toNextSelection();
     XmlObject obj = cursor.getObject();
     if ("begin".equals(((SimpleValue)obj).getStringValue())) {
      cursor.toParent();
      obj = cursor.getObject();
      obj = obj.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//w:ffData/w:name/@w:val")[0];
      if (ffname.equals(((SimpleValue)obj).getStringValue())) {
       foundformfield = true;
      } else {
       foundformfield = false;
      }
     } else if ("end".equals(((SimpleValue)obj).getStringValue())) {
      if (foundformfield) return;
      foundformfield = false;
     }
    }
    if (foundformfield && run.getCTR().getTList().size() > 0) {
     run.getCTR().getTList().get(0).setStringValue(text);
//System.out.println(run.getCTR());
    }
   }
  }
 }

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

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

  replaceFormFieldText(document, "Text1", "Моя Компания");
  replaceFormFieldText(document, "Text2", "Аксель Джоачимович Рихтер");
  replaceFormFieldText(document, "Text3", "Доверенность");

  document.write(new FileOutputStream("WordReplaceTextInFormFields.docx"));
  document.close();
 }
}

Этот код нуждается в полной банке всех схем ooxml-schemas-1.3.jar как упоминалось в FAQ-N10025.

Производит:

Другие вопросы по тегам