From 8eb7ed676989c15a4254559254e969a5c2e5b37b Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Tue, 27 Jan 2015 12:14:24 +0100 Subject: [PATCH] OO-1402: support <br/> and lists in the html to OpenXML converter --- .../util/openxml/HTMLToOpenXMLHandler.java | 30 ++++- .../core/util/openxml/OpenXMLDocument.java | 127 +++++++++++++++++- .../util/openxml/OpenXMLDocumentWriter.java | 43 +++++- .../util/openxml/_resources/numbering.xml | 2 + .../core/util/openxml/_resources/styles.xml | 8 ++ 5 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/olat/core/util/openxml/_resources/numbering.xml diff --git a/src/main/java/org/olat/core/util/openxml/HTMLToOpenXMLHandler.java b/src/main/java/org/olat/core/util/openxml/HTMLToOpenXMLHandler.java index e6a7d23eaeb..1f6b681af7e 100644 --- a/src/main/java/org/olat/core/util/openxml/HTMLToOpenXMLHandler.java +++ b/src/main/java/org/olat/core/util/openxml/HTMLToOpenXMLHandler.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.Deque; import java.util.List; +import org.olat.core.util.openxml.OpenXMLDocument.ListParagraph; import org.olat.core.util.openxml.OpenXMLDocument.Style; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -50,8 +51,9 @@ public class HTMLToOpenXMLHandler extends DefaultHandler { private List<Node> content = new ArrayList<Node>(); private Deque<StyleStatus> styleStack = new ArrayDeque<StyleStatus>(); - private Element currentParagraph; private Table currentTable; + private Element currentParagraph; + private ListParagraph currentListParagraph; public HTMLToOpenXMLHandler(OpenXMLDocument document, Element paragraph) { this.factory = document; @@ -76,6 +78,18 @@ public class HTMLToOpenXMLHandler extends DefaultHandler { return currentParagraph; } + private Element getCurrentListParagraph(boolean create) { + if(create || currentParagraph == null) { + //flush the text + if(textBuffer != null) { + flushText(); + addContent(currentParagraph); + } + currentParagraph = factory.createListParagraph(currentListParagraph); + } + return currentParagraph; + } + private void closeParagraph() { flushText(); currentParagraph = addContent(currentParagraph); @@ -229,6 +243,8 @@ public class HTMLToOpenXMLHandler extends DefaultHandler { styles = setTextPreferences(cssStyles); } styleStack.add(new StyleStatus(tag, styles)); + } else if("br".equals(tag)) { + closeParagraph(); } else if("em".equalsIgnoreCase(tag)) { flushText(); Style[] styles = setTextPreferences(Style.italic); @@ -249,6 +265,10 @@ public class HTMLToOpenXMLHandler extends DefaultHandler { int colspan = OpenXMLUtils.getSpanAttribute("colspan", attributes); int rowspan = OpenXMLUtils.getSpanAttribute("rowspan", attributes); currentTable.addCellEl(colspan, rowspan); + } else if("ul".equals(tag) || "ol".equals(tag)) { + currentListParagraph = factory.createListParagraph(); + } else if("li".equals(tag)) { + getCurrentListParagraph(true); } } @@ -282,6 +302,8 @@ public class HTMLToOpenXMLHandler extends DefaultHandler { if(currentTable != null) { content.add(currentTable.getTableEl()); } + currentTable = null; + currentParagraph = null; } else if("td".equals(tag) || "th".equals(tag)) { flushText(); currentParagraph = addContent(currentParagraph); @@ -292,6 +314,11 @@ public class HTMLToOpenXMLHandler extends DefaultHandler { textBuffer = null; latex = false; currentParagraph = null; + } else if("ul".equals(tag) || "ol".equals(tag)) { + closeParagraph(); + currentListParagraph = null; + } else if("li".equals(tag)) { + //do nothing } } @@ -452,6 +479,5 @@ public class HTMLToOpenXMLHandler extends DefaultHandler { public void unDone() { done = false; } - } } \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/openxml/OpenXMLDocument.java b/src/main/java/org/olat/core/util/openxml/OpenXMLDocument.java index f47ce6d4f8f..a21520ebfaa 100644 --- a/src/main/java/org/olat/core/util/openxml/OpenXMLDocument.java +++ b/src/main/java/org/olat/core/util/openxml/OpenXMLDocument.java @@ -80,11 +80,13 @@ public class OpenXMLDocument { private final OpenXMLStyles styles; private int currentId = 4; + private int currentNumberingId = 0; private String documentHeader; private Map<File, DocReference> fileToImagesMap = new HashMap<File, DocReference>(); - private List<Node> cursorStack = new ArrayList<Node>(); - private List<HeaderReference> headers = new ArrayList<HeaderReference>(); + private List<Node> cursorStack = new ArrayList<>(); + private List<ListParagraph> numbering = new ArrayList<>(); + private List<HeaderReference> headers = new ArrayList<>(); private VFSContainer mediaContainer; @@ -131,6 +133,10 @@ public class OpenXMLDocument { return headers; } + public Collection<ListParagraph> getNumbering() { + return numbering; + } + public Node getCursor() { return cursorStack.get(cursorStack.size() - 1); } @@ -639,6 +645,104 @@ public class OpenXMLDocument { return cellEl; } + public ListParagraph createListParagraph() { + int abstractNumberingId = currentNumberingId++; + int numberingId = currentNumberingId++; + ListParagraph lp = new ListParagraph(abstractNumberingId, numberingId); + numbering.add(lp); + return lp; + } + + /* +<w:p> + <w:pPr> + <w:pStyle w:val="ListParagraph"/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="1"/> + </w:numPr> + </w:pPr> + <w:r> + <w:t>One</w:t> + </w:r> +</w:p> + */ + public Element createListParagraph(ListParagraph def) { + Element paragraphEl = document.createElement("w:p"); + Element listEl = (Element)paragraphEl.appendChild(document.createElement("w:pPr")); + Element pStyleEl = (Element)listEl.appendChild(document.createElement("w:pStyle")); + pStyleEl.setAttribute("w:val", "ListParagraph"); + Element numberingEl = (Element)listEl.appendChild(document.createElement("w:numPr")); + Element ilvlEl = (Element)numberingEl.appendChild(document.createElement("w:ilvl")); + ilvlEl.setAttribute("w:val", "0"); + Element numIdEl = (Element)numberingEl.appendChild(document.createElement("w:numId")); + numIdEl.setAttribute("w:val", Integer.toString(def.getNumId())); + return paragraphEl; + } + + /* +<w:abstractNum w:abstractNumId="0"> + <w:lvl w:ilvl="0"> + <w:start w:val="1"/> + <w:numFmt w:val="bullet"/> + <w:lvlText w:val="o"/> + <w:lvlJc w:val="left"/> + <w:pPr> + <w:ind w:left="720" + w:hanging="360"/> + </w:pPr> + <w:rPr> + <w:rFonts w:ascii="Symbol" + w:hAnsi="Symbol" + w:hint="default"/> + </w:rPr> + </w:lvl> + */ + public Element createAbstractNumbering(ListParagraph def, Document doc) { + Element numEl = doc.createElement("w:abstractNum"); + numEl.setAttribute("w:abstractNumId", Integer.toString(def.getAbstractNumId())); + numEl.appendChild(createNumberingLevel(doc)); + return numEl; + } + + private Element createNumberingLevel(Document numberingDoc) { + Element levelEl = numberingDoc.createElement("w:lvl"); + levelEl.setAttribute("w:ilvl", "0"); + Element startEl = (Element)levelEl.appendChild(numberingDoc.createElement("w:start")); + startEl.setAttribute("w:val", "1"); + Element numFmtEl = (Element)levelEl.appendChild(numberingDoc.createElement("w:numFmt")); + numFmtEl.setAttribute("w:val", "bullet"); + Element lvlTextEl = (Element)levelEl.appendChild(numberingDoc.createElement("w:lvlText")); + lvlTextEl.setAttribute("w:val", Character.toString((char)0xB7));//bullet + Element lvlJcEl = (Element)levelEl.appendChild(numberingDoc.createElement("w:lvlJc")); + lvlJcEl.setAttribute("w:val", "left"); + //pPr + Element pPrEl = (Element)levelEl.appendChild(numberingDoc.createElement("w:pPr")); + Element indEl = (Element)pPrEl.appendChild(numberingDoc.createElement("w:ind")); + indEl.setAttribute("w:left", "720"); + indEl.setAttribute("w:hanging", "360"); + //rPr + Element rPrEl = (Element)levelEl.appendChild(numberingDoc.createElement("w:rPr")); + Element rFontsEl = (Element)rPrEl.appendChild(numberingDoc.createElement("w:rFonts")); + rFontsEl.setAttribute("w:ascii", "Symbol"); + rFontsEl.setAttribute("w:hAnsi", "Symbol"); + rFontsEl.setAttribute("w:hint", "default"); + return levelEl; + } + + /* + <w:num w:numId="1"> + <w:abstractNumId w:val="0"/> + </w:num> + */ + public Element createNumbering(ListParagraph def, Document numberingDoc) { + Element numEl = numberingDoc.createElement("w:num"); + numEl.setAttribute("w:numId", Integer.toString(def.getNumId())); + Element abstractNumEl = (Element)numEl.appendChild(numberingDoc.createElement("w:abstractNumId")); + abstractNumEl.setAttribute("w:val", Integer.toString(def.getAbstractNumId())); + return numEl; + } + /* <w:shd w:val="solid" w:color="E9EAF2" w:fill="auto" /> */ @@ -992,4 +1096,23 @@ public class OpenXMLDocument { return header; } } + + public static class ListParagraph { + + private final int abstractNumId; + private final int numId; + + public ListParagraph(int abstractNumId, int numId) { + this.abstractNumId = abstractNumId; + this.numId = numId; + } + + public int getAbstractNumId() { + return abstractNumId; + } + + public int getNumId() { + return numId; + } + } } diff --git a/src/main/java/org/olat/core/util/openxml/OpenXMLDocumentWriter.java b/src/main/java/org/olat/core/util/openxml/OpenXMLDocumentWriter.java index 2d05fbbc7b4..725680cb167 100644 --- a/src/main/java/org/olat/core/util/openxml/OpenXMLDocumentWriter.java +++ b/src/main/java/org/olat/core/util/openxml/OpenXMLDocumentWriter.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.Collection; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -31,9 +32,11 @@ import org.apache.commons.io.IOUtils; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.openxml.OpenXMLDocument.HeaderReference; +import org.olat.core.util.openxml.OpenXMLDocument.ListParagraph; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** @@ -101,6 +104,12 @@ public class OpenXMLDocumentWriter { //word/media appendMedias(out, document); + //word/numbering + ZipEntry numberingDocument = new ZipEntry("word/numbering.xml"); + out.putNextEntry(numberingDocument); + appendNumbering(out, document); + out.closeEntry(); + //word/document.xml ZipEntry wordDocument = new ZipEntry("word/document.xml"); out.putNextEntry(wordDocument); @@ -138,6 +147,28 @@ public class OpenXMLDocumentWriter { } } + private void appendNumbering(ZipOutputStream out, OpenXMLDocument document) { + try(InputStream in = OpenXMLDocumentWriter.class.getResourceAsStream("_resources/numbering.xml")) { + Collection<ListParagraph> numberingList = document.getNumbering(); + if(numberingList != null && numberingList.size() > 0) { + Document numberingDoc = OpenXMLUtils.createDocument(in); + NodeList numberingElList = numberingDoc.getElementsByTagName("w:numbering"); + Node numberingEl = numberingElList.item(0); + for(ListParagraph numberingItem : numberingList) { + Element abstractEl = document.createAbstractNumbering(numberingItem, numberingDoc); + numberingEl.appendChild(abstractEl); + Element numEl = document.createNumbering(numberingItem, numberingDoc); + numberingEl.appendChild(numEl); + } + OpenXMLUtils.writeTo(numberingDoc, out, false); + } else { + IOUtils.copy(in, out); + } + } catch (IOException e) { + log.error("", e); + } + } + private void appendPredefinedStyles(ZipOutputStream out, OpenXMLStyles styles) { try(InputStream in = OpenXMLDocumentWriter.class.getResourceAsStream("_resources/styles.xml")) { if(styles != null) { @@ -159,12 +190,13 @@ public class OpenXMLDocumentWriter { /* <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> - <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/> - <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/> - <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/> - <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/> + <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/> + <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/> + <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/> + <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/> <Relationship Id="rId2" Type="http://schemas.microsoft.com/office/2007/relationships/stylesWithEffects" Target="stylesWithEffects.xml"/> + <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml" /> </Relationships> */ protected void createDocumentRelationships(OutputStream out, OpenXMLDocument document) { @@ -175,6 +207,8 @@ public class OpenXMLDocumentWriter { addRelationship("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", "styles.xml", relationshipsEl, doc); + addRelationship("rId2", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering", + "numbering.xml", relationshipsEl, doc); if(document != null) { for(DocReference docRef:document.getImages()) { @@ -315,6 +349,7 @@ public class OpenXMLDocumentWriter { createContentTypesOverride("/docProps/core.xml", CT_CORE_PROPERTIES, typesEl, doc); createContentTypesOverride("/word/document.xml", CT_WORD_DOCUMENT, typesEl, doc); createContentTypesOverride("/word/styles.xml", CT_STYLES, typesEl, doc); + createContentTypesOverride("/word/numbering.xml", CT_NUMBERING, typesEl, doc); for(HeaderReference headerRef:document.getHeaders()) { createContentTypesOverride("/word/" + headerRef.getFilename(), CT_HEADER, typesEl, doc); diff --git a/src/main/java/org/olat/core/util/openxml/_resources/numbering.xml b/src/main/java/org/olat/core/util/openxml/_resources/numbering.xml new file mode 100644 index 00000000000..b29a0088f3b --- /dev/null +++ b/src/main/java/org/olat/core/util/openxml/_resources/numbering.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<w:numbering xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mo="http://schemas.microsoft.com/office/mac/office/2008/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 wp14"></w:numbering> \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/openxml/_resources/styles.xml b/src/main/java/org/olat/core/util/openxml/_resources/styles.xml index 00978bd4d6f..0fd8bf0801e 100644 --- a/src/main/java/org/olat/core/util/openxml/_resources/styles.xml +++ b/src/main/java/org/olat/core/util/openxml/_resources/styles.xml @@ -365,6 +365,14 @@ <w:semiHidden /> <w:unhideWhenUsed /> </w:style> + <w:style w:type="paragraph" w:styleId="ListParagraph"> + <w:name w:val="List Paragraph"/> + <w:basedOn w:val="Normal"/> + <w:pPr> + <w:ind w:left="720"/> + <w:contextualSpacing/> + </w:pPr> + </w:style> <w:style w:type="character" w:customStyle="1" w:styleId="ooHeading1Zeichen"> <w:name w:val="Ãœberschrift 1 Zeichen" /> <w:basedOn w:val="Absatzstandardschriftart" /> -- GitLab