Разделенная текстовая строка в объекте 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
состоит из нескольких XWPFRun
s, а XWPFRun - заразительный текст, имеющий фиксированный стиль.
Поэтому, когда вы попытаетесь написать что-то вроде "[PLACEHOLDER_NAME]" в MS-Word, он создаст один XWPFRun. Но если вы как-то добавите еще несколько вещей, а затем вернетесь и измените "[PLACEHOLDER_NAME]" на что-то другое, никогда не гарантируется, что оно останется единичным. XWPFRun
вполне возможно, что он разделится на два цикла. AFAIK, так работает MS-Word.
Как избежать расщепления прогонов в таких случаях?
Решение: Есть два решения, о которых я знаю:
Скопируйте текст "[PLACEHOLDER_NAME]" в Блокнот или что-то еще. Сделайте необходимые изменения и скопируйте их обратно и вставьте вместо "[PLACEHOLDER_NAME]" в файл слов, таким образом, весь текст "[PLACEHOLDER_NAME]" будет заменен новым текстом, избегая разделения XWPFRuns.
Выберите "[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 как переменную в таком слове
- Поместите курсор на слово, которое вы хотите сделать одним прогоном.
- Нажмите CTRL и F9 вместе, и { } появится в сером цвете.
- Щелкните правой кнопкой мыши поле { } и выберите «Редактировать поле».
- Во всплывающем окне выберите Слияние почты из категорий, а затем MergeField из имен полей.
- Нажмите «ОК».
Для меня это не сработало, как я ожидал (каждый раз). В моем случае я использовал "${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.