001/* 002 * All rights reserved. This program and the accompanying materials 003 * are made available under the terms of the GNU Lesser General Public License 004 * (LGPL) version 2.1 which accompanies this distribution, and is available at 005 * http://www.gnu.org/licenses/lgpl.html 006 * 007 * This library is distributed in the hope that it will be useful, 008 * but WITHOUT ANY WARRANTY; without even the implied warranty of 009 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 010 * Lesser General Public License for more details. 011 * 012 * Contributors: 013 * Original file from org.jboss.seam.excel.ui.UICell.java in jboss-seam-excel 014 * Anahide Tchertchian 015 */ 016package org.nuxeo.ecm.platform.ui.web.component.seam; 017 018import java.io.IOException; 019import java.io.StringWriter; 020import java.text.DateFormat; 021import java.text.ParseException; 022import java.util.Locale; 023 024import javax.el.ELException; 025import javax.el.ValueExpression; 026import javax.faces.FacesException; 027import javax.faces.component.UIComponent; 028import javax.faces.context.FacesContext; 029import javax.faces.context.ResponseWriter; 030 031import org.apache.commons.lang.StringUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.jboss.seam.core.Interpolator; 035import org.jboss.seam.excel.ExcelWorkbookException; 036import org.jboss.seam.ui.util.JSF; 037import org.nuxeo.ecm.platform.ui.web.util.NXHtmlResponseWriter; 038 039import com.sun.faces.config.WebConfiguration; 040 041/** 042 * Override of Seam cell component to control HTML encoding of accents in excel, and to improve data type guessing when 043 * using dates or numbers. 044 * 045 * @since 5.5 046 */ 047public class UICellExcel extends org.jboss.seam.excel.ui.UICell { 048 049 private static final Log log = LogFactory.getLog(UICellExcel.class); 050 051 public static final String DEFAULT_CONTENT_TYPE = "text/html"; 052 053 public static final String DEFAULT_CHARACTER_ENCODING = "utf-8"; 054 055 // add field again as it's private in parent class 056 protected Object value; 057 058 /** 059 * Force type attribute, added here to ensure value expression resolution 060 */ 061 protected String forceType; 062 063 /** 064 * Style attribute, added here to ensure value expression resolution 065 */ 066 protected String style; 067 068 @Override 069 public Object getValue() { 070 Object theValue = valueOf("value", value); 071 if (theValue == null) { 072 try { 073 theValue = cmp2String(FacesContext.getCurrentInstance(), this); 074 String forceType = getForceType(); 075 if (forceType != null && !forceType.isEmpty()) { 076 theValue = convertStringToTargetType((String) theValue, forceType); 077 } 078 } catch (IOException e) { 079 String message = Interpolator.instance().interpolate("Could not render cell #0", getId()); 080 throw new ExcelWorkbookException(message, e); 081 } 082 } else { 083 theValue = theValue.toString(); 084 } 085 return theValue; 086 } 087 088 @Override 089 public void setValue(Object value) { 090 this.value = value; 091 } 092 093 /** 094 * Converts string value as returned by widget to the target type for an accurate cell format in the XLS/CSV export. 095 * <ul> 096 * <li>If force type is set to "number", convert value to a double (null if empty).</li> 097 * <li>If force type is set to "bool", convert value to a boolean (null if empty).</li> 098 * <li>If force type is set to "date", convert value to a date using most frequent date parsers using the short, 099 * medium, long and full formats and current locale, trying first with time information and after with only date 100 * information. Returns null if date is empty or could not be parsed.</li> 101 * </ul> 102 * 103 * @since 5.6 104 */ 105 protected Object convertStringToTargetType(String value, String forceType) { 106 if (CellType.number.name().equals(forceType)) { 107 if (StringUtils.isBlank(value)) { 108 return null; 109 } 110 return Double.valueOf(value); 111 } else if (CellType.date.name().equals(forceType)) { 112 if (StringUtils.isBlank(value)) { 113 return null; 114 } 115 Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale(); 116 int[] formats = { DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL }; 117 for (int format : formats) { 118 try { 119 return DateFormat.getDateTimeInstance(format, format, locale).parse(value); 120 } catch (ParseException e) { 121 // ignore 122 } 123 try { 124 return DateFormat.getDateInstance(format, locale).parse(value); 125 } catch (ParseException e) { 126 // ignore 127 } 128 } 129 log.warn("Could not convert value to a date instance: " + value); 130 return null; 131 } else if (CellType.bool.name().equals(forceType)) { 132 if (StringUtils.isBlank(value)) { 133 return null; 134 } 135 return Boolean.valueOf(value); 136 } 137 return value; 138 } 139 140 /** 141 * Helper method for rendering a component (usually on a facescontext with a caching reponsewriter) 142 * 143 * @param facesContext The faces context to render to 144 * @param component The component to render 145 * @return The textual representation of the component 146 * @throws IOException If the JSF helper class can't render the component 147 */ 148 public static String cmp2String(FacesContext facesContext, UIComponent component) throws IOException { 149 ResponseWriter oldResponseWriter = facesContext.getResponseWriter(); 150 String contentType = oldResponseWriter != null ? oldResponseWriter.getContentType() : DEFAULT_CONTENT_TYPE; 151 String characterEncoding = oldResponseWriter != null ? oldResponseWriter.getCharacterEncoding() 152 : DEFAULT_CHARACTER_ENCODING; 153 StringWriter cacheingWriter = new StringWriter(); 154 155 // XXX: create a response writer by hand, to control html escaping of 156 // iso characters 157 // take default values for these confs 158 Boolean scriptHiding = Boolean.FALSE; 159 Boolean scriptInAttributes = Boolean.TRUE; 160 // force escaping to true 161 WebConfiguration.DisableUnicodeEscaping escaping = WebConfiguration.DisableUnicodeEscaping.True; 162 ResponseWriter newResponseWriter = new NXHtmlResponseWriter(cacheingWriter, contentType, characterEncoding, 163 scriptHiding, scriptInAttributes, escaping); 164 // ResponseWriter newResponseWriter = renderKit.createResponseWriter( 165 // cacheingWriter, contentType, characterEncoding); 166 167 facesContext.setResponseWriter(newResponseWriter); 168 JSF.renderChild(facesContext, component); 169 if (oldResponseWriter != null) { 170 facesContext.setResponseWriter(oldResponseWriter); 171 } 172 cacheingWriter.flush(); 173 cacheingWriter.close(); 174 return cacheingWriter.toString(); 175 } 176 177 /** 178 * Returns the style attribute, used to format cells with a specific {@link #forceType}. Sample value for dates 179 * formatting: "xls-format-mask: #{nxu:basicDateFormatter()};". 180 * 181 * @since 5.6 182 */ 183 @Override 184 public String getStyle() { 185 if (style != null) { 186 return style; 187 } 188 ValueExpression ve = getValueExpression("style"); 189 if (ve != null) { 190 try { 191 return (String) ve.getValue(getFacesContext().getELContext()); 192 } catch (ELException e) { 193 throw new FacesException(e); 194 } 195 } else { 196 return null; 197 } 198 } 199 200 /** 201 * @since 5.6 202 */ 203 @Override 204 public void setStyle(String style) { 205 this.style = style; 206 } 207 208 /** 209 * Returns the force type attribute, used to force cell type to "date" or "number" for instance. 210 * 211 * @since 5.6 212 */ 213 public String getForceType() { 214 if (forceType != null) { 215 return forceType; 216 } 217 ValueExpression ve = getValueExpression("forceType"); 218 if (ve != null) { 219 try { 220 return (String) ve.getValue(getFacesContext().getELContext()); 221 } catch (ELException e) { 222 throw new FacesException(e); 223 } 224 } else { 225 return null; 226 } 227 } 228 229 /** 230 * @since 5.6 231 */ 232 public void setForceType(String forceType) { 233 this.forceType = forceType; 234 } 235 236 // state holder 237 238 @Override 239 public void restoreState(FacesContext context, Object state) { 240 Object[] values = (Object[]) state; 241 super.restoreState(context, values[0]); 242 forceType = (String) values[1]; 243 style = (String) values[2]; 244 } 245 246 @Override 247 public Object saveState(FacesContext context) { 248 return new Object[] { super.saveState(context), forceType, style }; 249 } 250 251}