argos/dmp-backend/web/src/main/java/eu/eudat/logic/utilities/documents/word/WordBuilder.java

407 lines
21 KiB
Java

package eu.eudat.logic.utilities.documents.word;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.logic.services.forms.VisibilityRuleService;
import eu.eudat.logic.utilities.documents.types.ParagraphStyle;
import eu.eudat.logic.utilities.interfaces.ApplierWithValue;
import eu.eudat.models.data.components.commons.datafield.CheckBoxData;
import eu.eudat.models.data.components.commons.datafield.ComboBoxData;
import eu.eudat.models.data.components.commons.datafield.WordListData;
import eu.eudat.models.data.user.components.datasetprofile.Field;
import eu.eudat.models.data.user.components.datasetprofile.FieldSet;
import eu.eudat.models.data.user.components.datasetprofile.Section;
import eu.eudat.models.data.user.composite.DatasetProfilePage;
import eu.eudat.models.data.user.composite.PagedDatasetProfile;
import org.apache.poi.xwpf.usermodel.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTAbstractNum;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLvl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STNumberFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.stream.Collectors;
public class WordBuilder {
private static final Logger logger = LoggerFactory.getLogger(WordBuilder.class);
private Map<ParagraphStyle, ApplierWithValue<XWPFDocument, String, XWPFParagraph>> options = new HashMap<>();
private CTAbstractNum cTAbstractNum;
private BigInteger numId;
private Integer indent;
public WordBuilder() {
this.cTAbstractNum = CTAbstractNum.Factory.newInstance();
this.cTAbstractNum.setAbstractNumId(BigInteger.valueOf(1));
this.indent = 0;
this.buildOptions();
}
private void buildOptions() {
this.options.put(ParagraphStyle.TEXT, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
XWPFRun run = paragraph.createRun();
if (item != null)
run.setText(" " + item);
run.setFontSize(11);
return paragraph;
});
this.options.put(ParagraphStyle.HTML, (mainDocumentPart, item) -> {
Document htmlDoc = Jsoup.parse(item.replaceAll("\n", "<br>"));
HtmlToWorldBuilder htmlToWorldBuilder = HtmlToWorldBuilder.convert(mainDocumentPart, htmlDoc, indent > 0 ? (indent/2.0F) * 0.8F : 0.8F);
return htmlToWorldBuilder.getParagraph();
});
this.options.put(ParagraphStyle.TITLE, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
paragraph.setStyle("Title");
paragraph.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = paragraph.createRun();
run.setText(item);
run.setBold(true);
run.setFontSize(14);
return paragraph;
});
this.options.put(ParagraphStyle.HEADER1, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
paragraph.setStyle("Heading1");
XWPFRun run = paragraph.createRun();
run.setText(item);
// run.setBold(true);
// run.setFontSize(12);
// run.setStyle("0");
return paragraph;
});
this.options.put(ParagraphStyle.HEADER2, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
paragraph.setStyle("Heading2");
XWPFRun run = paragraph.createRun();
run.setText(" " + item);
// run.setBold(true);
// run.setFontSize(12);
return paragraph;
});
this.options.put(ParagraphStyle.HEADER3, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
paragraph.setStyle("Heading3");
XWPFRun run = paragraph.createRun();
run.setText(" " + item);
// run.setBold(true);
// run.setFontSize(11);
return paragraph;
});
this.options.put(ParagraphStyle.HEADER4, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
paragraph.setStyle("Heading4");
XWPFRun run = paragraph.createRun();
run.setText(item);
return paragraph;
});
this.options.put(ParagraphStyle.HEADER5, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
paragraph.setStyle("Heading5");
XWPFRun run = paragraph.createRun();
run.setText(" " + item);
return paragraph;
});
this.options.put(ParagraphStyle.HEADER6, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
paragraph.setStyle("Heading6");
XWPFRun run = paragraph.createRun();
run.setText(" " + item);
return paragraph;
});
this.options.put(ParagraphStyle.FOOTER, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(item);
return paragraph;
});
this.options.put(ParagraphStyle.COMMENT, (mainDocumentPart, item) -> {
XWPFParagraph paragraph = mainDocumentPart.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(item);
run.setItalic(true);
return paragraph;
});
}
public XWPFDocument build(XWPFDocument document, PagedDatasetProfile pagedDatasetProfile, VisibilityRuleService visibilityRuleService) throws IOException {
createPages(pagedDatasetProfile.getPages(), document, true, visibilityRuleService);
XWPFNumbering numbering = document.createNumbering();
BigInteger tempNumId = BigInteger.ONE;
boolean found = false;
while (!found) {
Object o = numbering.getAbstractNum(tempNumId);
found = (o == null);
if (!found) tempNumId = tempNumId.add(BigInteger.ONE);
}
cTAbstractNum.setAbstractNumId(tempNumId);
XWPFAbstractNum abstractNum = new XWPFAbstractNum(cTAbstractNum);
BigInteger abstractNumID = numbering.addAbstractNum(abstractNum);
this.numId = numbering.addNum(abstractNumID);
createPages(pagedDatasetProfile.getPages(), document, false, visibilityRuleService);
return document;
}
private void createPages(List<DatasetProfilePage> datasetProfilePages, XWPFDocument mainDocumentPart, Boolean createListing, VisibilityRuleService visibilityRuleService) {
datasetProfilePages.forEach(item -> {
try {
createSections(item.getSections(), mainDocumentPart, ParagraphStyle.HEADER4, 0, createListing, visibilityRuleService, item.getOrdinal() + 1, null);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
});
}
private void createSections(List<Section> sections, XWPFDocument mainDocumentPart, ParagraphStyle style, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService, Integer page, String sectionString) {
if (createListing) this.addListing(mainDocumentPart, indent, false, true);
boolean hasValue = false;
for (Section section: sections) {
int paragraphPos = -1;
String tempSectionString = sectionString != null ? sectionString + "." + (section.getOrdinal() + 1) : "" + (section.getOrdinal() + 1);
if (visibilityRuleService.isElementVisible(section.getId())) {
if (!createListing) {
XWPFParagraph paragraph = addParagraphContent(page + "." + tempSectionString + " " + section.getTitle(), mainDocumentPart, style, numId);
CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl();
number.setVal(BigInteger.valueOf(indent));
paragraphPos = mainDocumentPart.getPosOfParagraph(paragraph);
}
createSections(section.getSections(), mainDocumentPart, ParagraphStyle.HEADER5, 1, createListing, visibilityRuleService, page, tempSectionString);
hasValue = createCompositeFields(section.getCompositeFields(), mainDocumentPart, 2, createListing, visibilityRuleService, page, tempSectionString);
if (!hasValue && paragraphPos > -1) {
mainDocumentPart.removeBodyElement(paragraphPos);
}
}
}
}
private Boolean createCompositeFields(List<FieldSet> compositeFields, XWPFDocument mainDocumentPart, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService, Integer page, String section) {
if (createListing) this.addListing(mainDocumentPart, indent, true, true);
boolean hasValue = false;
for (FieldSet compositeField: compositeFields) {
if (visibilityRuleService.isElementVisible(compositeField.getId()) && hasVisibleFields(compositeField, visibilityRuleService)) {
int paragraphPos = -1;
if (compositeField.getTitle() != null && !compositeField.getTitle().isEmpty() && !createListing) {
XWPFParagraph paragraph = addParagraphContent(page + "." + section + "." + (compositeField.getOrdinal() +1) + " " + compositeField.getTitle(), mainDocumentPart, ParagraphStyle.HEADER6, numId);
CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl();
number.setVal(BigInteger.valueOf(indent));
paragraphPos = mainDocumentPart.getPosOfParagraph(paragraph);
}
hasValue = createFields(compositeField.getFields(), mainDocumentPart, 3, createListing, visibilityRuleService);
if (compositeField.getMultiplicityItems() != null && !compositeField.getMultiplicityItems().isEmpty()) {
List<FieldSet> list = compositeField.getMultiplicityItems().stream().sorted(Comparator.comparingInt(FieldSet::getOrdinal)).collect(Collectors.toList());
for (FieldSet multiplicityFieldset : list) {
hasValue = createFields(multiplicityFieldset.getFields(), mainDocumentPart, 3, createListing, visibilityRuleService);
}
}
if (hasValue && compositeField.getHasCommentField() && compositeField.getCommentFieldValue() != null && !compositeField.getCommentFieldValue().isEmpty() && !createListing) {
XWPFParagraph paragraph = addParagraphContent("Comment: " + compositeField.getCommentFieldValue(), mainDocumentPart, ParagraphStyle.COMMENT, numId);
CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl();
number.setVal(BigInteger.valueOf(indent));
}
if (!hasValue && paragraphPos > -1) {
mainDocumentPart.removeBodyElement(paragraphPos);
}
}
}
return hasValue;
}
private Boolean createFields(List<Field> fields, XWPFDocument mainDocumentPart, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService) {
if (createListing) this.addListing(mainDocumentPart, indent, false, false);
boolean hasValue = false;
List<Field> tempFields = fields.stream().sorted(Comparator.comparingInt(Field::getOrdinal)).collect(Collectors.toList());
for (Field field: tempFields) {
if (visibilityRuleService.isElementVisible(field.getId())) {
if (!createListing) {
try {
if (field.getValue() != null && !field.getValue().toString().isEmpty()) {
this.indent = indent;
XWPFParagraph paragraph = addParagraphContent(this.formatter(field), mainDocumentPart, field.getViewStyle().getRenderStyle().equals("richTextarea") ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId);
if (paragraph != null) {
CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl();
number.setVal(BigInteger.valueOf(indent));
hasValue = true;
}
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
}
return hasValue;
}
public XWPFParagraph addParagraphContent(String text, XWPFDocument mainDocumentPart, ParagraphStyle style, BigInteger numId) {
if (text != null && !text.isEmpty()) {
XWPFParagraph paragraph = this.options.get(style).apply(mainDocumentPart, text);
if (paragraph != null) {
if (numId != null) {
paragraph.setNumID(numId);
}
return paragraph;
}
}
return null;
}
private void addListing(XWPFDocument document, int indent, Boolean question, Boolean hasIndication) {
CTLvl cTLvl = this.cTAbstractNum.addNewLvl();
String textLevel = "";
for (int i = 0; i <= indent; i++) {
textLevel += "%" + (i + 1) + ".";
}
if (question) {
cTLvl.addNewNumFmt().setVal(STNumberFormat.DECIMAL);
cTLvl.addNewLvlText().setVal("");
cTLvl.setIlvl(BigInteger.valueOf(indent));
} else if (!question && hasIndication) {
cTLvl.addNewNumFmt().setVal(STNumberFormat.DECIMAL);
cTLvl.addNewLvlText().setVal("");
cTLvl.setIlvl(BigInteger.valueOf(indent));
}
if (!question && !hasIndication) {
cTLvl.addNewNumFmt().setVal(STNumberFormat.NONE);
cTLvl.addNewLvlText().setVal("");
cTLvl.setIlvl(BigInteger.valueOf(indent));
}
}
private String formatter(Field field) throws IOException {
String comboboxType = null;
switch (field.getViewStyle().getRenderStyle()) {
case "researchers":
case "projects":
case "organizations":
case "externalDatasets":
case "dataRepositories":
case "registries":
case "services":
case "tags":
case "currency":
comboboxType = "autocomplete";
case "combobox": {
if (comboboxType == null) {
comboboxType = ((ComboBoxData) field.getData()).getType();
}
if (comboboxType.equals("autocomplete")) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
if (field.getValue() == null) return null;
List<Map<String, Object>> mapList = new ArrayList<>();
if (!field.getValue().equals("") && field.getValue().toString() != null) {
try {
mapList = Arrays.asList(mapper.readValue(field.getValue().toString(), HashMap[].class));
}catch (Exception e) {
// logger.warn(e.getMessage(), e);
// logger.info("Moving to fallback parsing");
Map <String, Object> map = new HashMap<>();
map.put("label", field.getValue().toString());
mapList.add(map);
}
}
StringBuilder sb = new StringBuilder();
int index = 0;
for (Map<String, Object> map: mapList) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() != null && (entry.getKey().equals("label") || entry.getKey().equals("description") || entry.getKey().equals("name"))) {
sb.append(entry.getValue().toString());
break;
}
}
if (index != mapList.size() - 1) sb.append(", ");
index++;
}
return sb.toString();
} else if (comboboxType.equals("wordlist")) {
WordListData wordListData = (WordListData) field.getData();
if (field.getValue() != null){
ComboBoxData<WordListData>.Option selectedOption = null;
if (!wordListData.getOptions().isEmpty()) {
for (ComboBoxData<WordListData>.Option option : wordListData.getOptions()) {
if (option.getValue().equals(field.getValue())) {
selectedOption = option;
}
}
}
return selectedOption != null ? selectedOption.getLabel() : field.getValue().toString();
}
return "";
}
}
case "booleanDecision":
if (field.getValue() != null && field.getValue().equals("true")) return "Yes";
if (field.getValue() != null && field.getValue().equals("false")) return "No";
return null;
case "radiobox":
return field.getValue() != null ? field.getValue().toString() : null;
case "checkBox":
CheckBoxData data = (CheckBoxData) field.getData();
if (field.getValue() == null || field.getValue().equals("false")) return null;
return data.getLabel();
case "datepicker":
case "datePicker":{
Instant instant;
try {
instant = Instant.parse((String) field.getValue());
} catch (DateTimeParseException ex) {
instant = (Instant) DateTimeFormatter.ofPattern("yyyy-MM-dd").parse((String)field.getValue());
}
return field.getValue() != null ? DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault()).format(instant) : "";
}
case "freetext":
case "textarea":
case "richTextarea":
return field.getValue() != null ? field.getValue().toString(): "";
case "datasetIdentifier":
case "validation":
if (field.getValue() != null && !field.getValue().toString().isEmpty()) {
Map<String, String> identifierData;
ObjectMapper mapper = new ObjectMapper();
try {
identifierData = mapper.readValue(field.getValue().toString(), HashMap.class);
} catch (Exception ex) {
// logger.warn(ex.getLocalizedMessage(), ex);
// logger.info("Reverting to custom parsing");
identifierData = customParse(field.getValue().toString());
}
return "id: " + identifierData.get("identifier") + ", Validation Type: " + identifierData.get("type");
}
return "";
}
return null;
}
private boolean hasVisibleFields(FieldSet compositeFields, VisibilityRuleService visibilityRuleService) {
return compositeFields.getFields().stream().anyMatch(field -> visibilityRuleService.isElementVisible(field.getId()));
}
private Map<String, String> customParse(String value) {
Map<String, String> result = new LinkedHashMap<>();
String parsedValue = value.replaceAll("[^a-zA-Z0-9\\s:=,]", "");
StringTokenizer commaTokens = new StringTokenizer(parsedValue, ", ");
String delimeter = parsedValue.contains("=") ? "=" : ":";
while (commaTokens.hasMoreTokens()) {
String token = commaTokens.nextToken();
StringTokenizer delimiterTokens = new StringTokenizer(token, delimeter);
result.put(delimiterTokens.nextToken(), delimiterTokens.nextToken());
}
return result;
}
}