/*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* Original file from org.jboss.seam.excel.ui.UICell.java in jboss-seam-excel
* Anahide Tchertchian
*/
package org.nuxeo.ecm.platform.ui.web.component.seam;
import java.io.IOException;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Locale;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.core.Interpolator;
import org.jboss.seam.excel.ExcelWorkbookException;
import org.jboss.seam.ui.util.JSF;
import org.nuxeo.ecm.platform.ui.web.util.NXHtmlResponseWriter;
import com.sun.faces.config.WebConfiguration;
/**
* Override of Seam cell component to control HTML encoding of accents in
* excel, and to improve data type guessing when using dates or numbers.
*
* @since 5.5
*/
public class UICellExcel extends org.jboss.seam.excel.ui.UICell {
private static final Log log = LogFactory.getLog(UICellExcel.class);
public static final String DEFAULT_CONTENT_TYPE = "text/html";
public static final String DEFAULT_CHARACTER_ENCODING = "utf-8";
// add field again as it's private in parent class
protected Object value;
/**
* Force type attribute, added here to ensure value expression resolution
*/
protected String forceType;
/**
* Style attribute, added here to ensure value expression resolution
*/
protected String style;
public Object getValue() {
Object theValue = valueOf("value", value);
if (theValue == null) {
try {
theValue = cmp2String(FacesContext.getCurrentInstance(), this);
String forceType = getForceType();
if (forceType != null && !forceType.isEmpty()) {
theValue = convertStringToTargetType((String) theValue,
forceType);
}
} catch (IOException e) {
String message = Interpolator.instance().interpolate(
"Could not render cell #0", getId());
throw new ExcelWorkbookException(message, e);
}
} else {
theValue = theValue.toString();
}
return theValue;
}
public void setValue(Object value) {
this.value = value;
}
/**
* Converts string value as returned by widget to the target type for an
* accurate cell format in the XLS/CSV export.
* <ul>
* <li>If force type is set to "number", convert value to a double (null if
* empty).</li>
* <li>If force type is set to "bool", convert value to a boolean (null if
* empty).</li>
* <li>If force type is set to "date", convert value to a date using most
* frequent date parsers using the short, medium, long and full formats and
* current locale, trying first with time information and after with only
* date information. Returns null if date is empty or could not be parsed.</li>
* </ul>
*
* @since 5.6
*/
protected Object convertStringToTargetType(String value, String forceType) {
if (CellType.number.name().equals(forceType)) {
if (StringUtils.isBlank(value)) {
return null;
}
return Double.valueOf(value);
} else if (CellType.date.name().equals(forceType)) {
if (StringUtils.isBlank(value)) {
return null;
}
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
int[] formats = { DateFormat.SHORT, DateFormat.MEDIUM,
DateFormat.LONG, DateFormat.FULL };
for (int format : formats) {
try {
return DateFormat.getDateTimeInstance(format, format,
locale).parse(value);
} catch (ParseException e) {
// ignore
}
try {
return DateFormat.getDateInstance(format, locale).parse(
value);
} catch (ParseException e) {
// ignore
}
}
log.warn("Could not convert value to a date instance: " + value);
return null;
} else if (CellType.bool.name().equals(forceType)) {
if (StringUtils.isBlank(value)) {
return null;
}
return Boolean.valueOf(value);
}
return value;
}
/**
* Helper method for rendering a component (usually on a facescontext with
* a caching reponsewriter)
*
* @param facesContext The faces context to render to
* @param component The component to render
* @return The textual representation of the component
* @throws IOException If the JSF helper class can't render the component
*/
public static String cmp2String(FacesContext facesContext,
UIComponent component) throws IOException {
ResponseWriter oldResponseWriter = facesContext.getResponseWriter();
String contentType = oldResponseWriter != null ? oldResponseWriter.getContentType()
: DEFAULT_CONTENT_TYPE;
String characterEncoding = oldResponseWriter != null ? oldResponseWriter.getCharacterEncoding()
: DEFAULT_CHARACTER_ENCODING;
StringWriter cacheingWriter = new StringWriter();
// XXX: create a response writer by hand, to control html escaping of
// iso characters
// take default values for these confs
Boolean scriptHiding = Boolean.FALSE;
Boolean scriptInAttributes = Boolean.TRUE;
// force escaping to true
WebConfiguration.DisableUnicodeEscaping escaping = WebConfiguration.DisableUnicodeEscaping.True;
ResponseWriter newResponseWriter = new NXHtmlResponseWriter(
cacheingWriter, contentType, characterEncoding, scriptHiding,
scriptInAttributes, escaping);
// ResponseWriter newResponseWriter = renderKit.createResponseWriter(
// cacheingWriter, contentType, characterEncoding);
facesContext.setResponseWriter(newResponseWriter);
JSF.renderChild(facesContext, component);
if (oldResponseWriter != null) {
facesContext.setResponseWriter(oldResponseWriter);
}
cacheingWriter.flush();
cacheingWriter.close();
return cacheingWriter.toString();
}
/**
* Returns the style attribute, used to format cells with a specific
* {@link #forceType}. Sample value for dates formatting: "xls-format-mask:
* #{nxu:basicDateFormatter()};".
*
* @since 5.6
*/
public String getStyle() {
if (style != null) {
return style;
}
ValueExpression ve = getValueExpression("style");
if (ve != null) {
try {
return (String) ve.getValue(getFacesContext().getELContext());
} catch (ELException e) {
throw new FacesException(e);
}
} else {
return null;
}
}
/**
* @since 5.6
*/
public void setStyle(String style) {
this.style = style;
}
/**
* Returns the force type attribute, used to force cell type to "date" or
* "number" for instance.
*
* @since 5.6
*/
public String getForceType() {
if (forceType != null) {
return forceType;
}
ValueExpression ve = getValueExpression("forceType");
if (ve != null) {
try {
return (String) ve.getValue(getFacesContext().getELContext());
} catch (ELException e) {
throw new FacesException(e);
}
} else {
return null;
}
}
/**
* @since 5.6
*/
public void setForceType(String forceType) {
this.forceType = forceType;
}
// state holder
@Override
public void restoreState(FacesContext context, Object state) {
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
forceType = (String) values[1];
style = (String) values[2];
}
@Override
public Object saveState(FacesContext context) {
return new Object[] { super.saveState(context), forceType, style };
}
}