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> 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", "
")); 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 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
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
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
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 fields, XWPFDocument mainDocumentPart, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService) { if (createListing) this.addListing(mainDocumentPart, indent, false, false); boolean hasValue = false; List 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> 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 map = new HashMap<>(); map.put("label", field.getValue().toString()); mapList.add(map); } } StringBuilder sb = new StringBuilder(); int index = 0; for (Map map: mapList) { for (Map.Entry 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.Option selectedOption = null; if (!wordListData.getOptions().isEmpty()) { for (ComboBoxData.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 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 customParse(String value) { Map 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; } }