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()); + } }