package com.kodcu.service.convert.odf; import com.kodcu.component.HtmlPane; import com.kodcu.config.OdfConfigBean; import com.kodcu.controller.ApplicationController; import com.kodcu.engine.AsciidocConverterProvider; import com.kodcu.engine.AsciidocWebkitConverter; import com.kodcu.other.Constants; import com.kodcu.other.Current; import com.kodcu.other.ExtensionFilters; import com.kodcu.service.DirectoryService; import com.kodcu.service.ThreadService; import com.kodcu.service.convert.Traversable; import com.kodcu.service.ui.IndikatorService; import netscape.javascript.JSObject; import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement; import org.odftoolkit.odfdom.dom.element.draw.DrawImageElement; import org.odftoolkit.odfdom.pkg.OdfElement; import org.odftoolkit.odfdom.type.Color; import org.odftoolkit.odfdom.type.Length; import org.odftoolkit.simple.Component; import org.odftoolkit.simple.TextDocument; import org.odftoolkit.simple.draw.FrameRectangle; import org.odftoolkit.simple.draw.FrameStyleHandler; import org.odftoolkit.simple.draw.Image; import org.odftoolkit.simple.style.Border; import org.odftoolkit.simple.style.Font; import org.odftoolkit.simple.style.ParagraphProperties; import org.odftoolkit.simple.style.StyleTypeDefinitions.*; import org.odftoolkit.simple.table.Cell; import org.odftoolkit.simple.table.CellRange; import org.odftoolkit.simple.table.Column; import org.odftoolkit.simple.table.Table; import org.odftoolkit.simple.text.Paragraph; import org.odftoolkit.simple.text.ParagraphContainer; import org.odftoolkit.simple.text.list.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import java.nio.file.Path; import java.util.*; import java.util.List; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by Hakan on 4/13/2015. */ @Controller public class ODFConverter implements Traversable { private final Logger logger = LoggerFactory.getLogger(ODFConverter.class); private final ApplicationController controller; private final Current current; private final HtmlPane htmlPane; private final AsciidocWebkitConverter asciidocWebkitConverter; private final ThreadService threadService; private final IndikatorService indikatorService; private final DirectoryService directoryService; private final OdfConfigBean odfConfigBean; private TextDocument odtDocument; private List<JSObject> unstructuredDocument = new ArrayList<>(); private final Predicate<String> expectedElement = (name) -> Arrays.asList("paragraph", "image", "section", "listing", "colist", "table", "quote", "page_break", "olist", "ulist", "admonition", "thematic_break", "sidebar", "pass", "example", "literal", "verse", "open", "dlist", "video").stream().anyMatch(s -> s.equals(name)); private final Predicate<String> parentElement = (name) -> Arrays.asList("section", "quote", "admonition", "sidebar", "example", "verse", "open").stream().anyMatch(s -> s.equals(name)); private Path odtFilePath; private final AsciidocConverterProvider converterProvider; @Autowired public ODFConverter(final ApplicationController controller, final Current current, final HtmlPane htmlPane, AsciidocWebkitConverter asciidocWebkitConverter, final ThreadService threadService, final IndikatorService indikatorService, DirectoryService directoryService, OdfConfigBean odfConfigBean, AsciidocConverterProvider converterProvider) { this.controller = controller; this.current = current; this.htmlPane = htmlPane; this.asciidocWebkitConverter = asciidocWebkitConverter; this.threadService = threadService; this.indikatorService = indikatorService; this.directoryService = directoryService; this.odfConfigBean = odfConfigBean; this.converterProvider = converterProvider; } public void generateODFDocument(boolean askPath) { // threadService.runTaskLater(() -> { // after enabling runTaskLater, I receive "TypeError: 'undefined' is not a function (evaluating 'A.$backtrace()') " if the asciidoc file contains a math block final Path currentTabPath = current.currentPath().get(); final Path currentTabPathDir = currentTabPath.getParent(); odtFilePath = directoryService.getSaveOutputPath(ExtensionFilters.ODT, askPath); indikatorService.startProgressBar(); logger.debug("ODF conversion started"); try { this.openOdtDocument(); converterProvider.get(odfConfigBean).convertOdf(current.currentEditorValue()); this.saveOdtDocument(); } catch (Exception e) { logger.error("Problem occured while converting to ODF", e); } finally { unstructuredDocument.clear(); indikatorService.stopProgressBar(); logger.debug("ODF conversion ended"); } // }); } private void openOdtDocument() { // create an empty text doc to begin try { odtDocument = TextDocument.newTextDocument(); } catch (Exception e) { logger.error("Problem occured while creating new OdtTextDocument", e); } } private void saveOdtDocument() { try { odtDocument.save(odtFilePath.toString()); controller.addRemoveRecentList(odtFilePath); } catch (Exception e) { logger.error("Problem occured while saving OdtDocument", e); } finally { odtDocument.close(); } } public void buildDocument(String name, JSObject jObj) { // System.out.println(name); if (name.equals("document")) { List<AsciiElement> structuredDocument = this.createNewDocumentStructure(); this.buildODTDocument(Optional.ofNullable(structuredDocument), false); } else if (expectedElement.test(name)) { unstructuredDocument.add(jObj); } } private List<AsciiElement> createNewDocumentStructure() { List<AsciiElement> newTree = new ArrayList<>(); unstructuredDocument.forEach(item -> { String name = getSpecificProperty(item, "name", String.class); JSObject blocks = getSpecificProperty(item, "blocks", JSObject.class); Integer nOfBlocks = getSpecificProperty(blocks, "length", Integer.class); // System.out.println(nOfBlocks + " :" + name); AsciiElement element; if (parentElement.test(name)) { List<AsciiElement> subElements = this.getElementChildren(newTree, nOfBlocks); element = new AsciiElement(name, item, subElements); } else { element = new AsciiElement(name, item); } newTree.add(element); }); return newTree; } private List<AsciiElement> getElementChildren(List<AsciiElement> newTree, Integer nOfBlocks) { List<AsciiElement> subElements = new ArrayList<>(); int lastIndex = newTree.size() - 1; for (int repeat = 0; repeat < nOfBlocks; repeat++) { if (newTree.size() == 0) break; AsciiElement lastElement = newTree.get(lastIndex); subElements.add(lastElement); newTree.remove(lastIndex); lastIndex -= 1; } return subElements; } private void buildODTDocument(Optional<List<AsciiElement>> elements, boolean appendable) { elements.ifPresent(list -> { list.forEach(element -> { this.addComponent(element, Paragraph.newParagraph(odtDocument), appendable); }); }); } private void buildODTDocument(Optional<List<AsciiElement>> elements, Component component) { elements.ifPresent(list -> { list.forEach(element -> { this.addComponent(element, component, false); }); }); } private void addComponent(AsciiElement element, Component component, boolean appendable) { switch (element.getName()) { case "paragraph": addParagraph(element, component, appendable); break; case "listing": addListing(element, component); break; case "image": addImage(element, component); break; case "section": addSection(element); break; case "colist": case "olist": case "ulist": addList(element, component); break; case "dlist": addDList(element, component); break; case "quote": case "verse": addQuote(element, component); break; case "table": addTable(element); break; case "page_break": addPageBreak(component); break; case "admonition": addAdmonition(element); break; case "example": case "sidebar": case "open": addBlock(element); break; case "pass": addPass(element); break; case "literal": addLiteral(element, component); break; case "thematic_break": case "video": removeParagraph(component); break; } } private void addLiteral(AsciiElement element, Component component) { final ParagraphContainer paragraphContainer; if (component instanceof Cell) { paragraphContainer = (Cell) component; } else { paragraphContainer = odtDocument; } this.setTitle(element, FontStyle.ITALIC, HorizontalAlignmentType.LEFT, 12, new Color("#7a2518"), paragraphContainer); if (paragraphContainer instanceof Cell) { paragraphContainer.addParagraph(element.getContent()); } else { Table table = odtDocument.addTable(1, 1); Cell cell = table.getCellByPosition(0, 0); cell.setCellBackgroundColor(new Color("#f7f7f8")); Border border = new Border(Color.WHITE, 1.0, SupportedLinearMeasure.PT); cell.setBorders(CellBordersType.NONE, border); cell.setStringValue(element.getContent()); } } private void addPass(AsciiElement element) { odtDocument.addParagraph(element.getContent()); } private void addBlock(AsciiElement element) { final Table table = odtDocument.addTable(1, 1); Cell cell = table.getCellByPosition(0, 0); if (element.getName().equals("sidebar")) cell.setCellBackgroundColor(new Color("#f8f8f7")); HorizontalAlignmentType type; Border border = new Border(new Color("#e0e0dc"), 1.0, SupportedLinearMeasure.PT); if (element.getName().equals("open")) { type = HorizontalAlignmentType.JUSTIFY; border.setColor(Color.WHITE); cell.setBorders(CellBordersType.NONE, border); } else { type = HorizontalAlignmentType.CENTER; cell.setBorders(CellBordersType.ALL_FOUR, border); } this.setTitle(element, type, 14, new Color("#7a2518"), cell); this.buildODTDocument(element.getChildren(), cell); } private void removeParagraph(Component component) { if (component instanceof Paragraph) { Paragraph paragraph = (Paragraph) component; paragraph.remove(); } } private void addAdmonition(AsciiElement element) { this.setTitle(element, FontStyle.ITALIC, HorizontalAlignmentType.LEFT, 12, new Color("#7a2518"), odtDocument); Table table = odtDocument.addTable(1, 2); Cell rowOColumn0 = table.getCellByPosition(0, 0); String caption = getSpecificProperty(element.getjObj(), "caption", String.class); rowOColumn0.setDisplayText(caption.toUpperCase()); rowOColumn0.setHorizontalAlignment(HorizontalAlignmentType.CENTER); rowOColumn0.setVerticalAlignment(VerticalAlignmentType.MIDDLE); Cell rowOColumn1 = table.getCellByPosition(1, 0); if (element.getNOfBlocks() == 0) rowOColumn1.setStringValue(element.getContent()); else { this.buildODTDocument(element.getChildren(), rowOColumn1); } Column column = table.getColumnByIndex(0); column.setUseOptimalWidth(true); column.setWidth(32.0); } private void addPageBreak(Component component) { if (component instanceof Paragraph) { Paragraph paragraph = (Paragraph) component; odtDocument.addPageBreak(paragraph); } } private void addQuote(AsciiElement element, Component component) { Cell cell = null; final ParagraphContainer paragraphContainer; if (component instanceof Cell) { cell = (Cell) component; paragraphContainer = cell; } else { paragraphContainer = odtDocument; } this.setTitle(element, FontStyle.ITALIC, HorizontalAlignmentType.LEFT, 12, new Color("#7a2518"), paragraphContainer); if (element.getNOfBlocks() == 0) paragraphContainer.addParagraph(element.getContent()); else { if (Objects.nonNull(cell)) this.buildODTDocument(element.getChildren(), cell); else { this.buildODTDocument(element.getChildren(), true); } } this.addQuoteTypes(element, paragraphContainer); } private void addQuoteTypes(AsciiElement element, ParagraphContainer paragraphContainer) { Arrays.asList("attribution", "citetitle").forEach(type -> { String attr = getSpecificProperty(element.getAttr(), type, String.class); if (!attr.equals("") && !attr.equals("undefined")) { Paragraph parag = paragraphContainer.addParagraph(""); Font font = createFont(FontStyle.ITALIC, 12, Color.BLACK); parag.setFont(font); if (type.startsWith("attr")) parag.setTextContent("—".concat($(attr))); else parag.setTextContent($(attr)); } }); } private void addParagraph(AsciiElement element, Component component, boolean appendable) { final Paragraph paragraph; if (component instanceof Cell) { Cell cell = (Cell) component; paragraph = cell.addParagraph(element.getContent()); ParagraphProperties properties = paragraph.getStyleHandler().getParagraphPropertiesForRead(); properties.setMarginLeft(1.0); properties.setMarginRight(1.0); } else { if (appendable) { paragraph = (Paragraph) component; paragraph.appendTextContent(element.getContent()); } else { paragraph = odtDocument.addParagraph(element.getContent()); } } paragraph.setHorizontalAlignment(HorizontalAlignmentType.JUSTIFY); } private void addDList(AsciiElement element, Component component) { final ParagraphContainer paragraphContainer; final ListContainer listContainer; if (component instanceof Cell) { Cell cell = (Cell) component; paragraphContainer = cell; listContainer = cell; } else { paragraphContainer = odtDocument; listContainer = odtDocument; } int dListBlockLength = element.getItemsLength(); if (dListBlockLength > 0) { for (int index = 0; index < dListBlockLength; index++) { // traverse each item combining (dd + dt) JSObject items = element.getItemByIndex(index); this.addDListItems(items, paragraphContainer, listContainer); } } } // e.g. // CPU:: The brain of the computer. // Hard drive:: Permanent storage for operating system and/or user files. private void addDListItems(JSObject items, ParagraphContainer paragraphContainer, ListContainer listContainer) { int itemsLength = this.getSpecificProperty(items, "length", Integer.class); if (itemsLength > 0) { for (int i = 0; i < itemsLength; i++) { JSObject item = this.getSpecificProperty(items, i, JSObject.class); Object objItem = this.getSpecificProperty(item, 0, Object.class); if (objItem instanceof JSObject) { // for only title (text | dt) of the dlist JSObject jObjItem = this.castSpecificProperty(objItem, JSObject.class); String itemText = this.getSpecificProperty(jObjItem, "text", String.class); Font font = this.createFont(FontStyle.BOLD, 12, Color.BLACK); Paragraph paragraph = paragraphContainer.addParagraph($(itemText)); paragraph.setFont(font); } else { // find and construct dd elements JSObject itemBlocks = this.getSpecificProperty(item, "blocks", JSObject.class); int bLength = this.getSpecificProperty(itemBlocks, "length", Integer.class); if (bLength > 0) { itemBlocks = this.getSpecificProperty(itemBlocks, 0, JSObject.class); this.addDDListItems(itemBlocks, paragraphContainer, listContainer); } else { String text = this.getSpecificProperty(item, "text", String.class); paragraphContainer.addParagraph(" ".concat($(text))); } } } } } private void addDDListItems(JSObject itemBlocks, ParagraphContainer paragraphContainer, ListContainer listContainer) { itemBlocks = this.getSpecificProperty(itemBlocks, "blocks", JSObject.class); int blockLength = this.getSpecificProperty(itemBlocks, "length", Integer.class); if (blockLength > 0) { String context = this.getSpecificProperty(itemBlocks, "context", String.class); org.odftoolkit.simple.text.list.List list = listContainer.addList(); ListDecorator dec = this.findDecorator(context); list.setDecorator(dec); for (int inc = 0; inc < blockLength; inc++) { JSObject item = this.getSpecificProperty(itemBlocks, inc, JSObject.class); Object subBlocks = this.getSpecificProperty(item, "blocks", Object.class); if (subBlocks instanceof JSObject) { // only one dd which contain olist or ulist items String text = this.getSpecificProperty(item, "text", String.class); ListItem listItem = list.addItem($(text)); // look at whether there are sub list items or not in a dd element this.addSubList(listItem, (JSObject) subBlocks); } else { // if we go here, this means that we have a hybrid dlist including sub dlist(s) which has/have (dt + dd elements) this.addDListItems(item, paragraphContainer, listContainer); } } } } private void addList(AsciiElement element, Component component) { final ListContainer listContainer; final ParagraphContainer paragraphContainer; if (component instanceof Cell) { Cell cell = (Cell) component; listContainer = cell; paragraphContainer = cell; } else { listContainer = odtDocument; paragraphContainer = odtDocument; } this.setTitle(element, FontStyle.ITALIC, HorizontalAlignmentType.LEFT, 12, new Color("#7a2518"), paragraphContainer); this.addListItems(element, listContainer); } private void addListItems(AsciiElement element, ListContainer listContainer) { int len = element.getItemsLength(); if (len > 0) { org.odftoolkit.simple.text.list.List list = listContainer.addList(); ListDecorator dec = this.findDecorator(element.getName()); list.setDecorator(dec); for (int inc = 0; inc < len; inc++) { JSObject blocks = this.getSpecificProperty(element.getItemByIndex(inc), "blocks", JSObject.class); String text = this.getSpecificProperty(element.getItemByIndex(inc), "text", String.class); ListItem listItem = list.addItem($(text)); this.addSubList(listItem, blocks); } } } private ListDecorator findDecorator(String name) { ListDecorator dec; if (name.equals("olist")) { dec = new NumberDecorator(odtDocument); } else { dec = new BulletDecorator(odtDocument); } return dec; } private void addSubList(ListItem listItem, JSObject blocks) { int nOfBlocks = this.getSpecificProperty(blocks, "length", Integer.class); if (nOfBlocks > 0) { for (int counter = 0; counter < nOfBlocks; counter++) { JSObject subList = this.getSpecificProperty(blocks, counter, JSObject.class); JSObject subListItems = this.getSpecificProperty(subList, "blocks", JSObject.class); int itemsLength = this.getSpecificProperty(subListItems, "length", Integer.class); if (itemsLength > 0) { org.odftoolkit.simple.text.list.List list = listItem.addList(); for (int itemIndex = 0; itemIndex < itemsLength; itemIndex++) { JSObject subListItem = this.getSpecificProperty(subListItems, itemIndex, JSObject.class); String context = this.getSpecificProperty(subListItem, "context", String.class); String itemText = this.getSpecificProperty(subListItem, "text", String.class); ListDecorator decorator = this.findDecorator(context); list.setDecorator(decorator); ListItem listContainer = list.addItem($(itemText)); JSObject nestedSubListItemBlocks = this.getSpecificProperty(subListItem, "blocks", JSObject.class); this.addSubList(listContainer, nestedSubListItemBlocks); } } } } } private void addListing(AsciiElement element, Component component) { if (component instanceof Paragraph) { if (element.getTitle().equals("")) { Table table = odtDocument.addTable(1, 1); Cell cell = table.getCellByPosition(0, 0); cell.setStringValue(element.getContent()); } else { Table table = odtDocument.addTable(2, 1); Cell cell = table.getCellByPosition(0, 0); cell.setStringValue(element.getTitle()); cell.setHorizontalAlignment(HorizontalAlignmentType.CENTER); Cell cell2 = table.getCellByPosition(0, 1); cell2.setStringValue(element.getContent()); } } else if (component instanceof Cell) { Cell cell = (Cell) component; if (!element.getTitle().equals("")) cell.addParagraph(element.getTitle()); cell.addParagraph(element.getContent()); } } private void addSection(AsciiElement element) { this.removeFirstEmptyParagraphs(odtDocument); Font font = createFont(12, new Color("#ba3925")); switch (element.getLevel()) { case 1: font.setSize(18); break; case 2: font.setSize(17); break; case 3: font.setSize(16); break; case 4: font.setSize(15); break; case 5: font.setSize(14); break; case 6: font.setSize(13); break; } Paragraph paragraph = odtDocument.addParagraph(element.getTitle()); paragraph.setFont(font); paragraph.applyHeading(true, element.getLevel()); this.buildODTDocument(element.getChildren(), false); } private void removeFirstEmptyParagraphs(ParagraphContainer paragraphContainer) { // only if the initial doc containing less than 4 elems int paragraphLength = paragraphContainer.getParagraphContainerElement().getLength(); if (paragraphLength < 4) { Iterator<Paragraph> params = paragraphContainer.getParagraphIterator(); while (params.hasNext()) { this.removeParagraph(params.next()); } } } private void addImage(AsciiElement element, Component component) { String imageUrl = getSpecificProperty(element.getAttr(), "target", String.class); if (Constants.IMAGE_URL_MATCH.matcher(imageUrl).matches()) { this.removeParagraph(component); return; } Path currentTabPath = current.currentPath().get(); Path currentTabPathDir = currentTabPath.getParent(); Path path = currentTabPathDir.resolve(imageUrl); Paragraph para = odtDocument.addParagraph(""); para.setHorizontalAlignment(HorizontalAlignmentType.CENTER); Image image = Image.newImage(para, path.toUri()); FrameStyleHandler handler = image.getStyleHandler(); handler.setAchorType(AnchorType.AS_CHARACTER); this.setImageSize(image); this.setElementTitle(element, "Figure", odtDocument); } private void setImageSize(Image image) { DrawImageElement diElem = image.getOdfElement(); DrawFrameElement dfElem = (DrawFrameElement) diElem.getParentNode(); FrameRectangle rect = image.getRectangle(); if (rect.getWidth() > 14.0) { dfElem.setSvgWidthAttribute(Length.mapToUnit(String.valueOf(400) + "px", Length.Unit.CENTIMETER)); } if (rect.getHeight() > 14.0) { dfElem.setSvgHeightAttribute(Length.mapToUnit(String.valueOf(300) + "px", Length.Unit.CENTIMETER)); } } private void setElementTitle(AsciiElement element, String prefix, ParagraphContainer paragraphContainer) { String title = element.getTitle(); if (!title.equals("") && !title.equals("undefined")) { paragraphContainer.addParagraph(""); Font font = createFont(FontStyle.ITALIC, 12, new Color("#7a2518")); Paragraph titleParam = paragraphContainer.addParagraph(String.join(" : ", prefix, title)); titleParam.setHorizontalAlignment(HorizontalAlignmentType.CENTER); titleParam.setFont(font); } } private void addTable(AsciiElement element) { final Table table = odtDocument.addTable(element.getRowsLength(), element.getColumnsLength()); boolean headExist = false; for (String selection : Arrays.asList("head", "body", "foot")) { int rowSelection = element.getRowLengthBySelection(selection); int rowTable = 0; if (selection.equals("head")) { if (rowSelection == 1) headExist = true; } else if (selection.equals("body")) { if (rowSelection != 0 && headExist) { rowTable = 1; rowSelection += rowTable; } } else { if (rowSelection != 0) { rowTable = element.getRowsLength() - rowSelection; rowSelection = element.getRowsLength(); } } this.traverseCells(element, table, selection, rowSelection, rowTable); } this.setElementTitle(element, "Table", odtDocument); } private void traverseCells(AsciiElement element, Table table, String selection, int rowSelection, int rowTable) { for (int rowTableIndex = rowTable, rowElement = 0; rowTableIndex < rowSelection; rowTableIndex++, rowElement++) { int cellColumns = element.getNOfColumn(selection, rowElement); for (int column = 0; column < cellColumns; column++) { this.editCell(element, table, selection, rowTableIndex, rowElement, column); } } } private void editCell(AsciiElement element, Table table, String selection, int rowTableIndex, int rowElement, int column) { JSObject documentCell = element.getCell(selection, rowElement, column); Map<String, String> attrs = this.getCellAttributes(documentCell); String cellText = this.getSpecificProperty(documentCell, "text", String.class); Cell tableCell = this.getCell(table, rowTableIndex, column, attrs); this.setAlignmentTypes(attrs, tableCell); this.setSpecificFontStyle(selection, tableCell, attrs); this.setBorderRight(tableCell); tableCell.setStringValue($(cellText)); } private Cell getCell(Table table, int rowTableIndex, int column, Map<String, String> attrs) { Cell tableCell = table.getCellByPosition(column, rowTableIndex); OdfElement container = tableCell.getFrameContainerElement(); String localName = container.getOdfName().getLocalName(); if (localName.equals("table-cell")) { tableCell = this.setSpanAttributeOfCell(table, attrs, tableCell, container); } else if (localName.equals("covered-table-cell")) { tableCell = this.getCell(table, rowTableIndex, column + 1, attrs); } return tableCell; } private Cell setSpanAttributeOfCell(Table table, Map<String, String> attrs, Cell tableCell, OdfElement container) { int colSpan = Integer.valueOf(attrs.get("colspan")); int rowspan = Integer.valueOf(attrs.get("rowspan")); int row = tableCell.getRowIndex(); int column = tableCell.getColumnIndex(); int tableColumn = table.getColumnCount(); int tableRow = table.getRowCount(); boolean hasOfficeValueType = container.hasAttribute("office:value-type"); if (!hasOfficeValueType) { if (colSpan != 1 || rowspan != 1) { colSpan = this.getNewSpanValue(colSpan, column, tableColumn); rowspan = this.getNewSpanValue(rowspan, row, tableRow); // spanned attribute in action CellRange cellRange = table.getCellRangeByPosition(column, row, colSpan, rowspan); cellRange.merge(); } } else { int nextColumn = column + 1; if (nextColumn != tableColumn) tableCell = this.getCell(table, row, nextColumn, attrs); } return tableCell; } private int getNewSpanValue(int rowOrColSpan, int rowOrColumn, int tableRowOrColumn) { if (rowOrColSpan == tableRowOrColumn) rowOrColSpan -= 1; else if (rowOrColSpan == 1) rowOrColSpan = rowOrColumn; else if (rowOrColumn != rowOrColSpan) rowOrColSpan += rowOrColumn - 1; return rowOrColSpan; } private void setAlignmentTypes(Map<String, String> attrs, Cell tableCell) { HorizontalAlignmentType hAlign = HorizontalAlignmentType.enumValueOf(attrs.get("halign")); VerticalAlignmentType vAlign = VerticalAlignmentType.enumValueOf(attrs.get("valign")); tableCell.setHorizontalAlignment(hAlign); tableCell.setVerticalAlignment(vAlign); } private void setSpecificFontStyle(String selection, Cell tableCell, Map<String, String> attrs) { Font font = createFont(12, Color.BLACK); if (selection.equals("head")) { font.setFontStyle(FontStyle.BOLDITALIC); } else if (selection.equals("foot")) { font.setFontStyle(FontStyle.ITALIC); } else { if (attrs.containsKey("style")) { String style = attrs.get("style"); if (style.equals("strong")) font.setFontStyle(FontStyle.BOLD); else if (style.equals("emphasis")) font.setFontStyle(FontStyle.ITALIC); else if (style.equals("header")) font.setFontStyle(FontStyle.BOLDITALIC); } } tableCell.setFont(font); } private void setBorderRight(Cell tableCell) { Border borderRight = new Border(Color.BLACK, 1.0, SupportedLinearMeasure.PT); tableCell.setBorders(CellBordersType.RIGHT, borderRight); } private Map<String, String> getCellAttributes(JSObject cell) { Map<String, String> attrList = new LinkedHashMap<>(); String documentStr = cell.toString(); for (String regex : Arrays.asList("\"(\\w+)\"(?:=>)\"?(\\w+)\"?", "(colspan)(?:[\\:\\s]+)(\\d+)", "(rowspan)(?:[\\:\\s]+)(\\d+)")) { Pattern pattern = Pattern.compile(regex); Matcher mather = pattern.matcher(documentStr); while (mather.find()) { attrList.put(mather.group(1), mather.group(2)); } } return attrList; } private boolean setTitle(AsciiElement element, FontStyle fontStyle, HorizontalAlignmentType horizontalAlignmentType, int size, Color color, ParagraphContainer paragraphContainer) { boolean titled = false; String title = element.getTitle(); if (!title.equals("") && !title.equals("undefined")) { Font font = createFont(fontStyle, size, color); Paragraph paragraph = paragraphContainer.addParagraph(title); paragraph.setHorizontalAlignment(horizontalAlignmentType); paragraph.setFont(font); titled = true; } return titled; } private boolean setTitle(AsciiElement element, HorizontalAlignmentType horizontalAlignmentType, int size, Color color, ParagraphContainer paragraphContainer) { return setTitle(element, FontStyle.REGULAR, horizontalAlignmentType, size, color, paragraphContainer); } private <T> T getSpecificProperty(JSObject from, String propertyName, Class<T> returnType) { return returnType.cast(from.getMember(propertyName)); } private <T> T castSpecificProperty(Object from, Class<T> returnType) { return returnType.cast(from); } private <T> T getSpecificProperty(JSObject from, int index, Class<T> returnType) { return returnType.cast(from.getSlot(index)); } private Font createFont(int fontSize, Color color) { return createFont(FontStyle.REGULAR, fontSize, color); } private Font createFont(FontStyle style, int fontSize, Color color) { return new Font("Times New Roman", style, fontSize, color); } /** * Normalize the string content for the odf file * * @param content * @return normalized content */ private String $(String content) { content = content.replace(">", ">"); content = content.replace("<", "<"); content = content.replace("→", "→"); content = content.replace("’", "'"); content = content.replace("&", "&"); content = content.replace("↵", ""); content = content.replace(" — ", " --"); content = content.replace("…​", " …"); return content; } private class AsciiElement { private final String name; private final JSObject jObj; private final Optional<List<AsciiElement>> children; AsciiElement(final String name, final JSObject jObj) { this(name, jObj, null); } AsciiElement(final String name, final JSObject jObj, final List<AsciiElement> children) { this.name = name; this.jObj = jObj; if (Objects.nonNull(children)) Collections.reverse(children); this.children = Optional.ofNullable(children); } JSObject getjObj() { return jObj; } String getName() { return name; } Optional<List<AsciiElement>> getChildren() { return children; } int getLevel() { return (Integer) jObj.getMember("level"); } JSObject getBlocks() { return (JSObject) jObj.getMember("blocks"); } JSObject getBlockByIndex(int index) { return (JSObject) getBlocks().getSlot(index); } int getNOfBlocks() { return (Integer) getBlocks().getMember("length"); } String getTitle() { return $(jObj.getMember("title").toString()); } String getContent() { return $(jObj.getMember("content").toString()); } JSObject getAttr() { return (JSObject) jObj.getMember("attr"); } JSObject getItems() { return (JSObject) jObj.getMember("items"); } JSObject getItemByIndex(int index) { return (JSObject) ((JSObject) jObj.getMember("items")).getSlot(index); } int getItemsLength() { return (Integer) getItems().getMember("length"); } JSObject getRows() { return (JSObject) jObj.getMember("rows"); } JSObject getColumns() { return (JSObject) jObj.getMember("columns"); } int getColumnsLength() { return (Integer) getColumns().getMember("length"); } JSObject getTableBody() { return (JSObject) getRows().getMember("body"); } int getTableBodyLength() { return (Integer) getTableBody().getMember("length"); } JSObject getTableHead() { return (JSObject) getRows().getMember("head"); } int getTableHeadLength() { return (Integer) getTableHead().getMember("length"); } JSObject getTableFoot() { return (JSObject) getRows().getMember("foot"); } int getTableFootLength() { return (Integer) getTableFoot().getMember("length"); } int getRowsLength() { return getTableBodyLength() + getTableFootLength() + getTableHeadLength(); } JSObject getTableSectionByName(String name) { return (JSObject) getRows().getMember(name); } int getRowLengthBySelection(String name) { return (Integer) getTableSectionByName(name).getMember("length"); } int getNOfColumn(String selection, int row) { return (Integer) ((JSObject) getTableSectionByName(selection).getSlot(row)).getMember("length"); } JSObject getCell(String selection, int row, int column) { return (JSObject) ((JSObject) getTableSectionByName(selection).getSlot(row)).getSlot(column); } @Override public String toString() { return "AsciiElement{" + "children=" + children + ", name='" + name + '\'' + ", jObj=" + jObj + '}'; } } }