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;
|
package ru.spcex.clearing.reports.services;
|
||||||
|
|
||||||
import org.apache.poi.xwpf.usermodel.*;
|
import org.apache.poi.xwpf.usermodel.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.FileInputStream;
|
import java.util.LinkedList;
|
||||||
import java.io.IOException;
|
import java.util.List;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сервис для работы с DOCX файлами
|
* Сервис для работы с DOCX файлами
|
||||||
|
|
@ -19,18 +20,61 @@ public class DOCXService {
|
||||||
/**
|
/**
|
||||||
* При инициализации сохраняет все имеющиеся ключи (ищет по маске keyPattern), найденные в текстовых элементах документа
|
* При инициализации сохраняет все имеющиеся ключи (ищет по маске keyPattern), найденные в текстовых элементах документа
|
||||||
*/
|
*/
|
||||||
private final Map<String, TextPositionOnRun> bodyElementForKey = new HashMap<>();
|
private List<KeyPositionInDoc> keyPositions = new LinkedList<>();
|
||||||
private final Pattern KEY_PATTERN = Pattern.compile("\\$\\{.*?}");
|
|
||||||
private final int MAX_POSITION = 128;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инициализация шаблона
|
* Паттерн поиска ключей в файле
|
||||||
*
|
|
||||||
* @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();
|
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);
|
XWPFDocument doc = new XWPFDocument(inputStream);
|
||||||
|
|
||||||
for (IBodyElement element : doc.getBodyElements()) {
|
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) {
|
private void collectKeysFromTable(XWPFTable srcTable) {
|
||||||
for (XWPFTableRow row : srcTable.getRows()) {
|
for (XWPFTableRow row : srcTable.getRows()) {
|
||||||
for (XWPFTableCell cell : row.getTableCells()) {
|
for (XWPFTableCell cell : row.getTableCells()) {
|
||||||
|
|
@ -59,17 +163,20 @@ public class DOCXService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сбор ключей из параграфа
|
||||||
|
*/
|
||||||
private void collectKeysFromParagraph(XWPFParagraph paragraph) {
|
private void collectKeysFromParagraph(XWPFParagraph paragraph) {
|
||||||
|
|
||||||
for (XWPFRun run : paragraph.getRuns()) {
|
for (XWPFRun run : paragraph.getRuns()) {
|
||||||
try {
|
try {
|
||||||
|
int MAX_POSITION = 128;
|
||||||
for (int currPos = 0; currPos < MAX_POSITION; currPos++) {
|
for (int currPos = 0; currPos < MAX_POSITION; currPos++) {
|
||||||
String currText = run.getText(currPos);
|
String currText = run.getText(currPos);
|
||||||
if (currText == null) break;
|
if (currText == null) break;
|
||||||
Matcher matcher = KEY_PATTERN.matcher(currText);
|
Matcher matcher = KEY_PATTERN.matcher(currText);
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String key = matcher.group();
|
String key = matcher.group();
|
||||||
bodyElementForKey.put(key, new TextPositionOnRun(run, currPos));
|
keyPositions.add(new KeyPositionInDoc(key, run, currPos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IndexOutOfBoundsException ignored) {
|
} catch (IndexOutOfBoundsException ignored) {
|
||||||
|
|
@ -78,13 +185,16 @@ public class DOCXService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TextPositionOnRun {
|
static class KeyPositionInDoc {
|
||||||
|
String key;
|
||||||
XWPFRun run;
|
XWPFRun run;
|
||||||
int pos;
|
int pos;
|
||||||
|
|
||||||
public TextPositionOnRun(XWPFRun run, int pos) {
|
public KeyPositionInDoc(String key, XWPFRun run, int pos) {
|
||||||
|
this.key = key;
|
||||||
this.run = run;
|
this.run = run;
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,33 @@
|
||||||
package ru.spcex.clearing.reports.services;
|
package ru.spcex.clearing.reports.services;
|
||||||
|
|
||||||
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
class DOCXServiceTest {
|
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
|
@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();
|
DOCXService service = new DOCXService();
|
||||||
service.init(new File(getClass().getClassLoader().getResource("REC.ACTV_TEST.docx").getPath()));
|
service.insertValues(new File(getClass().getClassLoader().getResource("REC.ACTV_TEST.docx").getPath()),
|
||||||
System.out.println("p");
|
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