Index: pom.xml
===================================================================
--- pom.xml (revision 15651)
+++ pom.xml (working copy)
@@ -1,5 +1,6 @@
-
-
+
+
org.geoserver
extension
@@ -12,6 +13,10 @@
jar
Excel Output Format
+
+ 3.7
+
+
codehaus
@@ -31,15 +36,25 @@
org.apache.poi
poi
- 3.2-FINAL
+ ${poi-version}
+ org.apache.poi
+ poi-ooxml
+ ${poi-version}
+
+
+ xerces
+ xercesImpl
+ 2.9.1
+
+
org.geoserver
wfs
${project.version}
tests
test
-
+
org.geoserver
main
@@ -58,5 +73,4 @@
test
-
-
+
\ No newline at end of file
Index: src/main/java/org/geoserver/wfs/response/ExcelOutputFormat.java
===================================================================
--- src/main/java/org/geoserver/wfs/response/ExcelOutputFormat.java (revision 15651)
+++ src/main/java/org/geoserver/wfs/response/ExcelOutputFormat.java (working copy)
@@ -4,6 +4,7 @@
*/
package org.geoserver.wfs.response;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Calendar;
@@ -16,11 +17,16 @@
import net.opengis.wfs.GetFeatureType;
import net.opengis.wfs.QueryType;
-import org.apache.poi.hssf.usermodel.HSSFCell;
-import org.apache.poi.hssf.usermodel.HSSFRichTextString;
-import org.apache.poi.hssf.usermodel.HSSFRow;
-import org.apache.poi.hssf.usermodel.HSSFSheet;
-import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.log4j.Logger;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CreationHelper;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.RichTextString;
+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.xssf.usermodel.XSSFWorkbook;
import org.geoserver.config.GeoServer;
import org.geoserver.ows.util.OwsUtils;
import org.geoserver.platform.Operation;
@@ -34,41 +40,45 @@
/**
- * WFS output format for a GetFeature operation in which the outputFormat is "excel".
+ * Abstract base class for Excel WFS output format
*
- * @author Sebastian Benthall, OpenGeo, seb@opengeo.org
+ * @author Sebastian Benthall, OpenGeo, seb@opengeo.org and Shane StClair, Axiom Consulting, shane@axiomalaska.com
*/
-public class ExcelOutputFormat extends WFSGetFeatureOutputFormat {
+public abstract class ExcelOutputFormat extends WFSGetFeatureOutputFormat {
+ private static Logger log = Logger.getLogger( ExcelOutputFormat.class );
+ protected static int CELL_CHAR_LIMIT = (int) Math.pow(2,15) - 1; //32,767
+ protected static String TRUNCATE_WARNING = "DATA TRUNCATED";
- static final int EXCELL_CELL_CHAR_LIMIT = (int) Math.pow(2,15) - 1; //32,767
- static final String STRING_LENGTH_WARNING = "DATA TRUNCATED (EXCEEDS EXCEL LIMIT)";
+ protected int rowLimit;
+ protected int colLimit;
+ protected String fileExtension;
+ protected String mimeType;
- public ExcelOutputFormat(GeoServer gs) {
- //this is the name of your output format, it is the string
- // that will be used when requesting the format in a
- // GEtFeature request:
- // ie ;.../geoserver/wfs?request=getfeature&outputFormat=myOutputFormat
- super(gs, "excel");
+ public ExcelOutputFormat(GeoServer gs, String formatName) {
+ super(gs, formatName);
}
+ protected abstract Workbook getNewWorkbook();
+
/**
- * @return "application/msexcel";
+ * @return mime type;
*/
@Override
public String getMimeType(Object value, Operation operation)
throws ServiceException {
- return "application/msexcel";
+ return mimeType;
}
@Override
public String[][] getHeaders(Object value, Operation operation) throws ServiceException {
GetFeatureType request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(),
GetFeatureType.class);
- String outputFileName = ((QName) ((QueryType) request.getQuery().get(0)).getTypeName().get(0))
- .getLocalPart();
- return (String[][]) new String[][] {
- { "Content-Disposition", "attachment; filename=" + outputFileName + ".xls" }
- };
+ String outputFileName = ((QName) ((QueryType) request.getQuery().get(0))
+ .getTypeName().get(0)).getLocalPart();
+ return (String[][]) new String[][] {{
+ "Content-Disposition"
+ ,"attachment; filename=" + outputFileName + "." + fileExtension
+ }};
}
/**
@@ -78,42 +88,56 @@
protected void write(FeatureCollectionType featureCollection,
OutputStream output, Operation getFeature) throws IOException,
ServiceException {
- //Create the workbook
- HSSFWorkbook wb = new HSSFWorkbook();
-
+ //Create the workbook
+ Workbook wb = getNewWorkbook();
+ CreationHelper helper = wb.getCreationHelper();
+ ExcelCellStyles styles = new ExcelCellStyles( wb );
+
for (Iterator it = featureCollection.getFeature().iterator(); it.hasNext();) {
SimpleFeatureCollection fc = (SimpleFeatureCollection) it.next();
-
+
// create the sheet for this feature collection
- HSSFSheet sheet = wb.createSheet(fc.getSchema().getTypeName());
-
+ Sheet sheet = wb.createSheet(fc.getSchema().getTypeName());
+
// write out the header
- HSSFRow header = sheet.createRow((short) 0);
-
+ Row header = sheet.createRow(0);
+
SimpleFeatureType ft = (SimpleFeatureType) fc.getSchema();
- HSSFCell cell;
-
+ Cell cell;
+
cell = header.createCell(0);
- cell.setCellValue(new HSSFRichTextString("FID"));
- for ( int i = 0; i < ft.getAttributeCount(); i++ ) {
- AttributeDescriptor ad = ft.getDescriptor(i);
-
+ cell.setCellValue( helper.createRichTextString("FID") );
+ for ( int i = 0; i < ft.getAttributeCount() && i < colLimit; i++ ) {
+ AttributeDescriptor ad = ft.getDescriptor(i);
cell = header.createCell(i+1);
- cell.setCellValue(new HSSFRichTextString(ad.getLocalName()));
+ cell.setCellValue( helper.createRichTextString( ad.getLocalName() ) );
+ cell.setCellStyle( styles.getHeaderStyle() );
}
-
+
// write out the features
SimpleFeatureIterator i = fc.features();
int r = 0; // row index
try {
- HSSFRow row;
+ Row row;
while( i.hasNext() ) {
r++; //start at 1, since header is at 0
- SimpleFeature f = i.next();
- row = sheet.createRow((short) r);
+
+ row = sheet.createRow(r);
cell = row.createCell(0);
- cell.setCellValue(new HSSFRichTextString(f.getID()));
- for ( int j = 0; j < f.getAttributeCount(); j++ ) {
+
+ if( r == ( rowLimit - 1 ) && i.hasNext() ){
+ // there are more features than rows available in this
+ // Excel format. write out a warning line and break
+ RichTextString rowWarning = helper.createRichTextString(
+ TRUNCATE_WARNING + ": ROWS " + r + " - " + fc.size() + " NOT SHOWN");
+ cell.setCellValue(rowWarning);
+ cell.setCellStyle( styles.getWarningStyle() );
+ break;
+ }
+
+ SimpleFeature f = i.next();
+ cell.setCellValue( helper.createRichTextString( f.getID() ) );
+ for ( int j = 0; j < f.getAttributeCount() && j < colLimit; j++ ) {
Object att = f.getAttribute( j );
if ( att != null ) {
cell = row.createCell(j+1);
@@ -121,8 +145,10 @@
cell.setCellValue(((Number) att).doubleValue());
} else if(att instanceof Date) {
cell.setCellValue((Date) att);
+ cell.setCellStyle( styles.getDateStyle() );
} else if(att instanceof Calendar) {
cell.setCellValue((Calendar) att);
+ cell.setCellStyle( styles.getDateStyle() );
} else if(att instanceof Boolean) {
cell.setCellValue((Boolean) att);
} else {
@@ -131,12 +157,14 @@
// if string length > excel cell limit, truncate it and warn the
// user, otherwise excel workbook will be corrupted
- if (stringVal.length() > EXCELL_CELL_CHAR_LIMIT) {
- stringVal = STRING_LENGTH_WARNING + " "
- + stringVal.substring(0, EXCELL_CELL_CHAR_LIMIT
- - STRING_LENGTH_WARNING.length() - 1);
+ if (stringVal.length() > CELL_CHAR_LIMIT) {
+ stringVal = TRUNCATE_WARNING + " "
+ + stringVal.substring(0, CELL_CHAR_LIMIT
+ - TRUNCATE_WARNING.length() - 1);
+ cell.setCellStyle( styles.getWarningStyle() );
}
- cell.setCellValue(new HSSFRichTextString(stringVal));
+ cell.setCellValue( helper.createRichTextString( stringVal ) );
+
}
}
}
@@ -145,7 +173,7 @@
fc.close( i );
}
}
-
+
//write to output
wb.write(output);
}
Index: src/main/java/org/geoserver/wfs/response/ExcelCellStyles.java
===================================================================
--- src/main/java/org/geoserver/wfs/response/ExcelCellStyles.java (revision 0)
+++ src/main/java/org/geoserver/wfs/response/ExcelCellStyles.java (revision 0)
@@ -0,0 +1,45 @@
+package org.geoserver.wfs.response;
+
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CreationHelper;
+import org.apache.poi.ss.usermodel.DataFormat;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.Workbook;
+
+public class ExcelCellStyles {
+ private CellStyle dateStyle;
+ private CellStyle headerStyle;
+ private CellStyle warningStyle;
+
+ public ExcelCellStyles( Workbook wb ){
+ CreationHelper helper = wb.getCreationHelper();
+ DataFormat fmt = helper.createDataFormat();
+
+ dateStyle = wb.createCellStyle();
+ dateStyle.setDataFormat( fmt.getFormat("yyyy-mm-dd hh:mm:ss") );
+
+ headerStyle = wb.createCellStyle();
+ Font headerFont = wb.createFont();
+ headerFont.setBoldweight( Font.BOLDWEIGHT_BOLD );
+ headerStyle.setFont( headerFont );
+
+ warningStyle = wb.createCellStyle();
+ Font warningFont = wb.createFont();
+ warningFont.setBoldweight( Font.BOLDWEIGHT_BOLD );
+ warningFont.setColor( Font.COLOR_RED );
+ warningStyle.setFont( warningFont );
+ }
+
+ public CellStyle getDateStyle() {
+ return dateStyle;
+ }
+ public CellStyle getHeaderStyle() {
+ return headerStyle;
+ }
+ public CellStyle getWarningStyle() {
+ return warningStyle;
+ }
+
+
+
+}
Index: src/main/java/org/geoserver/wfs/response/Excel97OutputFormat.java
===================================================================
--- src/main/java/org/geoserver/wfs/response/Excel97OutputFormat.java (revision 0)
+++ src/main/java/org/geoserver/wfs/response/Excel97OutputFormat.java (revision 0)
@@ -0,0 +1,37 @@
+package org.geoserver.wfs.response;
+
+import org.apache.log4j.Logger;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.geoserver.config.GeoServer;
+
+/**
+ * Excel 97-2003 WFS output format
+ *
+ * @author Shane StClair, Axiom Consulting, shane@axiomalaska.com
+ */
+public class Excel97OutputFormat extends ExcelOutputFormat {
+ private static Logger log = Logger.getLogger( Excel97OutputFormat.class );
+
+ /**
+ * Constructor setting the format type as "excel" in addition to file extension, mime type,
+ * and row and column limits
+ *
+ * @param gs
+ */
+ public Excel97OutputFormat(GeoServer gs) {
+ super(gs, "excel");
+ rowLimit = (int) Math.pow(2,16); //65,536
+ colLimit = (int) Math.pow(2,8); //256
+ fileExtension = "xls";
+ mimeType = "application/msexcel";
+ }
+
+ /**
+ * Returns a new HSSFWorkbook workbook
+ */
+ @Override
+ protected Workbook getNewWorkbook() {
+ return new HSSFWorkbook();
+ }
+}
Index: src/main/java/org/geoserver/wfs/response/SpreadsheetWriter.java
===================================================================
--- src/main/java/org/geoserver/wfs/response/SpreadsheetWriter.java (revision 0)
+++ src/main/java/org/geoserver/wfs/response/SpreadsheetWriter.java (revision 0)
@@ -0,0 +1,137 @@
+package org.geoserver.wfs.response;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.xml.utils.XMLChar;
+
+/**
+ * Spreadsheet writer to directly generate OOXML spreadsheets instead of builing them in the Apache POI
+ * object model, which eats up tons of memory.
+ * Taken from existing example by Yegor Kozlov
+ * @see Code by Yegor Kozlov
+ *
+ * @author Shane StClair, Axiom Consulting and Design, shane@axiomalaska.com
+ */
+public class SpreadsheetWriter {
+ private final Writer _out;
+ private int _rownum;
+ private String xmlEncoding = "UTF-8";
+
+ public SpreadsheetWriter(Writer out){
+ _out = out;
+ }
+
+ public SpreadsheetWriter(Writer out, String xmlEncoding){
+ _out = out;
+ this.xmlEncoding = xmlEncoding;
+ }
+
+ public void beginSheet() throws IOException {
+ _out.write("" +
+ "" );
+ _out.write("\n");
+ }
+
+ public void endSheet() throws IOException {
+ _out.write("");
+ _out.write("");
+ }
+
+ /**
+ * Insert a new row
+ *
+ * @param rownum 0-based row number
+ */
+ public void insertRow(int rownum) throws IOException {
+ _out.write("\n");
+ this._rownum = rownum;
+ }
+
+ /**
+ * Insert row end marker
+ */
+ public void endRow() throws IOException {
+ _out.write("
\n");
+ }
+
+ public void createCell(int columnIndex, String value, int styleIndex) throws IOException {
+ String ref = new CellReference(_rownum, columnIndex).formatAsString();
+ _out.write("");
+ _out.write(""+ santizeForXml( value )+"");
+ _out.write("");
+ }
+
+ public void createCell(int columnIndex, String value) throws IOException {
+ createCell(columnIndex, value, -1);
+ }
+
+ public void createCell(int columnIndex, double value, int styleIndex) throws IOException {
+ String ref = new CellReference(_rownum, columnIndex).formatAsString();
+ _out.write("");
+ _out.write(""+value+"");
+ _out.write("");
+ }
+
+ public void createCell(int columnIndex, double value) throws IOException {
+ createCell(columnIndex, value, -1);
+ }
+
+ public void createCell(int columnIndex, boolean value) throws IOException {
+ createCell( columnIndex, value, -1 );
+ }
+
+ public void createCell(int columnIndex, boolean value, int styleIndex ) throws IOException {
+ String ref = new CellReference(_rownum, columnIndex).formatAsString();
+ _out.write("");
+ _out.write("" + ( value ? 1 : 0 ) + "");
+ _out.write("");
+ }
+
+
+ public void createCell(int columnIndex, Calendar value, int styleIndex) throws IOException {
+ createCell(columnIndex, DateUtil.getExcelDate(value, false), styleIndex);
+ }
+
+ public void createCell(int columnIndex, Date value, int styleIndex) throws IOException {
+ createCell(columnIndex, DateUtil.getExcelDate(value, false), styleIndex);
+ }
+
+ private String cData( String str ){
+ return "";
+ }
+
+ private String santizeForXml( String str ){
+ StringBuilder strBuilder = new StringBuilder();
+
+ boolean stringHasSpecial = false;
+ for (int i = 0; i < str.length(); i++){
+ char c = str.charAt(i);
+ if( !XMLChar.isInvalid(c) ){
+ strBuilder.append( c );
+ stringHasSpecial = stringHasSpecial || charIsSpecial( c );
+ }
+ }
+
+ if( stringHasSpecial ){
+ return cData( strBuilder.toString() );
+ }
+
+ return strBuilder.toString();
+ }
+
+ private boolean charIsSpecial( char c ){
+ if( c == '&' || c == '<' || c == '>' ) return true;
+ return false;
+ }
+}
Index: src/main/java/org/geoserver/wfs/response/BigGridUtil.java
===================================================================
--- src/main/java/org/geoserver/wfs/response/BigGridUtil.java (revision 0)
+++ src/main/java/org/geoserver/wfs/response/BigGridUtil.java (revision 0)
@@ -0,0 +1,59 @@
+package org.geoserver.wfs.response;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Utility to facilitate workaround when writing large OOXML spreadsheets with Apache POI.
+ * Taken from existing example by Yegor Kozlov
+ * @see Code by Yegor Kozlov
+ *
+ * @author Shane StClair, Axiom Consulting and Design, shane@axiomalaska.com
+ */
+public class BigGridUtil {
+ /**
+ *
+ * @param zipfile the template file
+ * @param tmpfile the XML file with the sheet data
+ * @param entry the name of the sheet entry to substitute, e.g. xl/worksheets/sheet1.xml
+ * @param out the stream to write the result to
+ */
+ public static void substitute(File zipfile, File tmpfile, String entry, OutputStream out) throws IOException {
+ ZipFile zip = new ZipFile(zipfile);
+
+ ZipOutputStream zos = new ZipOutputStream(out);
+
+ @SuppressWarnings("unchecked")
+ Enumeration en = (Enumeration) zip.entries();
+ while (en.hasMoreElements()) {
+ ZipEntry ze = en.nextElement();
+ if(!ze.getName().equals(entry)){
+ zos.putNextEntry(new ZipEntry(ze.getName()));
+ InputStream is = zip.getInputStream(ze);
+ copyStream(is, zos);
+ is.close();
+ }
+ }
+ zos.putNextEntry(new ZipEntry(entry));
+ InputStream is = new FileInputStream(tmpfile);
+ copyStream(is, zos);
+ is.close();
+
+ zos.close();
+ }
+
+ public static void copyStream(InputStream in, OutputStream out) throws IOException {
+ byte[] chunk = new byte[1024];
+ int count;
+ while ((count = in.read(chunk)) >=0 ) {
+ out.write(chunk,0,count);
+ }
+ }
+}
Index: src/main/java/org/geoserver/wfs/response/Excel2007OutputFormat.java
===================================================================
--- src/main/java/org/geoserver/wfs/response/Excel2007OutputFormat.java (revision 0)
+++ src/main/java/org/geoserver/wfs/response/Excel2007OutputFormat.java (revision 0)
@@ -0,0 +1,202 @@
+package org.geoserver.wfs.response;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import net.opengis.wfs.FeatureCollectionType;
+
+import org.apache.log4j.Logger;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CreationHelper;
+import org.apache.poi.ss.usermodel.DataFormat;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.RichTextString;
+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.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.geoserver.config.GeoServer;
+import org.geoserver.platform.Operation;
+import org.geoserver.platform.ServiceException;
+import org.geoserver.wfs.WFSGetFeatureOutputFormat;
+import org.geotools.data.simple.SimpleFeatureCollection;
+import org.geotools.data.simple.SimpleFeatureIterator;
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+import org.opengis.feature.type.AttributeDescriptor;
+
+/**
+ * Excel 2007 WFS output format
+ *
+ * @author Shane StClair, Axiom Consulting, shane@axiomalaska.com
+ */
+public class Excel2007OutputFormat extends ExcelOutputFormat {
+ private static Logger log = Logger.getLogger( Excel2007OutputFormat.class );
+ private static final String XML_ENCODING = "UTF-8";
+
+ /**
+ * Constructor setting the format type as "excel2007" in addition to file extension, mime type,
+ * and row and column limits
+ *
+ * @param gs
+ */
+ public Excel2007OutputFormat(GeoServer gs) {
+ super(gs, "excel2007");
+ rowLimit = (int) Math.pow(2,20); //1,048,576
+ colLimit = (int) Math.pow(2,14); //16,384
+ fileExtension = "xlsx";
+ mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+ }
+
+ /**
+ * Returns a new XSSFWorkbook workbook
+ */
+ @Override
+ protected Workbook getNewWorkbook() {
+ return new XSSFWorkbook();
+ }
+
+
+ /**
+ * We override the write method of the ExcelOutputFormat base class here as a workaround.
+ * In a perfect world, we could use the same code for generating Excel 97 (binary) and
+ * Excel 2007/OOXML (xml based) files. In reality, generating OOXML spreadsheets with Apache POI
+ * uses a lot of memory overhead (3.5 GB for 80,000 rows in one test).
+ *
+ * For now, we use a workaround by Yegor Kozlov that creates a template workbook, writes
+ * rows direclty to an XML temp file, and then slices the temp file into the workbook.
+ *
+ * If POI's memory performance when creating OOXML spreadsheet improves in future versions, we
+ * should be able to just remove this override method to use the ss usermodel (ideal).
+ *
+ * @see Yegor Kozlov's code.
+ */
+ @Override
+ protected void write(FeatureCollectionType featureCollection,
+ OutputStream output, Operation getFeature) throws IOException,
+ ServiceException {
+ //Create the workbook
+ Workbook wb = getNewWorkbook();
+ CreationHelper helper = wb.getCreationHelper();
+ ExcelCellStyles styles = new ExcelCellStyles( wb );
+
+ Map sheetMap = new HashMap();
+
+ for (Iterator it = featureCollection.getFeature().iterator(); it.hasNext();) {
+ SimpleFeatureCollection fc = (SimpleFeatureCollection) it.next();
+
+ // create the sheet for this feature collection
+ Sheet sheet = wb.createSheet( fc.getSchema().getTypeName() );
+ String sheetName = ( (XSSFSheet) sheet ).getPackagePart().getPartName().getName();
+ File rawSheet = File.createTempFile( "Excel2007TempSheet", ".xml");
+ sheetMap.put( sheetName, rawSheet );
+
+ Writer rawWriter = new OutputStreamWriter(new FileOutputStream(rawSheet), XML_ENCODING);
+ SpreadsheetWriter sw = new SpreadsheetWriter( rawWriter, XML_ENCODING );
+ sw.beginSheet();
+
+ // write out the header
+ sw.insertRow(0);
+ int headerStyleIndex = styles.getHeaderStyle().getIndex();
+ SimpleFeatureType ft = (SimpleFeatureType) fc.getSchema();
+ sw.createCell(0, "FID");
+ for ( int i = 0; i < ft.getAttributeCount() && i < colLimit; i++ ) {
+ AttributeDescriptor ad = ft.getDescriptor(i);
+ sw.createCell( i + 1, ad.getLocalName(), headerStyleIndex );
+ }
+ sw.endRow();
+
+ // write out the features
+ SimpleFeatureIterator i = fc.features();
+ int r = 0; // row index
+ try {
+ while( i.hasNext() ) {
+ r++; //start at 1, since header is at 0
+ sw.insertRow( r );
+
+ if( r == ( rowLimit - 1 ) && i.hasNext() ){
+ // there are more features than rows available in this
+ // Excel format. write out a warning line and break
+ String rowWarning = TRUNCATE_WARNING + ": ROWS " + r
+ + " - " + fc.size() + " NOT SHOWN";
+ sw.createCell( 0, rowWarning );
+ sw.endRow();
+ break;
+ }
+
+ SimpleFeature f = i.next();
+ sw.createCell( 0, f.getID() );
+ for ( int j = 0; j < f.getAttributeCount() && j < colLimit; j++ ) {
+ Object att = f.getAttribute( j );
+ if ( att != null ) {
+ if(att instanceof Number) {
+ sw.createCell( j+1, ((Number) att).doubleValue() );
+ } else if(att instanceof Date) {
+ sw.createCell( j+1, (Date) att, styles.getDateStyle().getIndex() );
+ } else if(att instanceof Calendar) {
+ sw.createCell( j+1, (Calendar) att, styles.getDateStyle().getIndex() );
+ } else if(att instanceof Boolean) {
+ sw.createCell( j+1, (Boolean) att );
+ } else {
+ // ok, it seems we have no better way than dump it as a string
+ String stringVal = att.toString();
+
+ // if string length > excel cell limit, truncate it and warn the
+ // user, otherwise excel workbook will be corrupted
+ if (stringVal.length() > CELL_CHAR_LIMIT) {
+ stringVal = TRUNCATE_WARNING + " "
+ + stringVal.substring(0, CELL_CHAR_LIMIT
+ - TRUNCATE_WARNING.length() - 1);
+ sw.createCell( j+1, stringVal, styles.getWarningStyle().getIndex() );
+ } else {
+ sw.createCell( j+1, stringVal );
+ }
+ }
+ }
+ }
+ sw.endRow();
+ }
+ } finally {
+ fc.close( i );
+ }
+
+ sw.endSheet();
+ rawWriter.close();
+ }
+
+ //save the template
+ File template = File.createTempFile( "Excel2007TempTemplate", ".xlsx");
+ FileOutputStream os = new FileOutputStream( template );
+ wb.write(os);
+ os.close();
+
+ //swap out sheets
+ for( Entry sheetEntry : sheetMap.entrySet()) {
+ String sheetName = sheetEntry.getKey();
+ File tempXml = sheetEntry.getValue();
+ File newTemplate = File.createTempFile( "Excel2007TempTemplate", ".xlsx");
+ FileOutputStream out = new FileOutputStream( newTemplate );
+ BigGridUtil.substitute( template, tempXml, sheetName.substring(1), out);
+ out.close();
+ template = newTemplate;
+ }
+
+ //stream generated file to output
+ FileInputStream fis = new FileInputStream( template );
+ BigGridUtil.copyStream( fis, output );
+ fis.close();
+ }
+}
Index: src/main/resources/applicationContext.xml
===================================================================
--- src/main/resources/applicationContext.xml (revision 15651)
+++ src/main/resources/applicationContext.xml (working copy)
@@ -2,7 +2,10 @@
-
+
+
+
+
Index: src/test/java/org/geoserver/wfs/response/ExcelOutputFormatTest.java
===================================================================
--- src/test/java/org/geoserver/wfs/response/ExcelOutputFormatTest.java (revision 15651)
+++ src/test/java/org/geoserver/wfs/response/ExcelOutputFormatTest.java (working copy)
@@ -1,11 +1,18 @@
package org.geoserver.wfs.response;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileWriter;
import java.io.InputStream;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.geoserver.data.test.MockData;
import org.geoserver.wfs.WFSTestSupport;
import org.geotools.data.FeatureSource;
@@ -19,7 +26,7 @@
public class ExcelOutputFormatTest extends WFSTestSupport {
- public void testOutputFormat() throws Exception {
+ public void testExcel97OutputFormat() throws Exception {
// grab the real binary stream, avoiding mangling to due char conversion
MockHttpServletResponse resp = getAsServletResponse( "wfs?request=GetFeature&version=1.0.0&typeName=sf:PrimitiveGeoFeature&outputFormat=excel");
InputStream in = getBinaryInputStream(resp);
@@ -80,7 +87,68 @@
assertNull(cell);
}
- public void testMultipleFeatureTypes() throws Exception {
+ public void testExcel2007OutputFormat() throws Exception {
+ // grab the real binary stream, avoiding mangling to due char conversion
+ MockHttpServletResponse resp = getAsServletResponse( "wfs?request=GetFeature&version=1.0.0&typeName=sf:PrimitiveGeoFeature&outputFormat=excel2007");
+ InputStream in = getBinaryInputStream(resp);
+
+ // check the mime type
+ assertEquals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", resp.getContentType());
+
+ // check the content disposition
+ assertEquals("attachment; filename=PrimitiveGeoFeature.xlsx", resp.getHeader("Content-Disposition"));
+
+ // check we have the expected sheet
+ XSSFWorkbook wb = new XSSFWorkbook( in );
+ XSSFSheet sheet = wb.getSheet("PrimitiveGeoFeature");
+ assertNotNull(sheet);
+
+ FeatureSource fs = getFeatureSource( MockData.PRIMITIVEGEOFEATURE );
+
+ // check the number of rows in the output
+ final int feautureRows = fs.getCount(Query.ALL);
+ assertEquals(feautureRows + 1, sheet.getPhysicalNumberOfRows());
+
+ // check the header is what we expect
+ final SimpleFeatureType schema = (SimpleFeatureType) fs.getSchema();
+ final XSSFRow header = sheet.getRow(0);
+ assertEquals("FID", header.getCell(0).getRichStringCellValue().toString());
+ for (int i = 0; i < schema.getAttributeCount(); i++) {
+ assertEquals(schema.getDescriptor(i).getLocalName(), header.getCell(i+1).getRichStringCellValue().toString());
+ }
+
+ // check some selected values to see if the content and data type is the one
+ // we expect
+ FeatureIterator fi = fs.getFeatures().features();
+ SimpleFeature sf = (SimpleFeature) fi.next();
+ fi.close();
+
+ // ... a string cell
+ XSSFCell cell = sheet.getRow(1).getCell(1);
+ assertEquals(XSSFCell.CELL_TYPE_STRING, cell.getCellType());
+ assertEquals(sf.getAttribute(0), cell.getRichStringCellValue().toString());
+ // ... a geom cell
+ cell = sheet.getRow(1).getCell(4);
+ assertEquals(XSSFCell.CELL_TYPE_STRING, cell.getCellType());
+ assertEquals(sf.getAttribute(3).toString(), cell.getRichStringCellValue().toString());
+ // ... a number cell
+ cell = sheet.getRow(1).getCell(6);
+ assertEquals(XSSFCell.CELL_TYPE_NUMERIC, cell.getCellType());
+ assertEquals(((Number) sf.getAttribute(5)).doubleValue(), cell.getNumericCellValue());
+ // ... a date cell (they are mapped as numeric in xms?)
+ cell = sheet.getRow(1).getCell(10);
+ assertEquals(XSSFCell.CELL_TYPE_NUMERIC, cell.getCellType());
+ assertEquals(sf.getAttribute(9), cell.getDateCellValue());
+ // ... a boolean cell (they are mapped as numeric in xms?)
+ cell = sheet.getRow(1).getCell(12);
+ assertEquals(XSSFCell.CELL_TYPE_BOOLEAN, cell.getCellType());
+ assertEquals(sf.getAttribute(11), cell.getBooleanCellValue());
+ // ... an empty cell (original value is null -> no cell)
+ cell = sheet.getRow(1).getCell(3);
+ assertNull(cell);
+ }
+
+ public void testExcel97MultipleFeatureTypes() throws Exception {
// grab the real binary stream, avoiding mangling to due char conversion
MockHttpServletResponse resp = getAsServletResponse( "wfs?request=GetFeature&typeName=sf:PrimitiveGeoFeature,sf:GenericEntity&outputFormat=excel");
InputStream in = getBinaryInputStream(resp);
@@ -101,4 +169,26 @@
fs = getFeatureSource(MockData.GENERICENTITY);
assertEquals(fs.getCount(Query.ALL) + 1, sheet.getPhysicalNumberOfRows());
}
+
+ public void testExcel2007MultipleFeatureTypes() throws Exception {
+ // grab the real binary stream, avoiding mangling to due char conversion
+ MockHttpServletResponse resp = getAsServletResponse( "wfs?request=GetFeature&typeName=sf:PrimitiveGeoFeature,sf:GenericEntity&outputFormat=excel2007");
+ InputStream in = getBinaryInputStream(resp);
+
+ // check we have the expected sheets
+ XSSFWorkbook wb = new XSSFWorkbook(in);
+ XSSFSheet sheet = wb.getSheet("PrimitiveGeoFeature");
+ assertNotNull(sheet);
+
+ // check the number of rows in the output
+ FeatureSource fs = getFeatureSource(MockData.PRIMITIVEGEOFEATURE);
+ assertEquals(fs.getCount(Query.ALL) + 1, sheet.getPhysicalNumberOfRows());
+
+ sheet = wb.getSheet("GenericEntity");
+ assertNotNull(sheet);
+
+ // check the number of rows in the output
+ fs = getFeatureSource(MockData.GENERICENTITY);
+ assertEquals(fs.getCount(Query.ALL) + 1, sheet.getPhysicalNumberOfRows());
+ }
}