Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
ialbert 2022-12-20 13:18:59 +03:00
commit 94f878c0b4
3 changed files with 150 additions and 38 deletions

View file

@ -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<String, TextPositionOnRun> bodyElementForKey = new HashMap<>();
private final Pattern KEY_PATTERN = Pattern.compile("\\$\\{.*?}");
private final int MAX_POSITION = 128;
private List<KeyPositionInDoc> 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<String, String> 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<String> 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<String, String> valuesMap, List<String> notPresentInMap) {
Set<String> keysFromMap = valuesMap.keySet();
Set<String> keysFromFile = keyPositions.stream().map(keyPosition -> keyPosition.key).collect(Collectors.toSet());
List<String> intersection = keysFromMap.stream()
.filter(keysFromFile::contains)
.collect(Collectors.toList());
if (intersection.size() == keysFromFile.size()) return true;
List<String> notPresent = new LinkedList<>(keysFromFile);
notPresent.removeAll(intersection);
if (notPresentInMap != null) notPresentInMap.addAll(notPresent);
return false;
}
/**
* Вставка значений в элементы XWPFRun
*/
private void insertValues(Map<String, String> 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;
}
}
}

View file

@ -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<String, String> 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();
}
}