adjusted legal info
diff --git a/jenkins.build.config.xml b/jenkins.build.config.xml
index 8c9f500..e93417d 100644
--- a/jenkins.build.config.xml
+++ b/jenkins.build.config.xml
@@ -16,6 +16,8 @@
 	<!-- DO NOT EDIT BELOW THIS LINE -->
         <jenkins.build.dependencies>
                 <jenkins.build.dependency>org.eclipse.osbp.blob</jenkins.build.dependency>
+                <jenkins.build.dependency>org.eclipse.osbp.bpm.api</jenkins.build.dependency>
+                <jenkins.build.dependency>org.eclipse.osbp.runtime</jenkins.build.dependency>
                 <jenkins.build.dependency>org.eclipse.osbp.ui.api</jenkins.build.dependency>
                 <jenkins.build.dependency>org.eclipse.osbp.xtext.datamart.common</jenkins.build.dependency>
                 <jenkins.build.dependency>org.eclipse.osbp.xtext.i18n</jenkins.build.dependency>
diff --git a/org.eclipse.osbp.xtext.table.common.feature/feature.xml b/org.eclipse.osbp.xtext.table.common.feature/feature.xml
index b0f2def..151c8c4 100644
--- a/org.eclipse.osbp.xtext.table.common.feature/feature.xml
+++ b/org.eclipse.osbp.xtext.table.common.feature/feature.xml
@@ -15,7 +15,7 @@
         label="%featureName"
         version="0.9.0.qualifier"
         provider-name="%providerName"
-		plugin="org.eclipse.osbp.xtext.table.common">
+		plugin="org.eclipse.osbp.license">
         
     <description>
         %description
diff --git a/org.eclipse.osbp.xtext.table.common/.classpath b/org.eclipse.osbp.xtext.table.common/.classpath
index 43b9862..442b7f3 100644
--- a/org.eclipse.osbp.xtext.table.common/.classpath
+++ b/org.eclipse.osbp.xtext.table.common/.classpath
@@ -2,6 +2,8 @@
 <classpath>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="src/"/>
+	<classpathentry exported="true" kind="lib" path="lib/easytable-0.4.0.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/easytable-0.4.0-sources.jar"/>
 	<classpathentry kind="output" path="target/classes"/>
 </classpath>
diff --git a/org.eclipse.osbp.xtext.table.common/META-INF/MANIFEST.MF b/org.eclipse.osbp.xtext.table.common/META-INF/MANIFEST.MF
index 0716335..d930d31 100644
--- a/org.eclipse.osbp.xtext.table.common/META-INF/MANIFEST.MF
+++ b/org.eclipse.osbp.xtext.table.common/META-INF/MANIFEST.MF
@@ -18,12 +18,23 @@
  org.eclipse.osbp.ui.api,
  org.eclipse.e4.core.contexts,
  org.apache.commons.lang;bundle-version="2.6.0",
- org.eclipse.osbp.blob;bundle-version="0.9.0"
+ org.eclipse.osbp.blob;bundle-version="0.9.0",
+ org.eclipse.osbp.bpm.api,
+ org.apache.pdfbox;bundle-version="2.0.6",
+ org.apache.pdfbox.fontbox;bundle-version="2.0.6",
+ org.apache.commons.lang3;bundle-version="3.4.0",
+ org.apache.poi;bundle-version="3.9.0",
+ org.eclipse.osbp.runtime.common
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-ActivationPolicy: lazy
 Import-Package: mondrian.olap,
  org.eclipse.osbp.xtext.datamart.common.olap;version="0.9.0",
  org.eclipse.osbp.xtext.i18n;version="0.9.0",
+ org.eclipse.xtext.xbase.lib;version="2.11.0",
  org.slf4j
-Export-Package: org.eclipse.osbp.xtext.table.common;version="0.9.0"
+Export-Package: org.eclipse.osbp.xtext.table.common;version="0.9.0",
+ org.eclipse.osbp.xtext.table.common.export;version="0.9.0"
 Bundle-Vendor: Eclipse OSBP
+Bundle-ClassPath: .,
+ lib/easytable-0.4.0.jar,
+ lib/easytable-0.4.0-sources.jar
diff --git a/org.eclipse.osbp.xtext.table.common/build.properties b/org.eclipse.osbp.xtext.table.common/build.properties
index ff9e7df..ac81233 100644
--- a/org.eclipse.osbp.xtext.table.common/build.properties
+++ b/org.eclipse.osbp.xtext.table.common/build.properties
@@ -1,12 +1,18 @@
 source.. = src/
 output.. = target/classes/
-bin.includes = about.properties,  about.mappings,  about.ini,  about.html,  META-INF/,\
+bin.includes = about.properties,\
+               about.mappings,\
+               about.ini,\
+               about.html,\
+               META-INF/,\
                .,\
                .settings/,\
                plugin.xml,\
                license.html,\
                LICENSE.txt,\
-               epl-2.0.html
+               epl-2.0.html,\
+               lib/,\
+               notice.html
 src.includes = about.properties,  about.mappings,  about.ini,  about.html,  license.html,\
                LICENSE.txt,\
                epl-2.0.html			   
diff --git a/org.eclipse.osbp.xtext.table.common/lib/easytable-0.4.0-sources.jar b/org.eclipse.osbp.xtext.table.common/lib/easytable-0.4.0-sources.jar
new file mode 100644
index 0000000..cc90b2e
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/lib/easytable-0.4.0-sources.jar
Binary files differ
diff --git a/org.eclipse.osbp.xtext.table.common/lib/easytable-0.4.0.jar b/org.eclipse.osbp.xtext.table.common/lib/easytable-0.4.0.jar
new file mode 100644
index 0000000..2069e48
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/lib/easytable-0.4.0.jar
Binary files differ
diff --git a/org.eclipse.osbp.xtext.table.common/pom.xml b/org.eclipse.osbp.xtext.table.common/pom.xml
index eed5191..def6129 100644
--- a/org.eclipse.osbp.xtext.table.common/pom.xml
+++ b/org.eclipse.osbp.xtext.table.common/pom.xml
@@ -2,47 +2,66 @@
 <!--#======================================================================= -->
 <!--# Copyright (c) 2011, 2016 - Loetz GmbH&Co.KG (69115 Heidelberg, Germany) -->
 <!--# All rights reserved. This program and the accompanying materials -->
-<!--# are made available under the terms of the Eclipse Public License 2.0  -->
+<!--# are made available under the terms of the Eclipse Public License 2.0 -->
 <!--# which accompanies this distribution, and is available at -->
-<!--# https://www.eclipse.org/legal/epl-2.0/    -->
-<!--#                                           -->
-<!--# SPDX-License-Identifier: EPL-2.0          -->
+<!--# https://www.eclipse.org/legal/epl-2.0/ -->
+<!--# -->
+<!--# SPDX-License-Identifier: EPL-2.0 -->
 <!--# -->
 <!--# Contributors: -->
-<!--#     Christophe Loetz (Loetz GmbH&Co.KG) - initial API and implementation -->
+<!--# Christophe Loetz (Loetz GmbH&Co.KG) - initial API and implementation -->
 <!--#======================================================================= -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.eclipse.osbp.xtext.table.common</groupId>
-        <artifactId>org.eclipse.osbp.xtext.table.common.aggregator</artifactId>
-        <version>0.9.0-SNAPSHOT</version>
-        <relativePath>..</relativePath>
-    </parent>
-    <artifactId>org.eclipse.osbp.xtext.table.common</artifactId>
-    <build>
-        <sourceDirectory>emf-gen</sourceDirectory>
-        <resources>
-            <resource>
-                <directory>src</directory>
-                <excludes>
-                    <exclude>**/*.java</exclude>
-                </excludes>
-            </resource>
-            <resource>
-                <directory>src-gen</directory>
-                <excludes>
-                    <exclude>**/*.java</exclude>
-                </excludes>
-            </resource>
-            <resource>
-                <directory>xtend-gen</directory>
-                <excludes>
-                    <exclude>**/*.java</exclude>
-                </excludes>
-            </resource>
-        </resources>
-    </build>
-    <packaging>eclipse-plugin</packaging>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.eclipse.osbp.xtext.table.common</groupId>
+		<artifactId>org.eclipse.osbp.xtext.table.common.aggregator</artifactId>
+		<version>0.9.0-SNAPSHOT</version>
+		<relativePath>..</relativePath>
+	</parent>
+	<artifactId>org.eclipse.osbp.xtext.table.common</artifactId>
+	<packaging>eclipse-plugin</packaging>
+
+	<dependencies>
+		<!-- https://github.com/vandeseer/easytable -->
+		<!-- CQ: https://dev.eclipse.org/ipzilla/show_bug.cgi?id=19223 -->
+		<dependency>
+			<groupId>com.github.vandeseer</groupId>
+			<artifactId>easytable</artifactId>
+			<version>0.4.0</version>
+		</dependency>
+		<dependency>
+			<groupId>com.github.vandeseer</groupId>
+			<artifactId>easytable</artifactId>
+			<version>0.4.0</version>
+			<classifier>sources</classifier>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<sourceDirectory>emf-gen</sourceDirectory>
+		<resources>
+			<resource>
+				<directory>src</directory>
+				<excludes>
+					<exclude>**/*.java</exclude>
+				</excludes>
+			</resource>
+			<resource>
+				<directory>src-gen</directory>
+				<excludes>
+					<exclude>**/*.java</exclude>
+				</excludes>
+			</resource>
+			<resource>
+				<directory>xtend-gen</directory>
+				<excludes>
+					<exclude>**/*.java</exclude>
+				</excludes>
+			</resource>
+		</resources>
+	</build>
+
 </project>
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/CellSetImage.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/CellSetImage.java
index b95da12..8de80e2 100644
--- a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/CellSetImage.java
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/CellSetImage.java
@@ -25,14 +25,16 @@
 public class CellSetImage extends FormLayout implements Comparable<CellSetImage> {
 	private static final long serialVersionUID = 4873534419157818152L;
 	private Object value = null;
+	private String resourceName;
 
 	public Object getValue() {
 		return value;
 	}
 
-	public CellSetImage(Object value, String caption, Resource resource, boolean hideLabel, String sizeString) {
+	public CellSetImage(Object value, String caption, Resource resource, String resourceName, boolean hideLabel, String sizeString) {
 		super();
 		this.value = value;
+		this.resourceName = resourceName;
 		setSpacing(false);
         setMargin(new MarginInfo(false, false, false, false));
         Image img = null;
@@ -62,4 +64,8 @@
 		}
 		return 0;
 	}
+
+	public String getResourceName() {
+		return resourceName;
+	}
 }
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/CellSetIndexedContainer.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/CellSetIndexedContainer.java
index d8908d0..ca03ee0 100644
--- a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/CellSetIndexedContainer.java
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/CellSetIndexedContainer.java
@@ -27,6 +27,8 @@
 
 import org.eclipse.e4.core.contexts.IEclipseContext;
 import org.eclipse.osbp.blob.component.BlobUploadComponent;
+import org.eclipse.osbp.bpm.api.BPMStatus;
+import org.eclipse.osbp.runtime.common.historized.UUIDHist;
 import org.eclipse.osbp.ui.api.customfields.IBlobService;
 import org.eclipse.osbp.ui.api.metadata.IDSLMetadataService;
 import org.eclipse.osbp.ui.api.themes.IThemeResourceService;
@@ -674,6 +676,12 @@
 		}
 		DerivedCell cell = getCell(itemId, columnName, null);
 		if (cell != null) {
+			if(columnName.contains("__id__")) {
+				String currentCellName = columnName.replace("__id__", "__validfrom__");
+				if(getCell(itemId, currentCellName, null) != null) {
+					return new UUIDHist((String)cell.getValue(), (long)getCell(itemId, currentCellName, null).getValue());
+				}
+			}
 			return cell.getValue();
 		}
 		return null;
@@ -712,6 +720,15 @@
 		return null;
 	}
 
+	public BPMStatus getTaskStatus(int itemId) {
+		DerivedCell cell = getCell(itemId, "status", null);
+		if (cell != null) {
+			Object value = cell.getValue();
+			return BPMStatus.values()[(int)value];
+		}
+		return BPMStatus.Obsolete;
+	}
+
 	public String getCellTitle(int itemId, String columnName) {
 		String fullTitle = "";
 		List<Integer> coordinate = new ArrayList<>(getCoordinateSystem());
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/PropertyLookup.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/PropertyLookup.java
index 4ee59dd..4e01adc 100644
--- a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/PropertyLookup.java
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/PropertyLookup.java
@@ -29,6 +29,7 @@
 import org.eclipse.osbp.ui.api.metadata.IDSLMetadataService;
 import org.eclipse.osbp.ui.api.themes.IThemeResourceService;
 import org.eclipse.osbp.ui.api.themes.IThemeResourceService.ThemeResourceType;
+import org.eclipse.xtext.xbase.lib.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -90,16 +91,16 @@
 	boolean hideLabel = false;
 	boolean collapseColumn = false;
 	String tooltipPattern = null;
-	private Map<Date, Resource> dateResourceMap = new TreeMap<>(new DateComparator());
+	private Map<Date, Pair<Resource,String>> dateResourceMap = new TreeMap<>(new DateComparator());
 	private Map<Date, String> dateStyleMap = new TreeMap<>(new DateComparator());
 	private Map<Date, String> dateTooltipMap = new TreeMap<>(new DateComparator());
-	private Map<Double, Resource> doubleResourceMap = new TreeMap<>(new DoubleComparator());
+	private Map<Double, Pair<Resource,String>> doubleResourceMap = new TreeMap<>(new DoubleComparator());
 	private Map<Double, String> doubleStyleMap = new TreeMap<>(new DoubleComparator());
 	private Map<Double, String> doubleTooltipMap = new TreeMap<>(new DoubleComparator());
-	private Map<Integer, Resource> intResourceMap = new TreeMap<>(new IntegerComparator());
+	private Map<Integer, Pair<Resource,String>> intResourceMap = new TreeMap<>(new IntegerComparator());
 	private Map<Integer, String> intStyleMap = new TreeMap<>(new IntegerComparator());
 	private Map<Integer, String> intTooltipMap = new TreeMap<>(new IntegerComparator());
-	private Map<String, Resource> stringResourceMap = new HashMap<>();
+	private Map<String, Pair<Resource,String>> stringResourceMap = new HashMap<>();
 	private Map<String, String> stringStyleMap = new HashMap<>();
 	private Map<String, String> stringTooltipMap = new HashMap<>();
 	private Locale locale;
@@ -119,7 +120,6 @@
 	private Resource columnIcon = null;
 	private int resolutionId = 0;
 	private IBlobService blobService;
-	@SuppressWarnings("rawtypes")
 	private Class<?> enumClass;
 
 	public PropertyLookup(IThemeResourceService themeResourceService, IDSLMetadataService dslMetadataService,
@@ -197,13 +197,13 @@
 			break;
 		case STRING:
 			item = (String) value;
-			LOGGER.debug("getTooltip >" + item + "<");
+			LOGGER.debug("getTooltip >{}<", item);
 			break;
 		default:
 			break;
 		}
 		if (tooltipPattern != null) {
-			LOGGER.debug("getTooltip pattern >" + tooltipPattern + "< result:" + tooltip);
+			LOGGER.debug("getTooltip pattern >{}< result:{}", tooltipPattern, tooltip);
 			tooltip = String.format(tooltipPattern, item);
 		} else if (value != null) {
 			tooltip = item;
@@ -234,7 +234,6 @@
 		return isEnum;
 	}
 
-	@SuppressWarnings("rawtypes")
 	public PropertyLookup setEnum(boolean isEnum, Class<?> enumClass) {
 		this.isEnum = isEnum;
 		this.enumClass = enumClass;
@@ -272,7 +271,7 @@
 	 */
 	public PropertyLookup addResourceInterval(Date until, String resourceName) {
 		Resource resource = themeResourceService.getThemeResource(resourceName, ThemeResourceType.ICON);
-		dateResourceMap.put(until, resource);
+		dateResourceMap.put(until, new Pair(resource, resourceName));
 		isImage = true;
 		defaultType = DATE;
 		return this;
@@ -310,7 +309,7 @@
 
 	public PropertyLookup addResourceLookup(String value, String resourceName) {
 		Resource resource = themeResourceService.getThemeResource(resourceName, ThemeResourceType.ICON);
-		stringResourceMap.put(value, resource);
+		stringResourceMap.put(value, new Pair(resource, resourceName));
 		isImage = true;
 		defaultType = STRING;
 		return this;
@@ -348,7 +347,7 @@
 
 	public PropertyLookup addResourceInterval(int until, String resourceName) {
 		Resource resource = themeResourceService.getThemeResource(resourceName, ThemeResourceType.ICON);
-		intResourceMap.put(until, resource);
+		intResourceMap.put(until, new Pair(resource, resourceName));
 		isImage = true;
 		defaultType = INTEGER;
 		return this;
@@ -386,7 +385,7 @@
 
 	public PropertyLookup addResourceInterval(double until, String resourceName) {
 		Resource resource = themeResourceService.getThemeResource(resourceName, ThemeResourceType.ICON);
-		doubleResourceMap.put(until, resource);
+		doubleResourceMap.put(until, new Pair(resource, resourceName));
 		isImage = true;
 		defaultType = NUMBER;
 		return this;
@@ -460,13 +459,13 @@
 				if ((resourceDiscreteValues && ((Date) value).equals(key))
 						|| (!resourceDiscreteValues && ((Date) value).before(key))) {
 					return new CellSetImage(value, dateConv.convertToPresentation((Date) value, String.class, locale),
-							dateResourceMap.get(key), hideLabel, resizeString);
+							dateResourceMap.get(key).getKey(), dateResourceMap.get(key).getValue(), hideLabel, resizeString);
 				}
 			}
 			// if it should be an image and no match was found, return an empty
 			// image to avoid complaints
 			if (isImage) {
-				return new CellSetImage(value, dateConv.convertToPresentation((Date) value, String.class, locale), null,
+				return new CellSetImage(value, dateConv.convertToPresentation((Date) value, String.class, locale), null, null,
 						hideLabel, resizeString);
 			}
 			break;
@@ -476,7 +475,7 @@
 				if ((resourceDiscreteValues && doubleValue == key) || (!resourceDiscreteValues && doubleValue <= key)) {
 					return new CellSetImage(doubleValue,
 							numberConv.convertToPresentation(doubleValue, String.class, locale),
-							doubleResourceMap.get(key), hideLabel, resizeString);
+							doubleResourceMap.get(key).getKey(), doubleResourceMap.get(key).getValue(), hideLabel, resizeString);
 				}
 			}
 			if (imagePath != null) {
@@ -486,14 +485,14 @@
 				}
 				Resource res = themeResourceService.getThemeResource(path, ThemeResourceType.ICON);
 				return new CellSetImage(doubleValue,
-						numberConv.convertToPresentation(doubleValue, String.class, locale), res, hideLabel,
+						numberConv.convertToPresentation(doubleValue, String.class, locale), res, path, hideLabel,
 						resizeString);
 			}
 			// if it should be an image and no match was found, return an empty
 			// image to avoid complaints
 			if (isImage) {
 				return new CellSetImage(doubleValue,
-						numberConv.convertToPresentation(doubleValue, String.class, locale), null, hideLabel,
+						numberConv.convertToPresentation(doubleValue, String.class, locale), null, null, hideLabel,
 						resizeString);
 			}
 			break;
@@ -502,7 +501,7 @@
 			for (Integer key : intResourceMap.keySet()) {
 				if ((resourceDiscreteValues && intValue == key) || (!resourceDiscreteValues && intValue <= key)) {
 					return new CellSetImage(intValue, intConv.convertToPresentation(intValue, String.class, locale),
-							intResourceMap.get(key), hideLabel, resizeString);
+							intResourceMap.get(key).getKey(), intResourceMap.get(key).getValue(), hideLabel, resizeString);
 				}
 			}
 			if (imagePath != null) {
@@ -511,13 +510,13 @@
 					path = path.replace(imageParameterPattern, String.format(imageParameterFormat, intValue));
 				}
 				Resource res = themeResourceService.getThemeResource(path, ThemeResourceType.ICON);
-				return new CellSetImage(intValue, intConv.convertToPresentation(intValue, String.class, locale), res,
+				return new CellSetImage(intValue, intConv.convertToPresentation(intValue, String.class, locale), res, path,
 						hideLabel, resizeString);
 			}
 			// if it should be an image and no match was found, return an empty
 			// image to avoid complaints
 			if (isImage) {
-				return new CellSetImage(intValue, intConv.convertToPresentation(intValue, String.class, locale), null,
+				return new CellSetImage(intValue, intConv.convertToPresentation(intValue, String.class, locale), null, null,
 						hideLabel, resizeString);
 			}
 			if (isEnum) {
@@ -531,7 +530,7 @@
 				if ((resourceDiscreteValues && longValue == (long) key)
 						|| (!resourceDiscreteValues && longValue <= (long) key)) {
 					return new CellSetImage(longValue, longConv.convertToPresentation(longValue, String.class, locale),
-							intResourceMap.get(key), hideLabel, resizeString);
+							intResourceMap.get(key).getKey(), intResourceMap.get(key).getValue(), hideLabel, resizeString);
 				}
 			}
 			if (imagePath != null) {
@@ -541,14 +540,14 @@
 							String.format(imageParameterFormat, longValue.intValue()));
 				}
 				Resource res = themeResourceService.getThemeResource(path, ThemeResourceType.ICON);
-				return new CellSetImage(longValue, longConv.convertToPresentation(longValue, String.class, locale), res,
+				return new CellSetImage(longValue, longConv.convertToPresentation(longValue, String.class, locale), res, path,
 						hideLabel, resizeString);
 			}
 			// if it should be an image and no match was found, return an empty
 			// image to avoid complaints
 			if (isImage) {
 				return new CellSetImage(longValue, longConv.convertToPresentation(longValue, String.class, locale),
-						null, hideLabel, resizeString);
+						null, null, hideLabel, resizeString);
 			}
 			break;
 		case BIGDECIMAL:
@@ -559,7 +558,7 @@
 						|| (!resourceDiscreteValues && bigValue.compareTo(bdKey) <= 0)) {
 					return new CellSetImage(bigValue,
 							bigDecimalConv.convertToPresentation(bigValue, String.class, locale),
-							doubleResourceMap.get(key), hideLabel, resizeString);
+							doubleResourceMap.get(key).getKey(), doubleResourceMap.get(key).getValue(), hideLabel, resizeString);
 				}
 			}
 			if (imagePath != null) {
@@ -569,19 +568,19 @@
 				}
 				Resource res = themeResourceService.getThemeResource(path, ThemeResourceType.ICON);
 				return new CellSetImage(bigValue, bigDecimalConv.convertToPresentation(bigValue, String.class, locale),
-						res, hideLabel, resizeString);
+						res, path, hideLabel, resizeString);
 			}
 			// if it should be an image and no match was found, return an empty
 			// image to avoid complaints
 			if (isImage) {
 				return new CellSetImage(bigValue, bigDecimalConv.convertToPresentation(bigValue, String.class, locale),
-						null, hideLabel, resizeString);
+						null, null, hideLabel, resizeString);
 			}
 			break;
 		case STRING:
 			for (String key : stringResourceMap.keySet()) {
 				if (key.equals(value)) {
-					return new CellSetImage(value, (String) value, stringResourceMap.get(key), hideLabel, resizeString);
+					return new CellSetImage(value, (String) value, stringResourceMap.get(key).getKey(), stringResourceMap.get(key).getValue(), hideLabel, resizeString);
 				}
 			}
 			if (imagePath != null) {
@@ -590,7 +589,7 @@
 					path = path.replace(imageParameterPattern, String.format(imageParameterFormat, (String) value));
 				}
 				Resource res = themeResourceService.getThemeResource(path, ThemeResourceType.ICON);
-				return new CellSetImage(value, (String) value, res, hideLabel, resizeString);
+				return new CellSetImage(value, (String) value, res, path,  hideLabel, resizeString);
 			}
 			if (isBlob) {
 				return new BlobComponent(blobService, (String) value, resolutionId);
@@ -598,7 +597,7 @@
 			// if it should be an image and no match was found, return an empty
 			// image to avoid complaints
 			if (isImage) {
-				return new CellSetImage(value, (String) value, null, hideLabel, resizeString);
+				return new CellSetImage(value, (String) value, null, null, hideLabel, resizeString);
 			}
 			break;
 		default:
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CsvExport.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CsvExport.java
new file mode 100644
index 0000000..ee9b9f9
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CsvExport.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.logging.Logger;
+
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.eclipse.osbp.ui.api.themes.IThemeResourceService;
+import org.tepi.filtertable.FilterTable;
+
+public class CsvExport extends ExcelExport {
+    private static final long serialVersionUID = 935966816321924835L;
+    private static final Logger LOGGER = Logger.getLogger(CsvExport.class.getName());
+
+    public CsvExport(final FilterTable table, IThemeResourceService themeResourceService) {
+        super(table, themeResourceService);
+    }
+
+    public CsvExport(final FilterTable table, IThemeResourceService themeResourceService, final String sheetName) {
+        super(table, themeResourceService, sheetName);
+    }
+
+    public CsvExport(final FilterTable table, IThemeResourceService themeResourceService, final String sheetName, final String reportTitle) {
+        super(table, themeResourceService, sheetName, reportTitle);
+    }
+
+    public CsvExport(final FilterTable table, IThemeResourceService themeResourceService, final String sheetName, final String reportTitle,
+            final String exportFileName) {
+        super(table, themeResourceService, sheetName, reportTitle, exportFileName);
+    }
+
+    public CsvExport(final FilterTable table, IThemeResourceService themeResourceService, final String sheetName, final String reportTitle,
+            final String exportFileName, final boolean hasTotalsRow) {
+        super(table, themeResourceService, sheetName, reportTitle, exportFileName, hasTotalsRow);
+    }
+    
+    public CsvExport(final TableHolder tableHolder) {
+        super(tableHolder);
+    }
+
+    public CsvExport(final TableHolder tableHolder, final String sheetName) {
+        super(tableHolder, sheetName);
+    }
+
+    public CsvExport(final TableHolder tableHolder, final String sheetName, final String reportTitle) {
+        super(tableHolder, sheetName, reportTitle);
+    }
+
+    public CsvExport(final TableHolder tableHolder, final String sheetName, final String reportTitle,
+            final String exportFileName) {
+        super(tableHolder, sheetName, reportTitle, exportFileName);
+    }
+
+    public CsvExport(final TableHolder tableHolder, final String sheetName, final String reportTitle,
+            final String exportFileName, final boolean hasTotalsRow) {
+        super(tableHolder, sheetName, reportTitle, exportFileName, hasTotalsRow);
+    }
+
+    @Override
+    /**
+     * Convert Excel to CSV and send to user. 
+     * 
+     */
+    public boolean sendConverted() {
+        File tempXlsFile;
+        File tempCsvFile;
+        try {
+            tempXlsFile = File.createTempFile("tmp", ".xls");
+            final FileOutputStream fileOut = new FileOutputStream(tempXlsFile);
+            workbook.write(fileOut);
+            final FileInputStream fis = new FileInputStream(tempXlsFile);
+            final POIFSFileSystem fs = new POIFSFileSystem(fis);
+            tempCsvFile = File.createTempFile("tmp", ".csv");
+            final PrintStream p =
+                    new PrintStream(new BufferedOutputStream(
+                            new FileOutputStream(tempCsvFile, true)));
+
+            final XLS2CSVmra xls2csv = new XLS2CSVmra(fs, p, -1);
+            xls2csv.process();
+            p.close();
+            if (null == mimeType) {
+                setMimeType(CSV_MIME_TYPE);
+            }
+            return super.sendConvertedFileToUser(getTableHolder().getUI(), tempCsvFile, getReportTitle()+".csv");
+        } catch (final IOException e) {
+            LOGGER.warning("Converting to CSV failed with IOException " + e);
+            return false;
+        }
+    }
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CustomTableExportableColumnGenerator.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CustomTableExportableColumnGenerator.java
new file mode 100644
index 0000000..e6ddad8
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CustomTableExportableColumnGenerator.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.data.Property;
+import com.vaadin.ui.CustomTable.ColumnGenerator;
+
+public interface CustomTableExportableColumnGenerator extends ColumnGenerator {
+
+    Property getGeneratedProperty(Object itemId, Object columnId);
+    // the type of the generated property
+    Class<?> getType();
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CustomTableHolder.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CustomTableHolder.java
new file mode 100644
index 0000000..cca90ec
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/CustomTableHolder.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Property;
+import com.vaadin.ui.CustomTable;
+import com.vaadin.ui.CustomTable.Align;
+import com.vaadin.ui.CustomTable.ColumnGenerator;
+import com.vaadin.ui.UI;
+import org.apache.poi.ss.usermodel.CellStyle;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author thomas
+ */
+public class CustomTableHolder implements TableHolder {
+
+    /**
+     * Whether the Container is a HierarchicalContainer or an extension thereof.
+     */
+    private boolean hierarchical;
+
+    private CustomTable table;
+    /**
+     * The Property ids of the Items in the Table.
+     */
+    private LinkedList<Object> propIds;
+
+    public CustomTableHolder(CustomTable table) {
+        this.table = table;
+        this.propIds = new LinkedList<Object>(Arrays.asList(table.getVisibleColumns()));
+        // fixed issue pointed out by Mark Lillywhite in the forum and smorygo....@gmail.com on the issues page.
+        // Was comparing to HierarchicalContainer, should have been comparing to Container.Hierarchical.
+        if (Container.Hierarchical.class.isAssignableFrom(table.getContainerDataSource().getClass())) {
+            setHierarchical(true);
+        } else {
+            setHierarchical(false);
+        }
+    }
+
+    @Override
+    public List<Object> getPropIds() {
+        return propIds;
+    }
+
+    @Override
+    public boolean isHierarchical() {
+        return hierarchical;
+    }
+
+    @Override
+    public final void setHierarchical(boolean hierarchical) {
+        this.hierarchical = hierarchical;
+    }
+
+    @Override
+    public Short getCellAlignment(Object propId) {
+        final Align vaadinAlignment = table.getColumnAlignment(propId);
+        return vaadinAlignmentToCellAlignment(vaadinAlignment);
+    }
+
+    private Short vaadinAlignmentToCellAlignment(final Align vaadinAlignment) {
+        if (Align.LEFT.equals(vaadinAlignment)) {
+            return CellStyle.ALIGN_LEFT;
+        } else if (Align.RIGHT.equals(vaadinAlignment)) {
+            return CellStyle.ALIGN_RIGHT;
+        } else {
+            return CellStyle.ALIGN_CENTER;
+        }
+    }
+
+    @Override
+    public boolean isGeneratedColumn(final Object propId) throws IllegalArgumentException {
+        return table.getColumnGenerator(propId) != null;
+    }
+
+    @Override
+    public Property getPropertyForGeneratedColumn(final Object propId, final Object rootItemId) throws
+            IllegalArgumentException {
+        Property prop;
+        final ColumnGenerator tcg = table.getColumnGenerator(propId);
+        if (tcg instanceof CustomTableExportableColumnGenerator) {
+            prop = ((CustomTableExportableColumnGenerator) tcg).getGeneratedProperty(rootItemId, propId);
+        } else {
+            prop = null;
+        }
+        return prop;
+    }
+
+    @Override
+    public Class<?> getPropertyTypeForGeneratedColumn(final Object propId) throws IllegalArgumentException {
+        Class<?> classType;
+        final ColumnGenerator tcg = table.getColumnGenerator(propId);
+        if (tcg instanceof CustomTableExportableColumnGenerator) {
+            classType = ((CustomTableExportableColumnGenerator) tcg).getType();
+        } else {
+            classType = String.class;
+        }
+        return classType;
+    }
+
+    @Override
+    public boolean isExportableFormattedProperty() {
+        return table instanceof ExportableFormattedProperty;
+    }
+
+    @Override
+    public boolean isColumnCollapsed(Object propertyId) {
+        return table.isColumnCollapsed(propertyId);
+    }
+
+    @Override
+    public UI getUI() {
+        return table.getUI();
+    }
+
+    @Override
+    public String getColumnHeader(Object propertyId) {
+        return table.getColumnHeader(propertyId);
+    }
+
+    @Override
+    public Container getContainerDataSource() {
+        return table.getContainerDataSource();
+    }
+
+    @Override
+    public String getFormattedPropertyValue(Object rowId, Object colId, Property property) {
+        return ((ExportableFormattedProperty) table).getFormattedPropertyValue(rowId, colId, property);
+    }
+}
\ No newline at end of file
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/DefaultTableHolder.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/DefaultTableHolder.java
new file mode 100644
index 0000000..180d412
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/DefaultTableHolder.java
@@ -0,0 +1,205 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Property;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.UI;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.tepi.filtertable.FilterTable;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author thomas
+ */
+public class DefaultTableHolder implements TableHolder {
+
+    protected short defaultAlignment = CellStyle.ALIGN_LEFT;
+
+    /**
+     * Whether the Container is a HierarchicalContainer or an extension thereof.
+     */
+    private boolean hierarchical;
+
+    private FilterTable heldTable;
+    private Grid heldGrid;
+    private Container container;
+    /**
+     * The Property ids of the Items in the FilterTable.
+     */
+    private LinkedList<Object> propIds;
+
+    public DefaultTableHolder(FilterTable table) {
+        this(table.getContainerDataSource());
+        this.heldTable = table;
+        // The order and visibility of the columns are determined by the FilterTable
+        this.propIds = new LinkedList<Object>(Arrays.asList(table.getVisibleColumns()));
+    }
+
+    public DefaultTableHolder(Grid grid) {
+        this(grid.getContainerDataSource());
+        this.heldGrid = grid;
+
+        List<Grid.Column> columns = grid.getColumns();
+        this.propIds = new LinkedList<Object>();
+        for (Grid.Column c: columns) {
+            propIds.add(c.getPropertyId());
+        }
+    }
+
+    public DefaultTableHolder(Container container) {
+        this.container = container;
+        // fixed issue pointed out by Mark Lillywhite in the forum and smorygo....@gmail.com on the issues page.
+        // Was comparing to HierarchicalContainer, should have been comparing to Container.Hierarchical.
+        if (Container.Hierarchical.class.isAssignableFrom(this.container.getClass())) {
+            setHierarchical(true);
+        } else {
+            setHierarchical(false);
+        }
+        // The order and visibility of the columns are determined by the Container unless later
+        // superseded by the FilterTable or the Grid.
+        this.propIds = new LinkedList<Object>(container.getContainerPropertyIds());
+        this.heldTable = null;
+        this.heldGrid = null;
+    }
+
+    @Override
+    public List<Object> getPropIds() {
+        return propIds;
+    }
+
+    @Override
+    public boolean isHierarchical() {
+        return hierarchical;
+    }
+
+    @Override
+    final public void setHierarchical(boolean hierarchical) {
+        this.hierarchical = hierarchical;
+    }
+
+    @Override
+    public Short getCellAlignment(Object propId) {
+        if (null == heldTable) {
+            return defaultAlignment;
+        }
+        final FilterTable.Align vaadinAlignment = heldTable.getColumnAlignment(propId);
+        return vaadinAlignmentToCellAlignment(vaadinAlignment);
+    }
+
+    private Short vaadinAlignmentToCellAlignment(final FilterTable.Align vaadinAlignment) {
+        if (FilterTable.Align.LEFT.equals(vaadinAlignment)) {
+            return CellStyle.ALIGN_LEFT;
+        } else if (FilterTable.Align.RIGHT.equals(vaadinAlignment)) {
+            return CellStyle.ALIGN_RIGHT;
+        } else {
+            return CellStyle.ALIGN_CENTER;
+        }
+    }
+
+    @Override
+    public boolean isGeneratedColumn(final Object propId) throws IllegalArgumentException {
+        if (null == heldTable) {
+            return false;
+        }
+        return heldTable.getColumnGenerator(propId) != null;
+    }
+
+    @Override
+    public Property getPropertyForGeneratedColumn(final Object propId, final Object rootItemId) throws IllegalArgumentException {
+        Property prop = null;
+        if (null != heldTable) {
+            final FilterTable.ColumnGenerator tcg = heldTable.getColumnGenerator(propId);
+            if (tcg instanceof ExportableColumnGenerator) {
+                prop = ((ExportableColumnGenerator) tcg).getGeneratedProperty(rootItemId, propId);
+            }
+        }
+        return prop;
+    }
+
+    @Override
+    public Class<?> getPropertyTypeForGeneratedColumn(final Object propId) throws IllegalArgumentException {
+        Class<?> classType = String.class;
+        if (null != heldTable) {
+            final FilterTable.ColumnGenerator tcg = heldTable.getColumnGenerator(propId);
+            if (tcg instanceof ExportableColumnGenerator) {
+                classType = ((ExportableColumnGenerator) tcg).getType();
+            }
+        }
+        return classType;
+    }
+
+    @Override
+    public boolean isExportableFormattedProperty() {
+        if (null == heldTable) {
+            return false;
+        }
+        return heldTable instanceof ExportableFormattedProperty;
+    }
+
+    @Override
+    public boolean isColumnCollapsed(Object propertyId) {
+        if (null == heldTable) {
+            return false;
+        }
+        return heldTable.isColumnCollapsed(propertyId);
+    }
+
+    @Override
+    public UI getUI() {
+        if (null != heldTable) {
+            return heldTable.getUI();
+        }
+        if (null != heldGrid) {
+            return heldGrid.getUI();
+        }
+        return UI.getCurrent();
+    }
+
+    @Override
+    public String getColumnHeader(Object propertyId) {
+        if (null == heldTable) {
+            if (null != heldGrid) {
+                Grid.Column c = heldGrid.getColumn(propertyId);
+                return c.getHeaderCaption();
+            } else {
+                return propertyId.toString();
+            }
+        }
+        return heldTable.getColumnHeader(propertyId).replace("&nbsp;", "\n");
+    }
+
+    @Override
+    public Container getContainerDataSource() {
+        return this.container;
+    }
+
+    @Override
+    public String getFormattedPropertyValue(Object rowId, Object colId, Property property) {
+        if (null == heldTable) {
+            return "";
+        }
+        return ((ExportableFormattedProperty) heldTable).getFormattedPropertyValue(rowId, colId, property);
+    }
+}
\ No newline at end of file
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/DeletingFileInputStream.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/DeletingFileInputStream.java
new file mode 100644
index 0000000..3e796bd
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/DeletingFileInputStream.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * This input stream deletes the given file when the InputStream is closed; intended to be used with
+ * temporary files.
+ * 
+ * Code obtained from: http://vaadin.com/forum/-/message_boards/view_message/159583
+ * 
+ */
+class DeletingFileInputStream extends FileInputStream implements Serializable {
+
+    /** The file. */
+    protected File file = null;
+
+    /**
+     * Instantiates a new deleting file input stream.
+     * 
+     * @param file
+     *            the file
+     * @throws FileNotFoundException
+     *             the file not found exception
+     */
+    public DeletingFileInputStream(final File file) throws FileNotFoundException {
+        super(file);
+        this.file = file;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.FileInputStream#close()
+     */
+    @Override
+    public void close() throws IOException {
+        super.close();
+        file.delete();
+    }
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExcelExport.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExcelExport.java
new file mode 100644
index 0000000..1867901
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExcelExport.java
@@ -0,0 +1,1386 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.ObjectProperty;
+
+import org.apache.poi.hssf.usermodel.HSSFFont;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.ClientAnchor;
+import org.apache.poi.ss.usermodel.CreationHelper;
+import org.apache.poi.ss.usermodel.DataFormat;
+import org.apache.poi.ss.usermodel.Drawing;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Picture;
+import org.apache.poi.ss.usermodel.PrintSetup;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellUtil;
+import org.apache.poi.ss.util.RegionUtil;
+import org.apache.poi.util.IOUtils;
+import org.eclipse.osbp.ui.api.themes.IThemeResourceService;
+import org.eclipse.osbp.ui.api.themes.IThemeResourceService.ThemeResourceType;
+import org.eclipse.osbp.xtext.table.common.CellSetImage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tepi.filtertable.FilterTable;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * The Class ExcelExport. Implementation of TableExport to export Vaadin Tables to Excel .xls files.
+ *
+ * @author jnash
+ * @version $Revision: 1.2 $
+ */
+public class ExcelExport extends TableExport {
+
+    /**
+     * The Constant serialVersionUID.
+     */
+    private static final long serialVersionUID = -8404407996727936497L;
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExcelExport.class);
+
+    /**
+     * The name of the sheet in the workbook the table contents will be written to.
+     */
+    protected String sheetName;
+
+    /**
+     * The title of the "report" of the table contents.
+     */
+    protected String reportTitle;
+
+    /**
+     * The filename of the workbook that will be sent to the user.
+     */
+    protected String exportFileName;
+
+    /**
+     * Flag indicating whether we will add a totals row to the FilterTable. A totals row in the FilterTable is
+     * typically implemented as a footer and therefore is not part of the data source.
+     */
+    protected boolean displayTotals;
+
+    /**
+     * Flag indicating whether the first column should be treated as row headers. They will then be
+     * formatted either like the column headers or a special row headers CellStyle can be specified.
+     */
+    protected boolean rowHeaders = false;
+
+    /**
+     * Flag indicating whether we should use table.formatPropertyValue() as the cell value instead
+     * of the property value using the specified data formats.
+     */
+    protected boolean useTableFormatPropertyValue = false;
+
+    /**
+     * The workbook that contains the sheet containing the report with the table contents.
+     */
+    protected final transient Workbook workbook;
+
+    /**
+     * The Sheet object that will contain the table contents report.
+     */
+    protected transient Sheet sheet;
+    protected transient Sheet hierarchicalTotalsSheet = null;
+
+    /**
+     * The POI cell creation helper.
+     */
+    protected transient CreationHelper createHelper;
+    protected transient DataFormat dataFormat;
+	protected transient Drawing drawing;
+
+    /**
+     * Various styles that are used in report generation. These can be set by the user if the
+     * default style is not desired to be used.
+     */
+    protected transient CellStyle dateCellStyle;
+    protected transient CellStyle doubleCellStyle;
+    protected transient CellStyle integerCellStyle;
+    protected transient CellStyle totalsDoubleCellStyle;
+    protected transient CellStyle totalsIntegerCellStyle;
+    protected transient CellStyle columnHeaderCellStyle;
+    protected transient CellStyle titleCellStyle;
+    protected Short dateDataFormat;
+    protected Short doubleDataFormat;
+    protected Short integerDataFormat;
+    protected transient Map<Short, CellStyle> dataFormatCellStylesMap = new HashMap<>();
+
+    /**
+     * The default row header style is null and, if row headers are specified with
+     * setRowHeaders(true), then the column headers style is used. setRowHeaderStyle() allows the
+     * user to specify a different row header style.
+     */
+    protected transient CellStyle rowHeaderCellStyle = null;
+
+    /**
+     * The totals row.
+     */
+    protected transient Row titleRow;
+    protected transient Row headerRow;
+    protected transient Row totalsRow;
+    protected transient Row hierarchicalTotalsRow;
+    // This let's the user specify the data format of the property in case the formatting of the property
+    // will not be properly identified by the class of the property. In this case, the specified format is
+    // used.  However, all other cell stylings will be those of the
+    protected transient Map<Object, String> propertyExcelFormatMap = new HashMap<>();
+
+    /**
+     * At minimum, we need a FilterTable to export. Everything else has default settings.
+     *
+     * @param table the table
+     */
+    public ExcelExport(final FilterTable table, IThemeResourceService themeResourceService) {
+        this(table, themeResourceService, null);
+    }
+
+    /**
+     * Instantiates a new TableExport class.
+     *
+     * @param table     the table
+     * @param sheetName the sheet name
+     */
+    public ExcelExport(final FilterTable table, IThemeResourceService themeResourceService, final String sheetName) {
+        this(table, themeResourceService, sheetName, null);
+    }
+
+    /**
+     * Instantiates a new TableExport class.
+     *
+     * @param table       the table
+     * @param sheetName   the sheet name
+     * @param reportTitle the report title
+     */
+    public ExcelExport(final FilterTable table, IThemeResourceService themeResourceService, final String sheetName, final String reportTitle) {
+        this(table, themeResourceService, sheetName, reportTitle, null);
+    }
+
+    /**
+     * Instantiates a new TableExport class.
+     *
+     * @param table          the table
+     * @param sheetName      the sheet name
+     * @param reportTitle    the report title
+     * @param exportFileName the export file name
+     */
+    public ExcelExport(final FilterTable table, IThemeResourceService themeResourceService, final String sheetName, final String reportTitle,
+                       final String exportFileName) {
+        this(table, themeResourceService, sheetName, reportTitle, exportFileName, true);
+    }
+
+    /**
+     * Instantiates a new TableExport class. This is the final constructor that all other
+     * constructors end up calling. If the other constructors were called then they pass in the
+     * default parameters.
+     *
+     * @param table          the table
+     * @param sheetName      the sheet name
+     * @param reportTitle    the report title
+     * @param exportFileName the export file name
+     * @param hasTotalsRow   flag indicating whether we should create a totals row
+     */
+    public ExcelExport(final FilterTable table, IThemeResourceService themeResourceService, final String sheetName, final String reportTitle,
+                       final String exportFileName, final boolean hasTotalsRow) {
+        this(table, themeResourceService, new HSSFWorkbook(), sheetName, reportTitle, exportFileName, hasTotalsRow);
+    }
+
+    public ExcelExport(final FilterTable table, IThemeResourceService themeResourceService, final Workbook wkbk, final String shtName, final String rptTitle,
+                       final String xptFileName, final boolean hasTotalsRow) {
+        super(table, themeResourceService);
+        workbook = wkbk;
+        init(shtName, rptTitle, xptFileName, hasTotalsRow);
+        format();
+    }
+
+    /**
+     * At minimum, we need a FilterTable to export. Everything else has default settings.
+     *
+     * @param tableHolder the tableHolder
+     */
+    public ExcelExport(final TableHolder tableHolder) {
+        this(tableHolder, null);
+    }
+
+    /**
+     * Instantiates a new TableExport class.
+     *
+     * @param tableHolder the tableHolder
+     * @param sheetName   the sheet name
+     */
+    public ExcelExport(final TableHolder tableHolder, final String sheetName) {
+        this(tableHolder, sheetName, null);
+    }
+
+    /**
+     * Instantiates a new TableExport class.
+     *
+     * @param tableHolder the tableHolder
+     * @param sheetName   the sheet name
+     * @param reportTitle the report title
+     */
+    public ExcelExport(final TableHolder tableHolder, final String sheetName, final String reportTitle) {
+        this(tableHolder, sheetName, reportTitle, null);
+    }
+
+    /**
+     * Instantiates a new TableExport class.
+     *
+     * @param tableHolder    the tableHolder
+     * @param sheetName      the sheet name
+     * @param reportTitle    the report title
+     * @param exportFileName the export file name
+     */
+    public ExcelExport(final TableHolder tableHolder, final String sheetName, final String reportTitle,
+                       final String exportFileName) {
+        this(tableHolder, sheetName, reportTitle, exportFileName, true);
+    }
+
+    /**
+     * Instantiates a new TableExport class. This is the final constructor that all other
+     * constructors end up calling. If the other constructors were called then they pass in the
+     * default parameters.
+     *
+     * @param tableHolder    the tableHolder
+     * @param sheetName      the sheet name
+     * @param reportTitle    the report title
+     * @param exportFileName the export file name
+     * @param hasTotalsRow   flag indicating whether we should create a totals row
+     */
+    public ExcelExport(final TableHolder tableHolder, final String sheetName, final String reportTitle,
+                       final String exportFileName, final boolean hasTotalsRow) {
+        this(tableHolder, new HSSFWorkbook(), sheetName, reportTitle, exportFileName, hasTotalsRow);
+    }
+
+    public ExcelExport(final TableHolder tableHolder, final Workbook wkbk, final String shtName,
+                       final String rptTitle, final String xptFileName, final boolean hasTotalsRow) {
+        super(tableHolder);
+        workbook = wkbk;
+        init(shtName, rptTitle, xptFileName, hasTotalsRow);
+    }
+
+    private void init(final String shtName, final String rptTitle, final String xptFileName,
+                      final boolean hasTotalsRow) {
+        if ((null == shtName) || ("".equals(shtName))) {
+            sheetName = "FilterTable Export";
+        } else {
+            sheetName = shtName;
+        }
+        if (null == rptTitle) {
+            reportTitle = "";
+        } else {
+            reportTitle = rptTitle;
+        }
+        if ((null == xptFileName) || ("".equals(xptFileName))) {
+            exportFileName = "FilterTable-Export.xls";
+        } else {
+            exportFileName = xptFileName;
+        }
+        displayTotals = hasTotalsRow;
+
+        sheet = workbook.createSheet(sheetName);
+        createHelper = workbook.getCreationHelper();
+        dataFormat = workbook.createDataFormat();
+        drawing = sheet.createDrawingPatriarch();
+
+        dateDataFormat = defaultDateDataFormat();
+        doubleDataFormat = defaultDoubleDataFormat();
+        integerDataFormat = defaultIntegerDataFormat();
+
+        doubleCellStyle = defaultDataCellStyle(workbook);
+        doubleCellStyle.setDataFormat(doubleDataFormat);
+        dataFormatCellStylesMap.put(doubleDataFormat, doubleCellStyle);
+
+        integerCellStyle = defaultDataCellStyle(workbook);
+        integerCellStyle.setDataFormat(integerDataFormat);
+        dataFormatCellStylesMap.put(integerDataFormat, integerCellStyle);
+
+        dateCellStyle = defaultDataCellStyle(workbook);
+        dateCellStyle.setDataFormat(dateDataFormat);
+        dataFormatCellStylesMap.put(dateDataFormat, dateCellStyle);
+
+        totalsDoubleCellStyle = defaultTotalsDoubleCellStyle(workbook);
+        totalsIntegerCellStyle = defaultTotalsIntegerCellStyle(workbook);
+        columnHeaderCellStyle = defaultHeaderCellStyle(workbook);
+        titleCellStyle = defaultTitleCellStyle(workbook);
+    }
+
+    /*
+     * Set a new table to be exported in another workbook tab / sheet.
+     */
+    public void setNextTable(final FilterTable table, final String sheetName) {
+        setTable(table);
+        sheet = workbook.createSheet(sheetName);
+    }
+
+    public void setNextTableHolder(final TableHolder tableHolder, final String sheetName) {
+        setTableHolder(tableHolder);
+        sheet = workbook.createSheet(sheetName);
+    }
+
+    /*
+     * This will exclude columns from the export that are not visible due to them being collapsed.
+     * This should be called before convertTable() is called.
+     */
+    public void excludeCollapsedColumns() {
+        final Iterator<Object> iterator = getPropIds().iterator();
+        while (iterator.hasNext()) {
+            final Object propId = iterator.next();
+            if (getTableHolder().isColumnCollapsed(propId)) {
+                iterator.remove();
+            }
+        }
+    }
+
+    /**
+     * Creates the workbook containing the exported table data, without exporting it to the user.
+     */
+    @Override
+    public void convertTable() {
+        final int startRow;
+        // initial setup
+        initialSheetSetup();
+
+        // add title row
+        startRow = addTitleRow();
+        int row = startRow;
+
+        // add header row
+        addHeaderRow(row);
+        row++;
+
+        // add data rows
+        if (isHierarchical()) {
+            row = addHierarchicalDataRows(sheet, row);
+        } else {
+            row = addDataRows(sheet, row);
+        }
+
+        // add totals row
+        if (displayTotals) {
+            addTotalsRow(row, startRow);
+        }
+
+        // final sheet format before export
+        finalSheetFormat();
+    }
+
+    /**
+     * Export the workbook to the end-user.
+     * <p/>
+     * Code obtained from: http://vaadin.com/forum/-/message_boards/view_message/159583
+     *
+     * @return true, if successful
+     */
+    @Override
+    public boolean sendConverted() {
+        File tempFile = null;
+        FileOutputStream fileOut = null;
+        try {
+            tempFile = File.createTempFile("tmp", ".xls");
+            fileOut = new FileOutputStream(tempFile);
+            workbook.write(fileOut);
+            if (null == mimeType) {
+                setMimeType(EXCEL_MIME_TYPE);
+            }
+            return super.sendConvertedFileToUser(getTableHolder().getUI(), tempFile, getReportTitle()+".xls");
+        } catch (final IOException e) {
+            LOGGER.error("Converting to XLS failed with IOException {}", e);
+            return false;
+        } finally {
+        	if(tempFile != null) {
+        		tempFile.deleteOnExit();
+        	}
+            try {
+            	if(fileOut != null) {
+            		fileOut.close();
+            	}
+            } catch (final IOException e) {
+            	// nothing to do
+            }
+        }
+    }
+
+    /**
+     * Initial sheet setup. Override this method to specifically change initial, sheet-wide,
+     * settings.
+     */
+    protected void initialSheetSetup() {
+        final PrintSetup printSetup = sheet.getPrintSetup();
+        printSetup.setLandscape(true);
+        sheet.setFitToPage(true);
+        sheet.setHorizontallyCenter(true);
+        if ((isHierarchical()) && (displayTotals)) {
+            hierarchicalTotalsSheet = workbook.createSheet("tempHts");
+        }
+    }
+
+    /**
+     * Adds the title row. Override this method to change title-related aspects of the workbook.
+     * Alternately, the title Row Object is accessible via getTitleRow() after report creation. To
+     * change title text use setReportTitle(). To change title CellStyle use setTitleStyle().
+     *
+     * @return the int
+     */
+    protected int addTitleRow() {
+        if ((null == reportTitle) || ("".equals(reportTitle))) {
+            return 0;
+        }
+        titleRow = sheet.createRow(0);
+        titleRow.setHeightInPoints(45);
+        final Cell titleCell;
+        final CellRangeAddress cra;
+        if (rowHeaders) {
+            titleCell = titleRow.createCell(1);
+            cra = new CellRangeAddress(0, 0, 1, getPropIds().size() - 1);
+            sheet.addMergedRegion(cra);
+        } else {
+            titleCell = titleRow.createCell(0);
+            cra = new CellRangeAddress(0, 0, 0, getPropIds().size() - 1);
+            sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, getPropIds().size() - 1));
+        }
+        titleCell.setCellValue(reportTitle);
+        titleCell.setCellStyle(titleCellStyle);
+        // cell borders don't work on merged ranges so, if there are borders
+        // we apply them to the merged range here.
+        if (titleCellStyle.getBorderLeft() != CellStyle.BORDER_NONE) {
+            RegionUtil.setBorderLeft(titleCellStyle.getBorderLeft(), cra, sheet, workbook);
+        }
+        if (titleCellStyle.getBorderRight() != CellStyle.BORDER_NONE) {
+            RegionUtil.setBorderRight(titleCellStyle.getBorderRight(), cra, sheet, workbook);
+        }
+        if (titleCellStyle.getBorderTop() != CellStyle.BORDER_NONE) {
+            RegionUtil.setBorderTop(titleCellStyle.getBorderTop(), cra, sheet, workbook);
+        }
+        if (titleCellStyle.getBorderBottom() != CellStyle.BORDER_NONE) {
+            RegionUtil.setBorderBottom(titleCellStyle.getBorderBottom(), cra, sheet, workbook);
+        }
+        return 1;
+    }
+
+    /**
+     * Adds the header row. Override this method to change header-row-related aspects of the
+     * workbook. Alternately, the header Row Object is accessible via getHeaderRow() after report
+     * creation. To change header CellStyle, though, use setHeaderStyle().
+     *
+     * @param row the row
+     */
+    protected void addHeaderRow(final int row) {
+        headerRow = sheet.createRow(row);
+        Cell headerCell;
+        Object propId;
+        headerRow.setHeightInPoints(40);
+        for (int col = 0; col < getPropIds().size(); col++) {
+            propId = getPropIds().get(col);
+            headerCell = headerRow.createCell(col);
+            headerCell.setCellValue(createHelper.createRichTextString(getTableHolder().getColumnHeader(propId)));
+            headerCell.setCellStyle(getColumnHeaderStyle(row, col));
+
+            final Short poiAlignment = getTableHolder().getCellAlignment(propId);
+            CellUtil.setAlignment(headerCell, workbook, poiAlignment);
+        }
+    }
+
+    /**
+     * This method is called by addTotalsRow() to determine what CellStyle to use. By default we
+     * just return totalsCellStyle which is either set to the default totals style, or can be
+     * overriden by the user using setTotalsStyle(). However, if the user wants to have different
+     * total items have different styles, then this method should be overriden. The parameters
+     * passed in are all potentially relevant items that may be used to determine what formatting to
+     * return, that are not accessible globally.
+     *
+     * @param row the row
+     * @param col the current column
+     * @return the header style
+     */
+    protected CellStyle getColumnHeaderStyle(final int row, final int col) {
+        if ((rowHeaders) && (col == 0)) {
+            return titleCellStyle;
+        }
+        return columnHeaderCellStyle;
+    }
+
+    /**
+     * For Hierarchical Containers, this method recursively adds root items and child items. The
+     * child items are appropriately grouped using grouping/outlining sheet functionality. Override
+     * this method to make any changes. To change the CellStyle used for all FilterTable data use
+     * setDataStyle(). For different data cells to have different CellStyles, override
+     * getDataStyle().
+     *
+     * @param row the row
+     * @return the int
+     */
+    protected int addHierarchicalDataRows(final Sheet sheetToAddTo, final int row) {
+        final Collection<?> roots;
+        int localRow = row;
+        roots = ((Container.Hierarchical) getTableHolder().getContainerDataSource()).rootItemIds();
+        /*
+         * For Hierarchical Containers, the outlining/grouping in the sheet is with the summary row
+         * at the top and the grouped/outlined subcategories below.
+         */
+        sheet.setRowSumsBelow(false);
+        int count = 0;
+        for (final Object rootId : roots) {
+            count = addDataRowRecursively(sheetToAddTo, rootId, localRow);
+            // for totals purposes, we just want to add rootIds which contain totals
+            // so we store just the totals in a separate sheet.
+            if (displayTotals) {
+                addDataRow(hierarchicalTotalsSheet, rootId, localRow);
+            }
+            if (count > 1) {
+                sheet.groupRow(localRow + 1, (localRow + count) - 1);
+                sheet.setRowGroupCollapsed(localRow + 1, true);
+            }
+            localRow = localRow + count;
+        }
+        return localRow;
+    }
+
+    /**
+     * this method adds row items for non-Hierarchical Containers. Override this method to make any
+     * changes. To change the CellStyle used for all FilterTable data use setDataStyle(). For different
+     * data cells to have different CellStyles, override getDataStyle().
+     *
+     * @param row the row
+     * @return the int
+     */
+    protected int addDataRows(final Sheet sheetToAddTo, final int row) {
+        final Collection<?> itemIds = getTableHolder().getContainerDataSource().getItemIds();
+        int localRow = row;
+        int count = 0;
+        for (final Object itemId : itemIds) {
+            addDataRow(sheetToAddTo, itemId, localRow);
+            count = 1;
+            if (count > 1) {
+                sheet.groupRow(localRow + 1, (localRow + count) - 1);
+                sheet.setRowGroupCollapsed(localRow + 1, true);
+            }
+            localRow = localRow + count;
+        }
+        return localRow;
+    }
+
+    /**
+     * Used by addHierarchicalDataRows() to implement the recursive calls.
+     *
+     * @param rootItemId the root item id
+     * @param row        the row
+     * @return the int
+     */
+    private int addDataRowRecursively(final Sheet sheetToAddTo, final Object rootItemId, final int row) {
+        int numberAdded = 0;
+        int localRow = row;
+        addDataRow(sheetToAddTo, rootItemId, row);
+        numberAdded++;
+        if (((Container.Hierarchical) getTableHolder().getContainerDataSource()).hasChildren(rootItemId)) {
+            final Collection<?> children = ((Container.Hierarchical) getTableHolder().getContainerDataSource())
+                    .getChildren(rootItemId);
+            for (final Object child : children) {
+                localRow++;
+                numberAdded = numberAdded + addDataRowRecursively(sheetToAddTo, child, localRow);
+            }
+        }
+        return numberAdded;
+    }
+
+    /**
+     * This method is ultimately used by either addDataRows() or addHierarchicalDataRows() to
+     * actually add the data to the Sheet.
+     *
+     * @param rootItemId the root item id
+     * @param row        the row
+     */
+    
+    protected void addDataRow(final Sheet sheetToAddTo, final Object rootItemId, final int row) {
+        final Row sheetRow = sheetToAddTo.createRow(row);
+        Property<?> prop;
+        Object propId;
+        Object value;
+        Cell sheetCell;
+        for (int col = 0; col < getPropIds().size(); col++) {
+            propId = getPropIds().get(col);
+            prop = getProperty(rootItemId, propId);
+            if (null == prop) {
+                value = null;
+            } else {
+                value = prop.getValue();
+            }
+            sheetCell = sheetRow.createCell(col);
+            final CellStyle cs = getCellStyle(rootItemId, row, col, false);
+            sheetCell.setCellStyle(cs);
+            final Short poiAlignment = getTableHolder().getCellAlignment(propId);
+            CellUtil.setAlignment(sheetCell, workbook, poiAlignment);
+            if (null != value) {
+                if (!isNumeric(prop.getType())) {
+                    if (java.util.Date.class.isAssignableFrom(prop.getType())) {
+                        sheetCell.setCellValue((Date) value);
+                    } else if(CellSetImage.class.isAssignableFrom(prop.getType())) {
+                    	ClientAnchor anchor = createHelper.createClientAnchor();
+                    	anchor.setAnchorType( ClientAnchor.MOVE_AND_RESIZE );
+                    	InputStream stream = getThemeResourceService().getThemeResourceInputStream(((CellSetImage)prop.getValue()).getResourceName(), ThemeResourceType.ICON);
+                    	try {
+							int pictureIndex = workbook.addPicture(IOUtils.toByteArray(stream), Workbook.PICTURE_TYPE_PNG);
+							anchor.setCol1( col );
+							anchor.setRow1( row );
+							anchor.setRow2( row );
+							anchor.setCol2( col+1 );
+							final Picture pict = drawing.createPicture( anchor, pictureIndex );
+							pict.resize();
+						} catch (IOException e) {
+		                    LOGGER.error("{}", e);
+						}
+
+                    } else {
+                        sheetCell.setCellValue(createHelper.createRichTextString(value.toString()));
+                    }
+                } else {
+                    try {
+                        // parse all numbers as double, the format will determine how they appear
+                        final Double d = Double.parseDouble(value.toString());
+                        sheetCell.setCellValue(d);
+                    } catch (final NumberFormatException nfe) {
+                        LOGGER.error("NumberFormatException parsing a numeric value: {}", nfe);
+                        sheetCell.setCellValue(createHelper.createRichTextString(value.toString()));
+                    }
+                }
+            }
+        }
+    }
+
+    private Property<?> getProperty(final Object rootItemId, final Object propId) {
+        Property<?> prop;
+        if (getTableHolder().isGeneratedColumn(propId)) {
+            prop = getTableHolder().getPropertyForGeneratedColumn(propId, rootItemId);
+        } else {
+            prop = getTableHolder().getContainerDataSource().getContainerProperty(rootItemId, propId);
+            if (useTableFormatPropertyValue) {
+                if (getTableHolder().isExportableFormattedProperty()) {
+                    final String formattedProp = getTableHolder().getFormattedPropertyValue(rootItemId, propId, prop);
+                    if (null == prop) {
+                        prop = new ObjectProperty<>(formattedProp, String.class);
+                    } else {
+                        final Object val = prop.getValue();
+                        if (null == val) {
+                            prop = new ObjectProperty<>(formattedProp, String.class);
+                        } else {
+                            if (!val.toString().equals(formattedProp)) {
+                                prop = new ObjectProperty<>(formattedProp, String.class);
+                            }
+                        }
+                    }
+                } else {
+                    LOGGER.error("{}", "Cannot use FilterTable formatted property unless FilterTable is instance of ExportableFormattedProperty");
+                }
+            }
+        }
+        return prop;
+    }
+
+    private Class<?> getPropertyType(final Object propId) {
+        Class<?> classType;
+        if (getTableHolder().isGeneratedColumn(propId)) {
+            classType = getTableHolder().getPropertyTypeForGeneratedColumn(propId);
+        } else {
+            classType = getTableHolder().getContainerDataSource().getType(propId);
+        }
+        return classType;
+    }
+
+    public void setExcelFormatOfProperty(final Object propertyId, final String excelFormat) {
+        if (propertyExcelFormatMap.containsKey(propertyId)) {
+            propertyExcelFormatMap.remove(propertyId);
+        }
+        propertyExcelFormatMap.put(propertyId.toString(), excelFormat);
+    }
+
+    /**
+     * This method is called by addDataRow() to determine what CellStyle to use. By default we just
+     * return dataStyle which is either set to the default data style, or can be overriden by the
+     * user using setDataStyle(). However, if the user wants to have different data items have
+     * different styles, then this method should be overriden. The parameters passed in are all
+     * potentially relevant items that may be used to determine what formatting to return, that are
+     * not accessible globally.
+     *
+     * @param rootItemId the root item id
+     * @param row        the row
+     * @param col        the col
+     * @return the data style
+     */
+    protected CellStyle getCellStyle(final Object rootItemId, final int row, final int col, final boolean totalsRow) {
+        final Object propId = getPropIds().get(col);
+        // get the basic style for the type of cell (i.e. data, header, total)
+        if ((rowHeaders) && (col == 0)) {
+            if (null == rowHeaderCellStyle) {
+                return columnHeaderCellStyle;
+            }
+            return rowHeaderCellStyle;
+        }
+        final Class<?> propType = getPropertyType(propId);
+        if (totalsRow) {
+            if (propertyExcelFormatMap.containsKey(propId)) {
+                final short df = dataFormat.getFormat(propertyExcelFormatMap.get(propId));
+                final CellStyle customTotalStyle = workbook.createCellStyle();
+                customTotalStyle.cloneStyleFrom(totalsDoubleCellStyle);
+                customTotalStyle.setDataFormat(df);
+                return customTotalStyle;
+            }
+            if (isIntegerLongShortOrBigDecimal(propType)) {
+                return totalsIntegerCellStyle;
+            }
+            return totalsDoubleCellStyle;
+        }
+        // Check if the user has over-ridden that data format of this property
+        if (propertyExcelFormatMap.containsKey(propId)) {
+            final short df = dataFormat.getFormat(propertyExcelFormatMap.get(propId));
+            if (dataFormatCellStylesMap.containsKey(df)) {
+                return dataFormatCellStylesMap.get(df);
+            }
+            // if it hasn't already been created for re-use, we create a cell style and override the data format
+            // For data cells, each data format corresponds to a single complete cell style
+            final CellStyle retStyle = workbook.createCellStyle();
+            retStyle.cloneStyleFrom(dataFormatCellStylesMap.get(doubleDataFormat));
+            retStyle.setDataFormat(df);
+            dataFormatCellStylesMap.put(df, retStyle);
+            return retStyle;
+        }
+        // if not over-ridden, use the overall setting
+        if (isDoubleOrFloat(propType)) {
+            return dataFormatCellStylesMap.get(doubleDataFormat);
+        } else {
+            if (isIntegerLongShortOrBigDecimal(propType)) {
+                return dataFormatCellStylesMap.get(integerDataFormat);
+            } else {
+                if (java.util.Date.class.isAssignableFrom(propType)) {
+                    return dataFormatCellStylesMap.get(dateDataFormat);
+                }
+            }
+        }
+        return dataFormatCellStylesMap.get(doubleDataFormat);
+    }
+
+    /**
+     * Adds the totals row to the report. Override this method to make any changes. Alternately, the
+     * totals Row Object is accessible via getTotalsRow() after report creation. To change the
+     * CellStyle used for the totals row, use setFormulaStyle. For different totals cells to have
+     * different CellStyles, override getTotalsStyle().
+     *
+     * @param currentRow the current row
+     * @param startRow   the start row
+     */
+    protected void addTotalsRow(final int currentRow, final int startRow) {
+        totalsRow = sheet.createRow(currentRow);
+        totalsRow.setHeightInPoints(30);
+        Cell cell;
+        CellRangeAddress cra;
+        for (int col = 0; col < getPropIds().size(); col++) {
+            final Object propId = getPropIds().get(col);
+            cell = totalsRow.createCell(col);
+            cell.setCellStyle(getCellStyle(currentRow, startRow, col, true));
+            final Short poiAlignment = getTableHolder().getCellAlignment(propId);
+            CellUtil.setAlignment(cell, workbook, poiAlignment);
+            final Class<?> propType = getPropertyType(propId);
+            if (isNumeric(propType)) {
+                cra = new CellRangeAddress(startRow, currentRow - 1, col, col);
+                if (isHierarchical()) {
+                    // 9 & 109 are for sum. 9 means include hidden cells, 109 means exclude.
+                    // this will show the wrong value if the user expands an outlined category, so
+                    // we will range value it first
+                    cell.setCellFormula("SUM(" + cra.formatAsString(hierarchicalTotalsSheet.getSheetName(),
+                            true) + ")");
+                } else {
+                    cell.setCellFormula("SUM(" + cra.formatAsString() + ")");
+                }
+            } else {
+                if (0 == col) {
+                    cell.setCellValue(createHelper.createRichTextString("Total"));
+                }
+            }
+        }
+    }
+
+    /**
+     * Final formatting of the sheet upon completion of writing the data. For example, we can only
+     * size the column widths once the data is in the report and the sheet knows how wide the data
+     * is.
+     */
+    protected void finalSheetFormat() {
+        final FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
+        if (isHierarchical()) {
+            /*
+             * evaluateInCell() is equivalent to paste special -> value. The formula refers to cells
+             * in the other sheet we are going to delete. We sum in the other sheet because if we
+             * summed in the main sheet, we would double count. Subtotal with hidden rows is not yet
+             * implemented in POI.
+             */
+            for (final Row r : sheet) {
+                for (final Cell c : r) {
+                    if (c.getCellType() == Cell.CELL_TYPE_FORMULA) {
+                        evaluator.evaluateInCell(c);
+                    }
+                }
+            }
+            workbook.setActiveSheet(workbook.getSheetIndex(sheet));
+            if (hierarchicalTotalsSheet != null) {
+                workbook.removeSheetAt(workbook.getSheetIndex(hierarchicalTotalsSheet));
+            }
+        } else {
+            evaluator.evaluateAll();
+        }
+        for (int col = 0; col < getPropIds().size(); col++) {
+            sheet.autoSizeColumn(col);
+        }
+    }
+
+    /**
+     * Returns the default title style. Obtained from: http://svn.apache.org/repos/asf/poi
+     * /trunk/src/examples/src/org/apache/poi/ss/examples/TimesheetDemo.java
+     *
+     * @param wb the wb
+     * @return the cell style
+     */
+    protected CellStyle defaultTitleCellStyle(final Workbook wb) {
+        CellStyle style;
+        final Font titleFont = wb.createFont();
+        titleFont.setFontHeightInPoints((short) 18);
+        titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
+        style = wb.createCellStyle();
+        style.setAlignment(CellStyle.ALIGN_CENTER);
+        style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
+        style.setFont(titleFont);
+        return style;
+    }
+
+    /**
+     * Returns the default header style. Obtained from: http://svn.apache.org/repos/asf/poi
+     * /trunk/src/examples/src/org/apache/poi/ss/examples/TimesheetDemo.java
+     *
+     * @param wb the wb
+     * @return the cell style
+     */
+    protected CellStyle defaultHeaderCellStyle(final Workbook wb) {
+        CellStyle style;
+        final Font monthFont = wb.createFont();
+        monthFont.setFontHeightInPoints((short) 11);
+        monthFont.setColor(IndexedColors.WHITE.getIndex());
+        style = wb.createCellStyle();
+        style.setAlignment(CellStyle.ALIGN_CENTER);
+        style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
+        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setFillPattern(CellStyle.SOLID_FOREGROUND);
+        style.setFont(monthFont);
+        style.setWrapText(true);
+        return style;
+    }
+
+    /**
+     * Returns the default data cell style. Obtained from: http://svn.apache.org/repos/asf/poi
+     * /trunk/src/examples/src/org/apache/poi/ss/examples/TimesheetDemo.java
+     *
+     * @param wb the wb
+     * @return the cell style
+     */
+    protected CellStyle defaultDataCellStyle(final Workbook wb) {
+        CellStyle style;
+        style = wb.createCellStyle();
+        style.setAlignment(CellStyle.ALIGN_CENTER);
+        style.setWrapText(true);
+        style.setBorderRight(CellStyle.BORDER_THIN);
+        style.setRightBorderColor(IndexedColors.BLACK.getIndex());
+        style.setBorderLeft(CellStyle.BORDER_THIN);
+        style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
+        style.setBorderTop(CellStyle.BORDER_THIN);
+        style.setTopBorderColor(IndexedColors.BLACK.getIndex());
+        style.setBorderBottom(CellStyle.BORDER_THIN);
+        style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
+        style.setDataFormat(doubleDataFormat);
+        return style;
+    }
+
+    /**
+     * Returns the default totals row style for Double data. Obtained from: http://svn.apache.org/repos/asf/poi
+     * /trunk/src/examples/src/org/apache/poi/ss/examples/TimesheetDemo.java
+     *
+     * @param wb the wb
+     * @return the cell style
+     */
+    protected CellStyle defaultTotalsDoubleCellStyle(final Workbook wb) {
+        CellStyle style;
+        style = wb.createCellStyle();
+        style.setAlignment(CellStyle.ALIGN_CENTER);
+        style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
+        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        style.setFillPattern(CellStyle.SOLID_FOREGROUND);
+        style.setDataFormat(doubleDataFormat);
+        return style;
+    }
+
+    /**
+     * Returns the default totals row style for Integer data. Obtained from: http://svn.apache.org/repos/asf/poi
+     * /trunk/src/examples/src/org/apache/poi/ss/examples/TimesheetDemo.java
+     *
+     * @param wb the wb
+     * @return the cell style
+     */
+    protected CellStyle defaultTotalsIntegerCellStyle(final Workbook wb) {
+        CellStyle style;
+        style = wb.createCellStyle();
+        style.setAlignment(CellStyle.ALIGN_CENTER);
+        style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
+        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        style.setFillPattern(CellStyle.SOLID_FOREGROUND);
+        style.setDataFormat(integerDataFormat);
+        return style;
+    }
+
+    protected short defaultDoubleDataFormat() {
+        return createHelper.createDataFormat().getFormat("0.00");
+    }
+
+    protected short defaultIntegerDataFormat() {
+        return createHelper.createDataFormat().getFormat("0");
+    }
+
+    protected short defaultDateDataFormat() {
+        return createHelper.createDataFormat().getFormat("mm/dd/yyyy");
+    }
+
+    public void setDoubleDataFormat(final String excelDoubleFormat) {
+        CellStyle prevDoubleDataStyle = null;
+        if (dataFormatCellStylesMap.containsKey(doubleDataFormat)) {
+            prevDoubleDataStyle = dataFormatCellStylesMap.get(doubleDataFormat);
+            dataFormatCellStylesMap.remove(doubleDataFormat);
+        }
+        doubleDataFormat = createHelper.createDataFormat().getFormat(excelDoubleFormat);
+        if (null != prevDoubleDataStyle) {
+            doubleCellStyle = prevDoubleDataStyle;
+            doubleCellStyle.setDataFormat(doubleDataFormat);
+            dataFormatCellStylesMap.put(doubleDataFormat, doubleCellStyle);
+        }
+    }
+
+    public void setIntegerDataFormat(final String excelIntegerFormat) {
+        CellStyle prevIntegerDataStyle = null;
+        if (dataFormatCellStylesMap.containsKey(integerDataFormat)) {
+            prevIntegerDataStyle = dataFormatCellStylesMap.get(integerDataFormat);
+            dataFormatCellStylesMap.remove(integerDataFormat);
+        }
+        integerDataFormat = createHelper.createDataFormat().getFormat(excelIntegerFormat);
+        if (null != prevIntegerDataStyle) {
+            integerCellStyle = prevIntegerDataStyle;
+            integerCellStyle.setDataFormat(integerDataFormat);
+            dataFormatCellStylesMap.put(integerDataFormat, integerCellStyle);
+        }
+    }
+
+    public void setDateDataFormat(final String excelDateFormat) {
+        CellStyle prevDateDataStyle = null;
+        if (dataFormatCellStylesMap.containsKey(dateDataFormat)) {
+            prevDateDataStyle = dataFormatCellStylesMap.get(dateDataFormat);
+            dataFormatCellStylesMap.remove(dateDataFormat);
+        }
+        dateDataFormat = createHelper.createDataFormat().getFormat(excelDateFormat);
+        if (null != prevDateDataStyle) {
+            dateCellStyle = prevDateDataStyle;
+            dateCellStyle.setDataFormat(dateDataFormat);
+            dataFormatCellStylesMap.put(dateDataFormat, dateCellStyle);
+        }
+    }
+
+    /**
+     * Utility method to determine whether value being put in the Cell is numeric.
+     *
+     * @param type the type
+     * @return true, if is numeric
+     */
+    private boolean isNumeric(final Class<?> type) {
+        if (isIntegerLongShortOrBigDecimal(type)) {
+            return true;
+        }
+        return isDoubleOrFloat(type);
+    }
+
+    /**
+     * Utility method to determine whether value being put in the Cell is integer-like type.
+     *
+     * @param type the type
+     * @return true, if is integer-like
+     */
+    private boolean isIntegerLongShortOrBigDecimal(final Class<?> type) {
+        if ((Integer.class.equals(type) || (int.class.equals(type)))) {
+            return true;
+        }
+        if ((Long.class.equals(type) || (long.class.equals(type)))) {
+            return true;
+        }
+        if ((Short.class.equals(type)) || (short.class.equals(type))) {
+            return true;
+        }
+        return BigDecimal.class.equals(type);
+    }
+
+    /**
+     * Utility method to determine whether value being put in the Cell is double-like type.
+     *
+     * @param type the type
+     * @return true, if is double-like
+     */
+    private boolean isDoubleOrFloat(final Class<?> type) {
+        if ((Double.class.equals(type)) || (double.class.equals(type))) {
+            return true;
+        }
+        return ((Float.class.equals(type)) || (float.class.equals(type)));
+    }
+
+    /**
+     * Gets the workbook.
+     *
+     * @return the workbook
+     */
+    public Workbook getWorkbook() {
+        return workbook;
+    }
+
+    /**
+     * Gets the sheet name.
+     *
+     * @return the sheet name
+     */
+    public String getSheetName() {
+        return sheetName;
+    }
+
+    /**
+     * Gets the report title.
+     *
+     * @return the report title
+     */
+    public String getReportTitle() {
+        return reportTitle;
+    }
+
+    /**
+     * Gets the export file name.
+     *
+     * @return the export file name
+     */
+    public String getExportFileName() {
+        return exportFileName;
+    }
+
+    /**
+     * Gets the cell style used for report data..
+     *
+     * @return the cell style
+     */
+    public CellStyle getDoubleDataStyle() {
+        return doubleCellStyle;
+    }
+
+    /**
+     * Gets the cell style used for report data..
+     *
+     * @return the cell style
+     */
+    public CellStyle getIntegerDataStyle() {
+        return integerCellStyle;
+    }
+
+    public CellStyle getDateDataStyle() {
+        return dateCellStyle;
+    }
+
+    /**
+     * Gets the cell style used for the report headers.
+     *
+     * @return the column header style
+     */
+    public CellStyle getColumnHeaderStyle() {
+        return columnHeaderCellStyle;
+    }
+
+    /**
+     * Gets the cell title used for the report title.
+     *
+     * @return the title style
+     */
+    public CellStyle getTitleStyle() {
+        return titleCellStyle;
+    }
+
+    /**
+     * Sets the text used for the report title.
+     *
+     * @param reportTitle the new report title
+     */
+    public void setReportTitle(final String reportTitle) {
+        this.reportTitle = reportTitle;
+    }
+
+    /**
+     * Sets the export file name.
+     *
+     * @param exportFileName the new export file name
+     */
+    public void setExportFileName(final String exportFileName) {
+    	this.exportFileName = exportFileName;
+    }
+
+    /**
+     * Sets the cell style used for report data.
+     *
+     * @param doubleDataStyle the new data style
+     */
+    public void setDoubleDataStyle(final CellStyle doubleDataStyle) {
+    	this.doubleCellStyle = doubleDataStyle;
+    }
+
+    /**
+     * Sets the cell style used for report data.
+     *
+     * @param integerDataStyle the new data style
+     */
+    public void setIntegerDataStyle(final CellStyle integerDataStyle) {
+    	this.integerCellStyle = integerDataStyle;
+    }
+
+    /**
+     * Sets the cell style used for report data.
+     *
+     * @param dateDataStyle the new data style
+     */
+    public void setDateDataStyle(final CellStyle dateDataStyle) {
+    	this.dateCellStyle = dateDataStyle;
+    }
+
+    /**
+     * Sets the cell style used for the report headers.
+     *
+     * @param columnHeaderStyle CellStyle
+     */
+    public void setColumnHeaderStyle(final CellStyle columnHeaderStyle) {
+    	this.columnHeaderCellStyle = columnHeaderStyle;
+    }
+
+    /**
+     * Sets the cell style used for the report title.
+     *
+     * @param titleStyle the new title style
+     */
+    public void setTitleStyle(final CellStyle titleStyle) {
+    	this.titleCellStyle = titleStyle;
+    }
+
+    /**
+     * Gets the title row.
+     *
+     * @return the title row
+     */
+    public Row getTitleRow() {
+        return titleRow;
+    }
+
+    /**
+     * Gets the header row.
+     *
+     * @return the header row
+     */
+    public Row getHeaderRow() {
+        return headerRow;
+    }
+
+    /**
+     * Gets the totals row.
+     *
+     * @return the totals row
+     */
+    public Row getTotalsRow() {
+        return totalsRow;
+    }
+
+    /**
+     * Gets the cell style used for the totals row.
+     *
+     * @return the totals style
+     */
+    public CellStyle getTotalsDoubleStyle() {
+        return totalsDoubleCellStyle;
+    }
+
+    /**
+     * Sets the cell style used for the totals row.
+     *
+     * @param totalsDoubleStyle the new totals style
+     */
+    public void setTotalsDoubleStyle(final CellStyle totalsDoubleStyle) {
+    	this.totalsDoubleCellStyle = totalsDoubleStyle;
+    }
+
+    /**
+     * Gets the cell style used for the totals row.
+     *
+     * @return the totals style
+     */
+    public CellStyle getTotalsIntegerStyle() {
+        return totalsIntegerCellStyle;
+    }
+
+    /**
+     * Sets the cell style used for the totals row.
+     *
+     * @param totalsIntegerStyle the new totals style
+     */
+    public void setTotalsIntegerStyle(final CellStyle totalsIntegerStyle) {
+    	this.totalsIntegerCellStyle = totalsIntegerStyle;
+    }
+
+    /**
+     * Flag indicating whether a totals row will be added to the report or not.
+     *
+     * @return true, if totals row will be added
+     */
+    public boolean isDisplayTotals() {
+        return displayTotals;
+    }
+
+    /**
+     * Sets the flag indicating whether a totals row will be added to the report or not.
+     *
+     * @param displayTotals boolean
+     */
+    public void setDisplayTotals(final boolean displayTotals) {
+    	this.displayTotals = displayTotals;
+    }
+
+    public void setUseTableFormatPropertyValue(final boolean useFormatPropertyValue) {
+    	this.useTableFormatPropertyValue = useFormatPropertyValue;
+    }
+
+    /**
+     * See value of flag indicating whether the first column should be treated as row headers.
+     *
+     * @return boolean
+     */
+    public boolean hasRowHeaders() {
+        return rowHeaders;
+    }
+
+    /**
+     * Method getRowHeaderStyle.
+     *
+     * @return CellStyle
+     */
+    public CellStyle getRowHeaderStyle() {
+        return rowHeaderCellStyle;
+    }
+
+    /**
+     * Set value of flag indicating whether the first column should be treated as row headers.
+     *
+     * @param rowHeaders boolean
+     */
+    public void setRowHeaders(final boolean rowHeaders) {
+    	this.rowHeaders = rowHeaders;
+    }
+
+    /**
+     * Method setRowHeaderStyle.
+     *
+     * @param rowHeaderStyle CellStyle
+     */
+    public void setRowHeaderStyle(final CellStyle rowHeaderStyle) {
+    	this.rowHeaderCellStyle = rowHeaderStyle;
+    }
+
+    private void format() {
+        setRowHeaders(true);
+        CellStyle style;
+
+        style = getTitleStyle();
+        setStyle(style, HSSFColor.DARK_BLUE.index, 18, HSSFColor.WHITE.index, true,
+                CellStyle.ALIGN_CENTER_SELECTION);
+
+        style = getColumnHeaderStyle();
+        setStyle(style, HSSFColor.LIGHT_BLUE.index, 12, HSSFColor.BLACK.index, true,
+        		CellStyle.ALIGN_CENTER);
+
+        style = getDateDataStyle();
+        setStyle(style, HSSFColor.LIGHT_CORNFLOWER_BLUE.index, 12, HSSFColor.BLACK.index, false,
+        		CellStyle.ALIGN_RIGHT);
+
+        style = getDoubleDataStyle();
+        setStyle(style, HSSFColor.LIGHT_CORNFLOWER_BLUE.index, 12, HSSFColor.BLACK.index, false,
+        		CellStyle.ALIGN_RIGHT);
+        setTotalsDoubleStyle(style);
+
+        style = getIntegerDataStyle();
+        setStyle(style, HSSFColor.LIGHT_CORNFLOWER_BLUE.index, 12, HSSFColor.BLACK.index, false,
+        		CellStyle.ALIGN_RIGHT);
+        setTotalsIntegerStyle(style);
+
+        // we want the rowHeader style to be like the columnHeader style, just centered differently.
+        final CellStyle newStyle = workbook.createCellStyle();
+        newStyle.cloneStyleFrom(style);
+        newStyle.setAlignment(CellStyle.ALIGN_LEFT);
+        setRowHeaderStyle(newStyle);
+    }
+
+    private void setStyle(CellStyle style, short foregroundColor, int fontHeight, short fontColor,
+                          boolean isBold, short alignment) {
+        style.setFillForegroundColor(foregroundColor);
+        style.setFillPattern(CellStyle.SOLID_FOREGROUND);
+        Font f = workbook.getFontAt(style.getFontIndex());
+        f.setFontHeightInPoints((short) fontHeight);
+        f.setFontName(HSSFFont.FONT_ARIAL);
+        f.setColor(fontColor);
+        if(isBold) {
+        	f.setBoldweight(Font.BOLDWEIGHT_BOLD);
+        } else {
+        	f.setBoldweight(Font.BOLDWEIGHT_NORMAL);
+        }
+        style.setAlignment(alignment);
+        style.setBorderLeft(CellStyle.BORDER_THIN);
+        style.setBorderRight(CellStyle.BORDER_THIN);
+        style.setBorderTop(CellStyle.BORDER_THIN);
+        style.setBorderBottom(CellStyle.BORDER_THIN);
+        style.setLeftBorderColor(HSSFColor.BLACK.index);
+        style.setRightBorderColor(HSSFColor.BLACK.index);
+        style.setTopBorderColor(HSSFColor.BLACK.index);
+        style.setBottomBorderColor(HSSFColor.BLACK.index);
+    }
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExportableColumnGenerator.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExportableColumnGenerator.java
new file mode 100644
index 0000000..b1b78f3
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExportableColumnGenerator.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.data.Property;
+
+public interface ExportableColumnGenerator extends com.vaadin.ui.Table.ColumnGenerator {
+
+    Property getGeneratedProperty(Object itemId, Object columnId);
+    // the type of the generated property
+    Class<?> getType();
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExportableFormattedProperty.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExportableFormattedProperty.java
new file mode 100644
index 0000000..c87d939
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/ExportableFormattedProperty.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.data.Property;
+
+import java.io.Serializable;
+
+public interface ExportableFormattedProperty extends Serializable {
+
+    public String getFormattedPropertyValue(Object rowId, Object colId, Property property);
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/PdfExport.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/PdfExport.java
new file mode 100644
index 0000000..c312574
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/PdfExport.java
@@ -0,0 +1,234 @@
+/**
+ *                                                                            
+ *  Copyright (c) 2019 - Loetz GmbH&Co.KG (69115 Heidelberg, Germany) 
+ *                                                                            
+ *  All rights reserved. This program and the accompanying materials           
+ *  are made available under the terms of the Eclipse Public License 2.0        
+ *  which accompanies this distribution, and is available at                  
+ *  https://www.eclipse.org/legal/epl-2.0/                                 
+ *                                 
+ *  SPDX-License-Identifier: EPL-2.0                                 
+ *                                                                            
+ *  Contributors:                                                      
+ * 	   Christophe Loetz (Loetz GmbH&Co.KG) - initial implementation
+ * 
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
+import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
+import org.apache.poi.hssf.usermodel.HSSFPicture;
+import org.apache.poi.hssf.usermodel.HSSFPictureData;
+import org.apache.poi.hssf.usermodel.HSSFShape;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.eclipse.osbp.ui.api.themes.IThemeResourceService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tepi.filtertable.FilterTable;
+import org.vandeseer.easytable.TableDrawer;
+import org.vandeseer.easytable.settings.HorizontalAlignment;
+import org.vandeseer.easytable.structure.Row.RowBuilder;
+import org.vandeseer.easytable.structure.Table;
+import org.vandeseer.easytable.structure.Table.TableBuilder;
+import org.vandeseer.easytable.structure.cell.CellImage;
+import org.vandeseer.easytable.structure.cell.CellText;
+
+public class PdfExport extends ExcelExport {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -4742770309084096185L;
+	private static final Logger LOGGER = LoggerFactory.getLogger(PdfExport.class.getName());
+	private static final float PADDING = 50f;
+
+	public PdfExport(FilterTable table, IThemeResourceService themeResourceService) {
+		super(table, themeResourceService);
+	}
+
+	public PdfExport(TableHolder tableHolder) {
+		super(tableHolder);
+	}
+
+	@Override
+	public boolean sendConverted() {
+		File tempXlsFile;
+		File tempPdfFile;
+		try {
+			tempXlsFile = File.createTempFile("tmp", ".xls");
+			final FileOutputStream fileOut = new FileOutputStream(tempXlsFile);
+			workbook.write(fileOut);
+			final FileInputStream fis = new FileInputStream(tempXlsFile);
+
+			HSSFWorkbook xlsWorkbook = new HSSFWorkbook(fis);
+			HSSFSheet worksheet = xlsWorkbook.getSheetAt(0);
+
+			Map<Long,HSSFPictureData> pictureMap = getPictureData(xlsWorkbook, worksheet);
+
+			Iterator<Row> rowIterator = worksheet.iterator();
+			PDDocument document = new PDDocument();
+			tempPdfFile = File.createTempFile("tmp", ".pdf");
+
+			TableBuilder tableBuilder = Table.builder().fontSize(6).font(PDType1Font.HELVETICA);
+			int numCols = getColumnsCount(worksheet);
+			for(int i=0; i<numCols; i++) {
+				tableBuilder.addColumnOfWidth(60);
+			}
+			while (rowIterator.hasNext()) {
+				RowBuilder rowBuilder = org.vandeseer.easytable.structure.Row.builder();
+				Row row = rowIterator.next();
+				for(int col = 0; col < numCols; col++) {
+					Cell cell = row.getCell(col,  Row.CREATE_NULL_AS_BLANK);
+					//will iterate over the Merged cells
+					boolean skipMerged = false;
+					boolean regionFound = false;
+		            for (int i = 0; i < sheet.getNumMergedRegions() && !regionFound; i++) {
+		                CellRangeAddress region = sheet.getMergedRegion(i); //Region of merged cells
+
+		                int colIndex = region.getFirstColumn(); //number of columns merged
+		                int rowNum = region.getFirstRow();      //number of rows merged
+		                //check first cell of the region
+		                if (rowNum == cell.getRowIndex() && colIndex == cell.getColumnIndex()) {
+				            addCell(rowBuilder, sheet.getRow(rowNum).getCell(colIndex), region.getLastColumn()-region.getFirstColumn()+1, false, pictureMap, document);
+				            regionFound = true;
+		                    break;
+		                }
+		                //the data in merge cells is always present on the first cell. All other cells(in merged region) are considered blank
+		                if(region.getFirstColumn() <= cell.getColumnIndex() && cell.getColumnIndex() <= region.getLastColumn() && 
+		                	region.getFirstRow() <= cell.getRowIndex() && cell.getRowIndex() <= region.getLastRow()) {
+		                	skipMerged = true;
+		                }
+		            }
+		            if(!regionFound) {
+		            	addCell(rowBuilder, cell, 1, skipMerged, pictureMap, document);
+		            }
+				}
+				tableBuilder.addRow(rowBuilder.build());
+			}
+
+			TableDrawer drawer = TableDrawer.builder()
+					.table(tableBuilder.build())
+					.startX(PADDING)
+					.startY(100F)
+					.endY(50F)
+					.build();
+			do {
+	            PDPage page = new PDPage(PDRectangle.A4);
+	            drawer.startY(page.getMediaBox().getHeight() - 50);
+	            document.addPage(page);
+	            try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+	                drawer.contentStream(contentStream).draw();
+	            }
+
+	            drawer.startY(page.getMediaBox().getHeight() - 50);
+	        } while (!drawer.isFinished());
+			
+			document.save(tempPdfFile);
+			document.close();
+			fis.close();
+			if (null == mimeType) {
+				setMimeType(PDF_MIME_TYPE);
+			}
+			return super.sendConvertedFileToUser(getTableHolder().getUI(), tempPdfFile, getReportTitle() + ".pdf");
+		} catch (Exception e) {
+			LOGGER.error("Converting to CSV failed with IOException {}", e);
+			return false;
+		}
+	}
+
+	private Map<Long, HSSFPictureData> getPictureData(HSSFWorkbook xlsWorkbook, HSSFSheet worksheet) {
+		Map<Long, HSSFPictureData> pictureMap = new HashMap<>();
+		List<HSSFShape> shapes = worksheet.getDrawingPatriarch().getChildren();
+		for (int i = 0; i < shapes.size(); i++) {
+			if (shapes.get(i) instanceof HSSFPicture) {
+				HSSFClientAnchor anchor = ((HSSFPicture)shapes.get(i)).getPreferredSize();
+				pictureMap.put(getPictureKey(anchor.getCol1(), anchor.getRow1()), xlsWorkbook.getAllPictures().get(i));
+			}
+		}
+		return pictureMap;
+	}
+	
+	private Long getPictureKey(int col, int row) {
+		return (long) (col + row*1000);
+	}
+
+	private void addCell(RowBuilder rowBuilder, Cell cell, int span, boolean skipMerged, Map<Long, HSSFPictureData> pictureMap, PDDocument document) {
+		if(pictureMap.containsKey(getPictureKey(cell.getColumnIndex(), cell.getRowIndex()))) {
+			PDImageXObject image = null;
+			try {
+				image = PDImageXObject.createFromByteArray(document, pictureMap.get(getPictureKey(cell.getColumnIndex(), cell.getRowIndex())).getData(), cell.getStringCellValue());
+				rowBuilder.add(CellImage.builder().image(image).build());
+				return;
+			} catch (IOException e) {
+				LOGGER.error("cannot create image from picture due to {}", e);
+			}
+		} 
+		switch (cell.getCellType()) {
+		case Cell.CELL_TYPE_BLANK:
+			if(!skipMerged) {
+				rowBuilder.add(CellText.builder().text("").colSpan(span).build());
+			}
+			break;
+		case Cell.CELL_TYPE_BOOLEAN:
+			rowBuilder.add(CellText.builder().text(Boolean.toString(cell.getBooleanCellValue())).colSpan(span).build());
+			break;
+		case Cell.CELL_TYPE_NUMERIC:
+			rowBuilder.add(CellText.builder().text(String.valueOf(cell.getNumericCellValue())).colSpan(span).horizontalAlignment(HorizontalAlignment.RIGHT).build());
+			break;
+		case Cell.CELL_TYPE_STRING:
+			rowBuilder.add(CellText.builder().text(cell.getStringCellValue()).colSpan(span).build());
+			break;
+		case Cell.CELL_TYPE_FORMULA:
+			rowBuilder.add(CellText.builder().text(String.valueOf(cell.getNumericCellValue())).colSpan(span).build());
+			break;
+		case Cell.CELL_TYPE_ERROR:
+			rowBuilder.add(CellText.builder().text("error").colSpan(span).build());
+			break;
+		default:
+			break;
+		}
+	}
+
+	/** Count max number of nonempty cells in sheet rows */
+	private int getColumnsCount(HSSFSheet xssfSheet) {
+		int result = 0;
+		Iterator<Row> rowIterator = xssfSheet.iterator();
+		while (rowIterator.hasNext()) {
+			Row row = rowIterator.next();
+			List<Cell> cells = new ArrayList<>();
+			Iterator<Cell> cellIterator = row.cellIterator();
+			while (cellIterator.hasNext()) {
+				cells.add(cellIterator.next());
+			}
+			for (int i = cells.size(); i >= 0; i--) {
+				Cell cell = cells.get(i - 1);
+				if (cell.toString().trim().isEmpty()) {
+					cells.remove(i - 1);
+				} else {
+					result = cells.size() > result ? cells.size() : result;
+					break;
+				}
+			}
+		}
+		return result;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/PropertyFormatTable.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/PropertyFormatTable.java
new file mode 100644
index 0000000..fdab223
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/PropertyFormatTable.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.data.Property;
+import com.vaadin.ui.Table;
+
+public class PropertyFormatTable extends Table implements ExportableFormattedProperty {
+    private static final long serialVersionUID = 3155836832984769425L;
+
+    @Override
+    public String getFormattedPropertyValue(final Object rowId, final Object colId,
+            final Property property) {
+        return this.formatPropertyValue(rowId, colId, property);
+    }
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TableExport.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TableExport.java
new file mode 100644
index 0000000..ba771e1
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TableExport.java
@@ -0,0 +1,153 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.ui.UI;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.eclipse.osbp.ui.api.themes.IThemeResourceService;
+import org.tepi.filtertable.FilterTable;
+
+public abstract class TableExport implements Serializable {
+
+    private static final long serialVersionUID = -2972527330991334117L;
+    private static final Logger LOGGER = Logger.getLogger(TableExport.class.getName());
+
+    public static final String EXCEL_MIME_TYPE = "application/vnd.ms-excel";
+    public static final String CSV_MIME_TYPE = "text/csv";
+    public static final String PDF_MIME_TYPE = "application/pdf";
+
+    /** The Tableholder to export. */
+    private TableHolder tableHolder;
+
+    /** The window to send the export result */
+    protected String exportWindow = "_self";
+
+    protected String mimeType;
+	private transient IThemeResourceService themeResourceService;
+
+    public TableExport(final FilterTable table, IThemeResourceService themeResourceService) {
+        this.setTable(table);
+        this.setThemeResourceService(themeResourceService);
+    }
+
+    public TableExport(TableHolder tableHolder) {
+        this.tableHolder = tableHolder;
+    }
+
+    public TableHolder getTableHolder() {
+        return tableHolder;
+    }
+
+    public List<Object> getPropIds() {
+        return tableHolder.getPropIds();
+    }
+
+    public final void setTable(final FilterTable table) {
+        tableHolder = new DefaultTableHolder(table);
+    }
+    
+    public void setTableHolder(final TableHolder tableHolder) {
+        this.tableHolder = tableHolder;
+    }
+
+    public boolean isHierarchical() {
+        return tableHolder.isHierarchical();
+    }
+
+    public abstract void convertTable();
+    public abstract boolean sendConverted();
+
+    /**
+     * Create and export the FilterTable contents as some sort of file type. In the case of conversion to
+     * Excel it would be an ".xls" file containing the contents as a report. Only the export()
+     * method needs to be called. If the user wishes to manipulate the converted object to export,
+     * then convertTable() should be called separately, and, after manipulation, sendConverted().
+     */
+
+    public void export() {
+        convertTable();
+        sendConverted();
+    }
+
+    /**
+     * Utility method to send the converted object to the user, if it has been written to a
+     * temporary File.
+     * 
+     * Code obtained from: http://vaadin.com/forum/-/message_boards/view_message/159583
+     * 
+     * @return true, if successful
+     */
+    public boolean sendConvertedFileToUser(final UI app, final File fileToExport,
+            final String exportFileName, final String mimeType) {
+        setMimeType(mimeType);
+        return sendConvertedFileToUser(app, fileToExport, exportFileName);
+
+    }
+
+    @SuppressWarnings("deprecation")
+	protected boolean sendConvertedFileToUser(final UI app, final File fileToExport,
+            final String exportFileName) {
+        TemporaryFileDownloadResource resource;
+        try {
+            resource =
+                    new TemporaryFileDownloadResource(app, exportFileName, mimeType, fileToExport);
+            if (null == app) {
+                UI.getCurrent().getPage().open(resource, null, false);
+            } else {
+                app.getPage().open(resource, null, false);
+            }
+        } catch (final FileNotFoundException e) {
+            LOGGER.warning("Sending file to user failed with FileNotFoundException " + e);
+            return false;
+        }
+        return true;
+    }
+
+    public String getExportWindow() {
+        return this.exportWindow;
+    }
+
+    public void setExportWindow(final String exportWindow) {
+        this.exportWindow = exportWindow;
+    }
+
+    public String getMimeType() {
+        return this.mimeType;
+    }
+
+    public void setMimeType(final String mimeType) {
+        this.mimeType = mimeType;
+    }
+
+	public IThemeResourceService getThemeResourceService() {
+		return themeResourceService;
+	}
+
+	public void setThemeResourceService(IThemeResourceService themeResourceService) {
+		this.themeResourceService = themeResourceService;
+	}
+
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TableHolder.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TableHolder.java
new file mode 100644
index 0000000..f7bc2e9
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TableHolder.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Property;
+import com.vaadin.ui.UI;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author thomas
+ */
+public interface TableHolder extends Serializable {
+
+    List<Object> getPropIds();
+    boolean isHierarchical();
+    void setHierarchical(final boolean hierarchical);
+
+    Short getCellAlignment(Object propId);
+    boolean isGeneratedColumn(final Object propId) throws IllegalArgumentException;
+    Class<?> getPropertyTypeForGeneratedColumn(final Object propId) throws IllegalArgumentException;
+    Property getPropertyForGeneratedColumn(final Object propId, final Object rootItemId) throws
+            IllegalArgumentException;
+
+    // table delegated methods
+    boolean isColumnCollapsed(Object propertyId);
+    UI getUI();
+    String getColumnHeader(Object propertyId);
+    Container getContainerDataSource();
+    boolean isExportableFormattedProperty();
+    String getFormattedPropertyValue(Object rowId, Object colId, Property property);
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TemporaryFileDownloadResource.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TemporaryFileDownloadResource.java
new file mode 100644
index 0000000..295eef7
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/TemporaryFileDownloadResource.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright 2011-2019 Jonathan Nash and others
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ *  Contributors:                                                      
+ * 	   Jonathan Nash - initial implementation
+ * 	   Compex Systemhaus GmbH - adaptions for OSBP
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import com.vaadin.server.DownloadStream;
+import com.vaadin.server.StreamResource;
+import com.vaadin.ui.UI;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+/**
+ * The Class TemporaryFileDownloadResource.
+ * 
+ * Code obtained from: http://vaadin.com/forum/-/message_boards/view_message/159583
+ */
+public class TemporaryFileDownloadResource extends StreamResource {
+
+    /** The Constant serialVersionUID. */
+    private static final long serialVersionUID = 476307190141362413L;
+
+    /** The filename. */
+    private final String filename;
+
+    /** The content type. */
+    private String contentType;
+
+    /**
+     * Instantiates a new temporary file download resource.
+     * 
+     * @param application
+     *            the application
+     * @param fileName
+     *            the file name
+     * @param contentType
+     *            the content type
+     * @param tempFile
+     *            the temp file
+     * @throws FileNotFoundException
+     *             the file not found exception
+     */
+    public TemporaryFileDownloadResource(final UI application, final String fileName,
+            final String contentType, final File tempFile) throws FileNotFoundException {
+        super(new FileStreamResource(tempFile), fileName);
+        this.filename = fileName;
+        this.contentType = contentType;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.terminal.StreamResource#getStream()
+     */
+    @Override
+    public DownloadStream getStream() {
+        final DownloadStream stream =
+                new DownloadStream(getStreamSource().getStream(), contentType, filename);
+        stream.setParameter("Content-Disposition", "attachment;filename=" + filename);
+        // This magic incantation should prevent anyone from caching the data
+        stream.setParameter("Cache-Control", "private,no-cache,no-store");
+        // In theory <=0 disables caching. In practice Chrome, Safari (and, apparently, IE) all
+        // ignore <=0. Set to 1s
+        stream.setCacheTime(1000);
+        return stream;
+    }
+
+    /**
+     * The Class FileStreamResource.
+     */
+    private static class FileStreamResource implements StreamResource.StreamSource {
+
+        /** The Constant serialVersionUID. */
+        private static final long serialVersionUID = 3801605481686085335L;
+
+        /** The input stream.
+         *  Made it transient per: https://github.com/jnash67/tableexport-for-vaadin/issues/28
+         */
+        private final transient InputStream inputStream;
+
+        /**
+         * Instantiates a new file stream resource.
+         * 
+         * @param fileToDownload
+         *            the file to download
+         * @throws FileNotFoundException
+         *             the file not found exception
+         */
+        public FileStreamResource(final File fileToDownload) throws FileNotFoundException {
+            inputStream = new DeletingFileInputStream(fileToDownload);
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see com.vaadin.terminal.StreamResource.StreamSource#getStream()
+         */
+        @Override
+        public InputStream getStream() {
+            return inputStream;
+        }
+    }
+
+}
diff --git a/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/XLS2CSVmra.java b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/XLS2CSVmra.java
new file mode 100644
index 0000000..4815975
--- /dev/null
+++ b/org.eclipse.osbp.xtext.table.common/src/org/eclipse/osbp/xtext/table/common/export/XLS2CSVmra.java
@@ -0,0 +1,340 @@
+/*
+ * ====================================================================
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * 
+ * ====================================================================
+ */
+package org.eclipse.osbp.xtext.table.common.export;
+
+import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener;
+import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener;
+import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
+import org.apache.poi.hssf.eventusermodel.HSSFListener;
+import org.apache.poi.hssf.eventusermodel.HSSFRequest;
+import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener;
+import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord;
+import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
+import org.apache.poi.hssf.record.BOFRecord;
+import org.apache.poi.hssf.record.BlankRecord;
+import org.apache.poi.hssf.record.BoolErrRecord;
+import org.apache.poi.hssf.record.BoundSheetRecord;
+import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.LabelRecord;
+import org.apache.poi.hssf.record.LabelSSTRecord;
+import org.apache.poi.hssf.record.NoteRecord;
+import org.apache.poi.hssf.record.NumberRecord;
+import org.apache.poi.hssf.record.RKRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.SSTRecord;
+import org.apache.poi.hssf.record.StringRecord;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * A XLS -> CSV processor, that uses the MissingRecordAware EventModel code to ensure it outputs all
+ * columns and rows.
+ * 
+ * @author Nick Burch
+ */
+public class XLS2CSVmra implements HSSFListener, Serializable {
+    private int minColumns;
+    private POIFSFileSystem fs;
+    private PrintStream output;
+
+    private int lastRowNumber;
+    private int lastColumnNumber;
+
+    /** Should we output the formula, or the value it has? */
+    private boolean outputFormulaValues = true;
+
+    /** For parsing Formulas */
+    private SheetRecordCollectingListener workbookBuildingListener;
+    private HSSFWorkbook stubWorkbook;
+
+    // Records we pick up as we process
+    private SSTRecord sstRecord;
+    private FormatTrackingHSSFListener formatListener;
+
+    /** So we known which sheet we're on */
+    @SuppressWarnings("unused")
+    private int sheetIndex = -1;
+    private BoundSheetRecord[] orderedBSRs;
+    @SuppressWarnings("rawtypes")
+    private ArrayList boundSheetRecords = new ArrayList();
+
+    // For handling formulas with string results
+    private int nextRow;
+    private int nextColumn;
+    private boolean outputNextStringRecord;
+
+    /**
+     * Creates a new XLS -> CSV converter
+     * 
+     * @param fs
+     *            The POIFSFileSystem to process
+     * @param output
+     *            The PrintStream to output the CSV to
+     * @param minColumns
+     *            The minimum number of columns to output, or -1 for no minimum
+     */
+    public XLS2CSVmra(final POIFSFileSystem fs, final PrintStream output, final int minColumns) {
+        this.fs = fs;
+        this.output = output;
+        this.minColumns = minColumns;
+    }
+
+    /**
+     * Creates a new XLS -> CSV converter
+     * 
+     * @param filename
+     *            The file to process
+     * @param minColumns
+     *            The minimum number of columns to output, or -1 for no minimum
+     * @throws IOException
+     * @throws FileNotFoundException
+     */
+    public XLS2CSVmra(final String filename, final int minColumns) throws IOException,
+            FileNotFoundException {
+        this(new POIFSFileSystem(new FileInputStream(filename)), System.out, minColumns);
+    }
+
+    /**
+     * Initiates the processing of the XLS file to CSV
+     */
+    public void process() throws IOException {
+        final MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(this);
+        formatListener = new FormatTrackingHSSFListener(listener);
+
+        final HSSFEventFactory factory = new HSSFEventFactory();
+        final HSSFRequest request = new HSSFRequest();
+
+        if (outputFormulaValues) {
+            request.addListenerForAllRecords(formatListener);
+        } else {
+            workbookBuildingListener = new SheetRecordCollectingListener(formatListener);
+            request.addListenerForAllRecords(workbookBuildingListener);
+        }
+
+        factory.processWorkbookEvents(request, fs);
+    }
+
+    /**
+     * Main HSSFListener method, processes events, and outputs the CSV as the file is processed.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void processRecord(final Record record) {
+        int thisRow = -1;
+        int thisColumn = -1;
+        String thisStr = null;
+
+        switch (record.getSid()) {
+            case BoundSheetRecord.sid :
+                boundSheetRecords.add(record);
+                break;
+            case BOFRecord.sid :
+                final BOFRecord br = (BOFRecord) record;
+                if (br.getType() == BOFRecord.TYPE_WORKSHEET) {
+                    // Create sub workbook if required
+                    if ((workbookBuildingListener != null) && (stubWorkbook == null)) {
+                        stubWorkbook = workbookBuildingListener.getStubHSSFWorkbook();
+                    }
+
+                    // Output the worksheet name
+                    // Works by ordering the BSRs by the location of
+                    // their BOFRecords, and then knowing that we
+                    // process BOFRecords in byte offset order
+                    sheetIndex++;
+                    if (orderedBSRs == null) {
+                        orderedBSRs = BoundSheetRecord.orderByBofPosition(boundSheetRecords);
+                    }
+                    // uncomment the below if you want to output the worksheet name
+                    // output.println();
+                    // output.println(orderedBSRs[sheetIndex].getSheetname() + " [" + (sheetIndex +
+                    // 1)
+                    // + "]:");
+                }
+                break;
+
+            case SSTRecord.sid :
+                sstRecord = (SSTRecord) record;
+                break;
+
+            case BlankRecord.sid :
+                final BlankRecord brec = (BlankRecord) record;
+
+                thisRow = brec.getRow();
+                thisColumn = brec.getColumn();
+                thisStr = "";
+                break;
+            case BoolErrRecord.sid :
+                final BoolErrRecord berec = (BoolErrRecord) record;
+
+                thisRow = berec.getRow();
+                thisColumn = berec.getColumn();
+                thisStr = "";
+                break;
+
+            case FormulaRecord.sid :
+                final FormulaRecord frec = (FormulaRecord) record;
+
+                thisRow = frec.getRow();
+                thisColumn = frec.getColumn();
+
+                if (outputFormulaValues) {
+                    if (Double.isNaN(frec.getValue())) {
+                        // Formula result is a string
+                        // This is stored in the next record
+                        outputNextStringRecord = true;
+                        nextRow = frec.getRow();
+                        nextColumn = frec.getColumn();
+                    } else {
+                        thisStr = formatListener.formatNumberDateCell(frec);
+                    }
+                } else {
+                    thisStr =
+                            '"' + HSSFFormulaParser.toFormulaString(stubWorkbook,
+                                    frec.getParsedExpression()) + '"';
+                }
+                break;
+            case StringRecord.sid :
+                if (outputNextStringRecord) {
+                    // String for formula
+                    final StringRecord srec = (StringRecord) record;
+                    thisStr = srec.getString();
+                    thisRow = nextRow;
+                    thisColumn = nextColumn;
+                    outputNextStringRecord = false;
+                }
+                break;
+
+            case LabelRecord.sid :
+                final LabelRecord lrec = (LabelRecord) record;
+
+                thisRow = lrec.getRow();
+                thisColumn = lrec.getColumn();
+                thisStr = '"' + lrec.getValue() + '"';
+                break;
+            case LabelSSTRecord.sid :
+                final LabelSSTRecord lsrec = (LabelSSTRecord) record;
+
+                thisRow = lsrec.getRow();
+                thisColumn = lsrec.getColumn();
+                if (sstRecord == null) {
+                    thisStr = '"' + "(No SST Record, can't identify string)" + '"';
+                } else {
+                    thisStr = '"' + sstRecord.getString(lsrec.getSSTIndex()).toString() + '"';
+                }
+                break;
+            case NoteRecord.sid :
+                final NoteRecord nrec = (NoteRecord) record;
+
+                thisRow = nrec.getRow();
+                thisColumn = nrec.getColumn();
+                // TODO: Find object to match nrec.getShapeId()
+                thisStr = '"' + "(TODO)" + '"';
+                break;
+            case NumberRecord.sid :
+                final NumberRecord numrec = (NumberRecord) record;
+
+                thisRow = numrec.getRow();
+                thisColumn = numrec.getColumn();
+
+                // Format
+                thisStr = formatListener.formatNumberDateCell(numrec);
+                break;
+            case RKRecord.sid :
+                final RKRecord rkrec = (RKRecord) record;
+
+                thisRow = rkrec.getRow();
+                thisColumn = rkrec.getColumn();
+                thisStr = '"' + "(TODO)" + '"';
+                break;
+            default :
+                break;
+        }
+
+        // Handle new row
+        if ((thisRow != -1) && (thisRow != lastRowNumber)) {
+            lastColumnNumber = -1;
+        }
+
+        // Handle missing column
+        if (record instanceof MissingCellDummyRecord) {
+            final MissingCellDummyRecord mc = (MissingCellDummyRecord) record;
+            thisRow = mc.getRow();
+            thisColumn = mc.getColumn();
+            thisStr = "";
+        }
+
+        // If we got something to print out, do so
+        if (thisStr != null) {
+            if (thisColumn > 0) {
+                output.print(',');
+            }
+            output.print(thisStr);
+        }
+
+        // Update column and row count
+        if (thisRow > -1)
+            lastRowNumber = thisRow;
+        if (thisColumn > -1)
+            lastColumnNumber = thisColumn;
+
+        // Handle end of row
+        if (record instanceof LastCellOfRowDummyRecord) {
+            // Print out any missing commas if needed
+            if (minColumns > 0) {
+                // Columns are 0 based
+                if (lastColumnNumber == -1) {
+                    lastColumnNumber = 0;
+                }
+                for (int i = lastColumnNumber; i < (minColumns); i++) {
+                    output.print(',');
+                }
+            }
+
+            // We're onto a new row
+            lastColumnNumber = -1;
+
+            // End the row
+            output.println();
+        }
+    }
+
+    public static void main(final String[] args) throws Exception {
+        if (args.length < 1) {
+            System.err.println("Use:");
+            System.err.println("  XLS2CSVmra <xls file> [min columns]");
+            System.exit(1);
+        }
+
+        int minColumns = -1;
+        if (args.length >= 2) {
+            minColumns = Integer.parseInt(args[1]);
+        }
+
+        final XLS2CSVmra xls2csv = new XLS2CSVmra(args[0], minColumns);
+        xls2csv.process();
+    }
+}