Me fue útil el siguiente enlace, una guía muy completa: Unicode - How to get the characters right?
Comunicación BD Oracle y servidor
Por supuesto la BD debe utilizar una codificación correcta que permita guardar este símbolo. Damos por hecho que se puede hacer un update en un campo de texto con un € y se guarda correctamente. Si al hacer consulta de este campo desde una aplicación recibimos carácteres especiales pero no el € verifica el driver Oracle utilizado para la conexión. Por ejemplo para una BD Oracle 11g con un drive 10g no se notará problemas.. excepto detalles como no poder recibir este símbolo.Comunicación cliente http y servidor
Para empezar recomendamos utilizar siempre, indicándolo, UTF-8 para la codificación de ficheros y comunicación. Por ejemplo en un JSP<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> .. <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
Pero esto no es suficiente por ejemplo para el escurridizo €. Es recomendable tener un filtro que nos codifique correctamente las peticiones. Si tenemos la suerte de utilizar facelets en nuestro proyecto este se encargará de realizarlo automáticamente. En caso contrario podemos hacer nuestro propio filtro.
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class CharacterEncodingFilter implements Filter { @Override public void init(FilterConfig chain) throws ServletException { } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); chain.doFilter(request, response); } }Para registrarlo en el wmb.xml
<filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>Utiles.CharacterEncodingFilter</filter-class> </filter>
Comunicación cliente y servidor por Servlet
Aún con el filtro descrito en el punto anterior hay casos que se nos escapan. Pongo como ejemplo el uso de servlet para subida y descarga de ficheros, para poder utilizar € en el nombre del fichero.Subida del fichero:
ServletMultipartRequest parser = new ServletMultipartRequest(request, trewa.servlet.upload.MultipartRequest.MAX_READ_BYTES, 101, null); Enumeration<String> e = parser.getFileParameterNames(); String name = e.nextElement(); InputStream in = parser.getFileContents(name); String strD_MIME = parser.getContentType(name); String strD_NOMBRE_ARCHIVO = new String((parser.getBaseFilename(name)) .getBytes("iso-8859-1"), "UTF-8");
Bajada del fichero:
String nombreFichero = URLEncoder.encode(rs.getString("INFDOC_NOMBRE_FICHERO"), "UTF-8").replace("+", "%20"); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=" + nombreFichero);
Exportación de displaytag
La exportación de displaytag utiliza como charset de codificación el mismo definido en la página JSP en la que se incluye la tabla. Con charset como ISO o CP1252 funciona correctamente esta exportación respecto a carácteres especiales. Pero en el caso recomendable de que utilicemos UTF-8 la exportación a csv y excel no será correcta (pdf sí). Para corregirlo se ha probado con éxito la solución vista en este hilo: http://appfuse.547863.n4.nabble.com/encoding-problem-when-exporting-to-excel-td553943.htmlComo se explica en el mismo se ha modificado la librería displaytag.
Modificar la clase ExportDelegate entre las líneas 112 y 124 para añadir un if.
String characterEncoding = wrapper.getCharacterEncoding(); String wrappedContentType = wrapper.getContentType(); // charset is already specified (see #921811) if (wrappedContentType != null && wrappedContentType.indexOf("charset") > -1) { characterEncoding = StringUtils.substringAfter(wrappedContentType, "charset="); } + //the target encoding is already specified in contentType : + if( contentType.indexOf("charset") > -1) { + characterEncoding=StringUtils.substringAfter(contentType, "charset="); + contentType = contentType.substring(0, contentType.indexOf(';')).trim(); + } //$NON-NLS-1$ if (characterEncoding != null && contentType.indexOf("charset") == -1) { contentType += "; charset=" + characterEncoding; //$NON-NLS-1$ }Añadidas dos nuevas clases en displaytag.src.main.java.org.displaytag.export
ExcelCSVView para las exportaciones a CSV, tal como se describe en el foro.
package org.displaytag.export;
import java.io.IOException; import java.io.Writer; import javax.servlet.jsp.JspException; import org.apache.commons.lang.StringUtils; public class ExcelCSVView extends CsvView { public String getRowEnd() { return "\r\n"; //Windows end of line } public String getCellEnd() { return ";"; //Excel default cell separator (for french) } public String getMimeType() { return "text/csv; charset=cp1252"; //uses the Windows Latin-1 superset encoding } protected String escapeColumnValue(Object value) { String stringValue = StringUtils.trim(value.toString()); if (!StringUtils.containsNone(stringValue, new char[]{'\n', ';' })) { //double '"' as escape sequence for included quote symbols return "\"" + StringUtils.replace(stringValue, "\"", "\"\"") + "\""; } return stringValue; } }
La clase ExcelExcelView con idea de sustituir el uso de ExcelView. Es la misma clase ExcelView modificando unicamente el método getMimeType para ponerlo igual que en ExcelCSVView. Estamos poniendo la codificación que funciona junto con el tipo CSV, pero no importa porque excel sabrá entender el fichero y lo interpretará correctamente. Si el tipo hubiese sido application/vnd.ms-excel; CP1252 no codifica bien, por eso utilizamos text/csv; charset=cp1252. Unicamente modificamos una línea respecto a ExcelView, pero si lo hacemos en esa misma clase al compilar nos dará error por no superar las pruebas.
/** * Licensed under the Artistic License; you may not use this file * except in compliance with the License. * You may obtain a copy of the License at * * http://displaytag.sourceforge.net/license.html * * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ package org.displaytag.export; import org.apache.commons.lang.StringUtils; import org.displaytag.model.TableModel; /** * Export view for excel exporting. * @author Fabrizio Giustina * @version $Revision$ ($Author$) */ public class ExcelExcelView extends BaseExportView { /** * @see org.displaytag.export.BaseExportView#setParameters(TableModel, boolean, boolean, boolean) */ public void setParameters(TableModel tableModel, boolean exportFullList, boolean includeHeader, boolean decorateValues) { super.setParameters(tableModel, exportFullList, includeHeader, decorateValues); } /** * @see org.displaytag.export.ExportView#getMimeType() * @return "application/vnd.ms-excel" */ public String getMimeType() { return "text/csv; charset=cp1252"; //$NON-NLS-1$ } /** * @see org.displaytag.export.BaseExportView#getRowEnd() */ protected String getRowEnd() { return "\n"; //$NON-NLS-1$ } /** * @see org.displaytag.export.BaseExportView#getCellEnd() */ protected String getCellEnd() { return "\t"; //$NON-NLS-1$ } /** * @see org.displaytag.export.BaseExportView#getAlwaysAppendCellEnd() * @return false */ protected boolean getAlwaysAppendCellEnd() { return false; } /** * @see org.displaytag.export.BaseExportView#getAlwaysAppendRowEnd() * @return false */ protected boolean getAlwaysAppendRowEnd() { return false; } /** * Escaping for excel format. * <ul> * <li>Quotes inside quoted strings are escaped with a double quote</li> * <li>Fields are surrounded by " (should be optional, but sometimes you get a "Sylk error" * without those)</li> </ul> * @see org.displaytag.export.BaseExportView#escapeColumnValue(java.lang.Object) */ protected String escapeColumnValue(Object value) { if (value != null) { // quotes around fields are needed to avoid occasional "Sylk format invalid" messages from excel return "\"" //$NON-NLS-1$ + StringUtils.replace(StringUtils.trim(value.toString()), "\"", "\"\"") //$NON-NLS-1$ //$NON-NLS-2$ + "\""; //$NON-NLS-1$ } return null; } }
Finalmente los proyectos que quieran beneficiarse y poder exportar correctamente los CSV y Excel aunque sus JSP utilicen charset=UTF-8 deberán modificar el displaytag.properties y añadir las siguientes líneas para indicar que se utilicen las dos clases añadidas en las correspondientes exportaciones de estos formatos.
export.csv.class=org.displaytag.export.ExcelCSVView export.excel.class=org.displaytag.export.ExcelExcelView