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