miércoles, 13 de junio de 2012


How to export to excel datatable with header / column group


Voy a intentar explicar como he resuelto el asunto de exportar una tabla con cabeceras agrupadas como las que tiene ICEfaces <ice:dataExport> o PrimeFaces, ya que su componente de exportación directamente no sirve para este tipo de cabeceras.

En principio yo divido el trabajo en dos partes, por una parte defino la cabecera agrupada en un fichero xml y a continuación le añado las filas de la tabla con los datos en si.

Tabla:

1.- Cabecera definida en un xml
2.-Lista con los datos de la tabla.


Mi fichero xml para la definición de la cabecera tendría un aspecto similar a este:

<?xml version="1.0" encoding="UTF-8"?>


<header>
< cabecera tipo="1">
< table id="1101" cols="8">
< tr>
< td align="center" styleClass="logo" width="20%"
firstCol="0" rowspan="3" colspan="3" titulo="logo"
media="/xmlhttp/images/logobd3.gif" heigth="77px" />
< td align="center" styleClass="LCAB" colspan="5"
firstCol="3" titulo="header.id_1101.Titulo" />
< /tr>
< tr>
< td align="center" colspan="2" styleClass="LN"
firstCol="3" titulo="header.id_1101.row_1" />
< td align="center" colspan="2" styleClass="LN"
firstCol="5" titulo="header.id_1101.row_2" />
< td align="center" rowspan="2" styleClass="LN"
firstCol="7" titulo="header.id_1101.row_3" />

< /tr>
< tr>

< td titulo="header.id_1101.col_1" />

< td titulo="header.id_1101.col_2" />

< td titulo="header.id_1101.col_3" />

< td titulo="header.id_1101.col_4" />
< /tr>
< /table>
< /cabecera>



ok, ahora que tenemos la definición de la cabecera tipo html de siempre, lo que hay que hacer es procesar el fichero. Yo utilizo DOM para procesarlo y JXL para generar el fichero excel, aprovecho así el componente outputresource de ICEfaces.

Me he creado un clase para hacer todo el trabajo, desde leer el fichero hasta generar el fichero  excel.

Hay parámetros que yo utilizo pero que a efectos de ejemplo, pueden sobrar.


public class ExcelExport {
private String mostrarBotonExcel;

private List < ArrayList < String > > tdatos = new ArrayList < ArrayList < String > > ();

private Nodo nodo;

private ResourceBean resourceBean;
// Contiene la hoja excel a mostrar. Es global
WritableWorkbook wb = null;

WritableSheet excelSheet = null;

Document document;



public ExcelExport() {
super();
creaRecurso();
}


public void creaRecurso(List> tdatos, Nodo nodo) {
this.tdatos = tdatos;
this.nodo = nodo;
parametros = true;

creaExcel();

try {
resourceBean = downloadFile("pdfFileName", wb);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}




private void creaExcel() {

if (parametros) {

HttpSession session = (HttpSession) FacesContext
.getCurrentInstance().getExternalContext().getSession(true);

String basePath = "/excel";
String archivo = "fichero.xls";
String archivoCreado = session.getServletContext().getRealPath(
basePath + "/" + archivo);
String inputFile = archivoCreado;
File file = new File(inputFile);
WorkbookSettings wbSettings = new WorkbookSettings();
wbSettings.setLocale(new Locale("es", "ES"));

try {
wb = Workbook.createWorkbook(file, wbSettings);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Creo la hoja del libro
wb.createSheet("excel", 0);
excelSheet = wb.getSheet(0);

try {

parseTest();

addData2Excel();

fila += filaExcel;
addInfo2Excel();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

try {
wb.write();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
wb.close();
} catch (WriteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mostrarBotonExcel = "display: block";
} else {
System.out.println(tdatos.size());

HttpSession session = (HttpSession) FacesContext
.getCurrentInstance().getExternalContext().getSession(true);

String basePath = "/excel";
String archivo = "fichero.xls";
String archivoCreado = session.getServletContext().getRealPath(
basePath + "/" + archivo);
String inputFile = archivoCreado;
File file = new File(inputFile);
WorkbookSettings wbSettings = new WorkbookSettings();
wbSettings.setLocale(new Locale("es", "ES"));

try {
wb = Workbook.createWorkbook(file, wbSettings);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Creo la hoja del libro
wb.createSheet("tabla_principal", 0);
excelSheet = wb.getSheet(0);

Date t0 = new java.util.Date();
try {
excelSheet.addCell(new Label(0, 0, "" + t0.getTime()));
} catch (RowsExceededException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (WriteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

try {
wb.write();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
wb.close();
} catch (WriteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

public ResourceBean downloadFile(String fileName, WritableWorkbook wb)
throws IOException, IllegalArgumentException {

// System.out.println("downloadFile1()");

Resource res = null;

ServletContext context = ((HttpSession) FacesContext
.getCurrentInstance().getExternalContext().getSession(false))
.getServletContext();
try {

// Aqui esta el asunto
res = new ByteArrayResource(toByteArray(new FileInputStream(
new File(context.getRealPath("/excel") + "/fichero.xls"))));
} catch (Exception e) {
System.out.println("error: " + e.getMessage());
}

return new ResourceBean(res, "fichero.xls", "fileDescription");

}

public static byte[] toByteArray(InputStream input) throws IOException {

ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int len = 0;

while ((len = input.read(buf)) > -1)
output.write(buf, 0, len);
return output.toByteArray();
}



}



El tema del outputResource de ICEfaces es que DEBE existir el recurso para que se pinte el botón en la página, por eso lo de los parámetros, la primera vez genero un fichero excel de test y sobre ese escribo lo que obtenga.

En el código anterior también está como leer el fichero de la aplicación. Yo utilizo un servidor Tomcat 5.0.16.


Bueno, y ahora veamos un poco la función parseTest que se encarga de leer el fichero xml y generar un objeto DOM, que luego recorro como un árbol.


private void parseTest() {


fila = 0;

HttpSession session = (HttpSession) FacesContext.getCurrentInstance()
.getExternalContext().getSession(true);
String basePath = session.getServletContext().getRealPath(
"/WEB-INF/resources");


 String fichero = "/" + "headerTest.xml";
basePath += fichero;


try {
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();

document = builder.parse(new File(basePath));
document.getDocumentElement().normalize();
} catch (SAXParseException spe) {
// Error generated by the parser
System.out.println("\n** Parsing error" + ", line "
+ spe.getLineNumber() + ", uri " + spe.getSystemId());
System.out.println(" " + spe.getMessage());
// Use the contained exception, if any
Exception x = spe;
if (spe.getException() != null)
x = spe.getException();
x.printStackTrace();
} catch (SAXException sxe) {
// Error generated by this application
// (or a parser-initialization error)
Exception x = sxe;
if (sxe.getException() != null)
x = sxe.getException();
x.printStackTrace();
} catch (ParserConfigurationException pce) {
// Parser with specified options can't be built
pce.printStackTrace();
} catch (IOException ioe) {
// I/O error
ioe.printStackTrace();
}

Application application = FacesContext.getCurrentInstance()
.getApplication();

// Here begins
Element rootElement = document.getDocumentElement();
if (rootElement != null) {
// System.out.println("rootElement != null");
} else {
System.out.println("rootElement == null");
}



// buildTree realiza todo el trabajo.



buildHeader(rootElement, nodo.getId(), id_header, "1");


}

ok, en rootElement tenemos ya la dichosa cabecera que llevo esperando 3 años para que la implementen los señores de ICEfaces y no ha habido manera.



private void buildHeader(Element rootElement, String id, String id_header,
String nTabla) {

addChildren((Node) rootElement, id, id_header, nTabla);
}

Y addChildren (los parámetros son los que yo utilizo, cada uno pues como más le guste).


private void addChildren(Node parentXMLElement, String id,
String id_header, String nTabla) {

System.out.println("addChildren");
HeaderRow headerRow;
UIColumn column;
HtmlOutputText someText;

boolean encontradoTipo = false;

NodeList childElements = parentXMLElement.getChildNodes();

for (int i = 0; i < childElements.getLength(); i++) {
Node childElement = childElements.item(i);

if (!(childElement instanceof Text || childElement instanceof Comment)) {
boolean tieneHijos = false;

if (childElement.hasChildNodes()) {
if (childElement.getNodeName().equalsIgnoreCase("cabecera")) {
// Preguntar por el tipo de cabecera y dependiendo de
// esta
// hacer una procesamiento diferente de las tablas
encontradoTipo = true;
tipoCabecera = buscaAtributo(childElement, "tipo");
}
if (childElement.getNodeName().equalsIgnoreCase("table")) {
switch (Integer.parseInt(tipoCabecera)) {
case 1:
// System.out.println("tipoCabecera: " + 1);
if (buscaAtributo(childElement, "id", id)) {

procesarTabla(childElement);

// Extraigo los parametros de la tabla que
// necesito
nColumnas = Integer.parseInt(buscaAtributo(
childElement, "cols"));

}
break;


}

}

} else {
if (childElement.getNodeName().equalsIgnoreCase("tr")) {

}
}
addChildren(childElement, id, id_header, nTabla);
}

}

}




A continuación dos funciones para buscar atributos y valores de los atributos en un nodo.

private boolean buscaAtributo(Node childElement, String Atributo,
String valorAtributo) {

boolean encontrado = false;
NamedNodeMap elementAttributes = childElement.getAttributes();
if (elementAttributes != null && elementAttributes.getLength() > 0) {
for (int i = 0; i < elementAttributes.getLength(); i++) {
Node attribute = elementAttributes.item(i);
if (attribute.getNodeName().equalsIgnoreCase(Atributo)) {
if (attribute.getNodeValue()
.equalsIgnoreCase(valorAtributo)) {

encontrado = true;
}
}
}
}
return (encontrado);
}

private String buscaAtributo(Node childElement, String Atributo) {
boolean encontrado = false;
String valorAtributo = "";
NamedNodeMap elementAttributes = childElement.getAttributes();
String nombreAtributo = "";
String treeNodeLabel = childElement.getNodeName();
if (elementAttributes != null && elementAttributes.getLength() > 0) {
for (int i = 0; i < elementAttributes.getLength(); i++) {
Node attribute = elementAttributes.item(i);
if (attribute.getNodeName().equalsIgnoreCase(Atributo)) {
valorAtributo = attribute.getNodeValue();
encontrado = true;

}
}
}
return (valorAtributo);
}




Y finalmente el asunto del excel. Yo utilizo las regiones de jxl para crear celdas combinadas como en una cabecera agrupada y funciona bastante bien. De esta manera puedo general cualquier cabecera combinada por muy grande y complicada que sea, siempre, de forma automática.


private void procesarTabla(Node parentXMLElement) {

boolean image = false;
int firstRowR = 0, lastRowR = 0, firstColR = 0, lastColR = 0;
int iFirstCol = 0;
String SCColor = "";

NodeList childElements = parentXMLElement.getChildNodes();
for (int i = 0; i < childElements.getLength(); i++) {
Node childElement = childElements.item(i);
if (childElement.getNodeName().equalsIgnoreCase("tr")) {

firstRowR = fila;
fila++;

NodeList childElements2 = childElement.getChildNodes();
// Procesamos las columnas
for (int j = 0; j < childElements2.getLength(); j++) {
Node childElement2 = childElements2.item(j);
if (childElement2.getNodeName().equalsIgnoreCase("td")) {

// Obtenemos los atributos
// El primero es la columna de inicio
String firstCol = buscaAtributo(childElement2,
"firstCol");
if (!firstCol.equals("")) {

firstColR = Integer.parseInt(firstCol);
iFirstCol = Integer.parseInt(firstCol);

}

String rowspan = buscaAtributo(childElement2, "rowspan");
if (!rowspan.equals("")) {
lastRowR = firstRowR + Integer.parseInt(rowspan)
- 1;
} else {
lastRowR = firstRowR;
}

String colspan = buscaAtributo(childElement2, "colspan");
if (!colspan.equals("")) {

lastColR = iFirstCol + Integer.parseInt(colspan)
- 1;


} else {
lastColR = iFirstCol;
}
String align = buscaAtributo(childElement2, "align");
if (!(align.equals(""))) {

}
String styleClass = buscaAtributo(childElement2,
"styleClass");
if (!styleClass.equals("")) {
SCColor = getColor(styleClass);
} else {
SCColor = "";
}
String titulo = buscaAtributo(childElement2, "titulo");
if (!(titulo.equals(""))) {

}


try {
WritableFont cabeceraFont = new WritableFont(
WritableFont.ARIAL, 10, WritableFont.BOLD,
true);

WritableCellFormat fCabecera = new WritableCellFormat(
cabeceraFont);
fCabecera.setAlignment(Alignment.CENTRE);
fCabecera
.setVerticalAlignment(VerticalAlignment.CENTRE);

if (!(SCColor.equalsIgnoreCase(""))) {

fCabecera.setBackground(Colour.AQUA);
}

fCabecera.setBorder(Border.ALL,
BorderLineStyle.THIN);

if (titulo.equalsIgnoreCase("logo")) {
String media = buscaAtributo(childElement2,
"media");

HttpSession session = (HttpSession) FacesContext
.getCurrentInstance()
.getExternalContext().getSession(true);

String archivoCreado = session
.getServletContext().getRealPath(
"/" + media);

String inputFile = archivoCreado;
// Meter una imagen
WritableImage wi = new WritableImage(firstColR,
firstRowR, lastColR - firstColR + 1,
lastRowR - firstRowR + 1, new File(
archivoCreado));
excelSheet.mergeCells(firstColR, firstRowR,
lastColR, lastRowR);
excelSheet.addImage(wi);
image = true;
}

if (image == false) {

excelSheet.mergeCells(firstColR, firstRowR,
lastColR, lastRowR);
Label miTexto = new Label(firstColR, firstRowR,
MessageBundleLoader.getMessage(titulo),
fCabecera);
excelSheet.addCell(miTexto);
}
image = false;

} catch (RowsExceededException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (WriteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

}
}

}


Botón para generar el fichero

<ice:commandButton id="BLImgExcel"
rendered="#{ miBean .mostrarBotonExcel}"
styleClass="boton-submit"
image="/xmlhttp/#{localeBean.images}/submit-naranja.gif"
actionListener="#{miBean.beforeSubmitExcel}"
action="#{metodos.submitExcel}" />


Falta el componente que pinta el fichero.

<div id="idExcel" style="#{excelExport.mostrarBotonExcel}"><ice:outputResource
label="#{msgs['generar.fichero.excel']}"
resource="#{excelExport.resourceBean.resource}"
mimeType="application/vnd.ms-excel"
image="./xmlhttp/images/excel_icon.png"
fileName="#{excelExport.resourceBean.fileName}" attachment="true"
shared="false" /></div>

y esto es todo, aseguro que funciona.

La putada del asunto es que tengo que meter en las definiciones de las cabeceras el parámetro firstCol para indicar donde comienza la celda, pero por lo demás va como la seda. Tengo alrededor de 500 cabeceras diferentes definidas en varios ficheros xml y en principio, genial.

Espero que le sirva a alguien.

Salu2








No hay comentarios:

Publicar un comentario