From 52a9f6e00d6dcbbea05481a63113598a9b6770b1 Mon Sep 17 00:00:00 2001 From: akulikov Date: Mon, 19 Dec 2022 17:47:38 +0300 Subject: [PATCH] insert values in docx (raw) --- .../reports/services/DOCXService.java | 146 +++++++++++++++--- .../reports/services/DOCXServiceTest.java | 42 ++--- .../src/test/resources/REC.ACTV_TEST.docx | Bin 10230 -> 10290 bytes 3 files changed, 150 insertions(+), 38 deletions(-) diff --git a/clearing-parent/reports-service/src/main/java/ru/spcex/clearing/reports/services/DOCXService.java b/clearing-parent/reports-service/src/main/java/ru/spcex/clearing/reports/services/DOCXService.java index 20012d16f..ac1a078ea 100644 --- a/clearing-parent/reports-service/src/main/java/ru/spcex/clearing/reports/services/DOCXService.java +++ b/clearing-parent/reports-service/src/main/java/ru/spcex/clearing/reports/services/DOCXService.java @@ -1,15 +1,16 @@ package ru.spcex.clearing.reports.services; import org.apache.poi.xwpf.usermodel.*; +import org.slf4j.Logger; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; +import java.io.*; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Сервис для работы с DOCX файлами @@ -19,18 +20,61 @@ public class DOCXService { /** * При инициализации сохраняет все имеющиеся ключи (ищет по маске keyPattern), найденные в текстовых элементах документа */ - private final Map bodyElementForKey = new HashMap<>(); - private final Pattern KEY_PATTERN = Pattern.compile("\\$\\{.*?}"); - private final int MAX_POSITION = 128; + private List keyPositions = new LinkedList<>(); /** - * Инициализация шаблона - * - * @param templateFile файл шаблона + * Паттерн поиска ключей в файле */ - public void init(File templateFile) throws IOException { + private Pattern KEY_PATTERN = Pattern.compile("\\$\\{.*?}"); + + /** + * (ре)Инициализация сервиса + */ + public void init(String keyPattern) { + KEY_PATTERN = Pattern.compile(keyPattern); + keyPositions = new LinkedList<>(); + } + + /** + * Вставка значений в шаблон. Шаблон может содержать любое количество ключей, при этом эти ключи должны содержаться в + * valuesMap. + * + * @param templateFile Файл шаблона + * @param outputFile Выходной файл. Должен быть либо файлом (будет перезаписан), либо отсутствовать + * @param valuesMap Мапа значений. В файле шаблона будут искаться ключи этой мапы и заменяться на соответствующие значения. + * @param log Если будет передан null, то метод будет выбрасывать исключения. В противном случае будет записан лог и возвращено false + * @param checkKeys True - строгий режим, где будет проверено наличие всех ключей из файла в valuesMap. + * False - мягкий режим, отсутствующие ключи будут пропущены + * + * @return true - если вставка прошла успешно + */ + public boolean insertValues(File templateFile, + File outputFile, + Map valuesMap, + Logger log, + boolean checkKeys) throws Exception { assert templateFile != null && templateFile.exists() && templateFile.isFile(); - try (InputStream inputStream = new FileInputStream(templateFile)) { + + if (outputFile == null || outputFile.isDirectory()) { + String logMsg = "Can't open output file. Output file: %s".formatted(outputFile == null ? "null" : outputFile.getAbsolutePath()); + if (log == null) throw new Exception(logMsg); + log.error(logMsg); + return false; + } + + if (outputFile.exists()) { + if (log != null) log.warn("Output file {} exist and will be overwritten", outputFile.getAbsolutePath()); + boolean deleteOk = outputFile.delete(); + if (!deleteOk) { + String logMsg = "Can't delete file %s".formatted(outputFile.getAbsolutePath()); + if (log == null) throw new Exception(logMsg); + log.error(logMsg); + return false; + } + } + + try (InputStream inputStream = new FileInputStream(templateFile); + OutputStream outputStream = new FileOutputStream(outputFile)) { XWPFDocument doc = new XWPFDocument(inputStream); for (IBodyElement element : doc.getBodyElements()) { @@ -41,10 +85,70 @@ public class DOCXService { } } - System.out.println("hello"); + if (checkKeys) { + List notPresentInMap = new LinkedList<>(); + boolean checkOk = checkIntersection(valuesMap, notPresentInMap); + if (!checkOk) { + String logMsg = "Can't insert values in file %s. Keys not in map: %s." + .formatted(templateFile.getAbsolutePath(), + String.join(", ", notPresentInMap)); + if (log == null) throw new Exception(logMsg); + log.error(logMsg); + return false; + } + } + + insertValues(valuesMap); + + doc.write(outputStream); + } + + return true; + } + + /** + * Проверка прочтённых ключей (keyPositions) и переданных (valuesMap.keySet()) на соответствие. + * Для успешной вставки мапа должна содержать все ключи, имеющиеся в файле. + * Метод вызывается для проверки только в строгом режиме + */ + private boolean checkIntersection(Map valuesMap, List notPresentInMap) { + Set keysFromMap = valuesMap.keySet(); + Set keysFromFile = keyPositions.stream().map(keyPosition -> keyPosition.key).collect(Collectors.toSet()); + List intersection = keysFromMap.stream() + .filter(keysFromFile::contains) + .collect(Collectors.toList()); + if (intersection.size() == keysFromFile.size()) return true; + + List notPresent = new LinkedList<>(keysFromFile); + notPresent.removeAll(intersection); + if (notPresentInMap != null) notPresentInMap.addAll(notPresent); + + return false; + } + + /** + * Вставка значений в элементы XWPFRun + */ + private void insertValues(Map valuesMap) { + for (KeyPositionInDoc keyPosition : keyPositions) { + String key = keyPosition.key; + + String valueFromMap = valuesMap.get(key); + if (valueFromMap == null) continue; + + XWPFRun xwpfRun = keyPosition.run; + int pos = keyPosition.pos; + do { + String srcText = xwpfRun.getText(pos); + xwpfRun.setText(srcText.replace(key, valueFromMap), pos); + } while (xwpfRun.getText(pos).contains(key)); } } + /** + * Сбор ключей из таблицы. + * Ячейка таблицы может являться как параграфом, так и таблицей, поэтому метод работает рекурсивно + */ private void collectKeysFromTable(XWPFTable srcTable) { for (XWPFTableRow row : srcTable.getRows()) { for (XWPFTableCell cell : row.getTableCells()) { @@ -59,17 +163,20 @@ public class DOCXService { } } + /** + * Сбор ключей из параграфа + */ private void collectKeysFromParagraph(XWPFParagraph paragraph) { - for (XWPFRun run : paragraph.getRuns()) { try { + int MAX_POSITION = 128; for (int currPos = 0; currPos < MAX_POSITION; currPos++) { String currText = run.getText(currPos); if (currText == null) break; Matcher matcher = KEY_PATTERN.matcher(currText); while (matcher.find()) { String key = matcher.group(); - bodyElementForKey.put(key, new TextPositionOnRun(run, currPos)); + keyPositions.add(new KeyPositionInDoc(key, run, currPos)); } } } catch (IndexOutOfBoundsException ignored) { @@ -78,13 +185,16 @@ public class DOCXService { } } - static class TextPositionOnRun { + static class KeyPositionInDoc { + String key; XWPFRun run; int pos; - public TextPositionOnRun(XWPFRun run, int pos) { + public KeyPositionInDoc(String key, XWPFRun run, int pos) { + this.key = key; this.run = run; this.pos = pos; } } + } diff --git a/clearing-parent/reports-service/src/test/java/ru/spcex/clearing/reports/services/DOCXServiceTest.java b/clearing-parent/reports-service/src/test/java/ru/spcex/clearing/reports/services/DOCXServiceTest.java index 6aad9d549..528d64be2 100644 --- a/clearing-parent/reports-service/src/test/java/ru/spcex/clearing/reports/services/DOCXServiceTest.java +++ b/clearing-parent/reports-service/src/test/java/ru/spcex/clearing/reports/services/DOCXServiceTest.java @@ -1,31 +1,33 @@ package ru.spcex.clearing.reports.services; -import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.junit.jupiter.api.Test; -import java.io.*; +import java.io.File; +import java.util.HashMap; +import java.util.Map; class DOCXServiceTest { - @Test - public void testREC_ACTV() { - String inputFilename = "REC.ACTV_TEST.docx"; - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(inputFilename); - InputStream testStream = getClass().getClassLoader().getResourceAsStream("1.docx"); - OutputStream outputStream = new FileOutputStream("test.docx")) { - XWPFDocument templateDoc = new XWPFDocument(inputStream); - XWPFDocument testDoc = new XWPFDocument(testStream); - testDoc.getParagraphs().get(6).getRuns().get(1).setText("========АОВЛДЫАОЫВЛДОАЛЫВДАОЫВЛДАОЫВ========"); - testDoc.write(outputStream); - System.out.println("h"); - } catch (IOException e) { - e.printStackTrace(); - } - } @Test - public void test() throws IOException { + public void test() throws Exception { + Map valuesMap = new HashMap<>(); + valuesMap.put("${company.fullName}", "======== COMPANY FULL NAME ========="); + valuesMap.put("${count}", "======== COUNT ========="); + valuesMap.put("${profileDocument.issueDate}", "======== ISSUE DATE ========="); + valuesMap.put("${test1}", "======== TEST 1 ========="); + valuesMap.put("${test2}", "======== TEST 2 ========="); + valuesMap.put("${test3}", "======== TEST 3 ========="); + DOCXService service = new DOCXService(); - service.init(new File(getClass().getClassLoader().getResource("REC.ACTV_TEST.docx").getPath())); - System.out.println("p"); + service.insertValues(new File(getClass().getClassLoader().getResource("REC.ACTV_TEST.docx").getPath()), + new File("output.docx"), + valuesMap, + null, + false); + + File file = new File("output.docx"); + System.out.println("test"); + file.delete(); } + } \ No newline at end of file diff --git a/clearing-parent/reports-service/src/test/resources/REC.ACTV_TEST.docx b/clearing-parent/reports-service/src/test/resources/REC.ACTV_TEST.docx index aca71b0e5e12d13ad03f80660147bd8eb5b4fe23..66865e6cc84f29bf58bd91c27bc67c825725a9f6 100644 GIT binary patch delta 2850 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 delta 2789 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