Разделенная текстовая строка в объекте Apache POI XWPFRun

Я пытаюсь заменить шаблон DOCX документ с Apache POI используя XWPFDocument учебный класс. У меня есть теги в документе и JSON файл для чтения данных замены. Моя проблема заключается в том, что текстовая строка, кажется, отделена определенным образом в DOCX когда я изменяю его расширение на ZIP файл и открыть document.xml, Например [MEMBER_CONTACT_INFO] текст становится [MEMBER_CONTACT_INFO а также ] по отдельности. POI читает это так же, так как DOCX оригинал так. Это создает 2 XWPFRun объекты в абзаце, которые показывают текст как [MEMBER_CONTACT_INFO а также ] по отдельности.

У меня вопрос, есть ли способ заставить POI бежать как Word через объединение связанных прогонов или что-то в этом роде? Или как я могу решить эту проблему? Я сопоставляю тексты прогонов при замене и не могу найти свой тег, потому что он разделен на 2 разных объекта прогона.

Лучший

5 ответов

Это потратило так много моего времени однажды...

В основном, XWPFParagraph состоит из нескольких XWPFRuns, а XWPFRun - заразительный текст, имеющий фиксированный стиль.

Поэтому, когда вы попытаетесь написать что-то вроде "[PLACEHOLDER_NAME]" в MS-Word, он создаст один XWPFRun. Но если вы как-то добавите еще несколько вещей, а затем вернетесь и измените "[PLACEHOLDER_NAME]" на что-то другое, никогда не гарантируется, что оно останется единичным. XWPFRun вполне возможно, что он разделится на два цикла. AFAIK, так работает MS-Word.

Как избежать расщепления прогонов в таких случаях?

Решение: Есть два решения, о которых я знаю:

  1. Скопируйте текст "[PLACEHOLDER_NAME]" в Блокнот или что-то еще. Сделайте необходимые изменения и скопируйте их обратно и вставьте вместо "[PLACEHOLDER_NAME]" в файл слов, таким образом, весь текст "[PLACEHOLDER_NAME]" будет заменен новым текстом, избегая разделения XWPFRuns.

  2. Выберите "[PLACEHOLDER_NAME]", затем нажмите "Заменить" в MS-Word и замените на "[Ваш новый отредактированный заполнитель]", и это будет гарантировать, что ваш новый заполнитель будет использовать один XWPFRun.

Если вам снова нужно изменить новый заполнитель, выполните шаги 1 или 2.

Вот код Java, чтобы исправить эту проблему с отдельной текстовой строкой. Он также будет обрабатывать многоформатную замену строки.

public static void replaceString(XWPFDocument doc, String search, String replace) throws Exception{
  for (XWPFParagraph p : doc.getParagraphs()) {
    List<XWPFRun> runs = p.getRuns();
    List<Integer> group = new ArrayList<Integer>();
    if (runs != null) {
      String groupText = search;
      for (int i=0 ; i<runs.size(); i++) {
        XWPFRun r = runs.get(i);
        String text = r.getText(0);
        if (text != null)
            if(text.contains(search)) {
              String safeToUseInReplaceAllString = Pattern.quote(search);
              text = text.replaceAll(safeToUseInReplaceAllString, replace);
              r.setText(text, 0);
            }
            else if(groupText.startsWith(text)){
              group.add(i);
              groupText = groupText.substring(text.length());
              if(groupText.isEmpty()){
                runs.get(group.get(0)).setText(replace, 0);
                for(int j = 1; j<group.size(); j++){
                  p.removeRun(group.get(j));
                }
                group.clear();
                groupText = search;
              }
            }else{
              group.clear();
              groupText = search;
            }
        }
    }
}
for (XWPFTable tbl : doc.getTables()) {
   for (XWPFTableRow row : tbl.getRows()) {
      for (XWPFTableCell cell : row.getTableCells()) {
         for (XWPFParagraph p : cell.getParagraphs()) {
            for (XWPFRun r : p.getRuns()) {
              String text = r.getText(0);
              if (text.contains(search)) {
                String safeToUseInReplaceAllString = Pattern.quote(search);
                text = text.replaceAll(safeToUseInReplaceAllString, replace);
                r.setText(text);
              }
            }
         }
      }
   }
}

}

Чтобы быть уверенным, что слово будет считаться единственнымXWPFRun, Вы можете использовать merge_field как переменную в таком слове

  1. Поместите курсор на слово, которое вы хотите сделать одним прогоном.
  2. Нажмите CTRL и F9 вместе, и { } появится в сером цвете.
  3. Щелкните правой кнопкой мыши поле { } и выберите «Редактировать поле».
  4. Во всплывающем окне выберите Слияние почты из категорий, а затем MergeField из имен полей.
  5. Нажмите «ОК».

Для меня это не сработало, как я ожидал (каждый раз). В моем случае я использовал "${PLACEHOLDER} в тексте. Сначала нам нужно посмотреть, как Apache Poi распознает каждый абзац, который мы хотим перебрать с помощью Runs. Если вы углубитесь в построение файла docx, вы узнаете, что он" run - это последовательность символов текста с одинаковым стилем шрифта / размером шрифта / цветом / полужирным / курсивом и т. д. Таким образом, заполнитель иногда разделялся на части ИЛИ иногда весь абзац распознавался как один Выполнить, и было невозможно перебирать слова.
Что я сделал, так это выделил имя заполнителя жирным шрифтом в шаблоне документа. Затем при итерации через RUN я смог пройти через все имя заполнителя ${PLACEHOLDER}. Когда я заменил это значение на

for (XWPFRun r : p.getRuns()) {
  String text = r.getText(0);
  if (text != null && text.contains("originalText")) {
     text = text.replace("originalText", "newText");
     r.setText(text,0);
     }
  }

Я только что добавил r.isBold(false);после setText.
Таким образом, заполнитель распознается как другой запуск -> Я могу заменить конкретный заполнитель, и в обработанном документе у меня нет жирного шрифта, только простой текст.
Для меня одним из дополнительных преимуществ было то, что я могу быстрее находить заполнители в тексте. Итак, наконец, приведенный выше цикл выглядит так:

for (XWPFRun r : p.getRuns()) {
      String text = r.getText(0);
      if (text != null && text.contains("originalText")) {
         text = text.replace("originalText", "newText");
         r.setText(text,0);
         r.isBold(false);
         }
      }

Надеюсь, это кому-то поможет, а я трачу на это слишком много времени:)

У меня также была эта проблема несколько дней назад, и я не мог найти никакого решения. Я решил использовать PLACEHOLDER_NAME вместо [PLACEHOLDER_NAME]. Это работает нормально для меня, и это выглядит как один объект XWPFRun.

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