Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
94f878c0b4
3 changed files with 150 additions and 38 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
Loading…
Add table
Reference in a new issue