Cell.java
/*
* Copyright 2012 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.odftoolkit.odfdom.changes;
import static org.odftoolkit.odfdom.changes.JsonOperationConsumer.addParagraph;
import static org.odftoolkit.odfdom.changes.JsonOperationConsumer.addStyle;
import static org.odftoolkit.odfdom.changes.JsonOperationConsumer.addText;
import static org.odftoolkit.odfdom.changes.OperationConstants.OPK_STYLE_ID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.element.number.DataStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberBooleanStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberTextStyleElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
import org.odftoolkit.odfdom.dom.element.text.TextAElement;
import org.odftoolkit.odfdom.dom.element.text.TextPElement;
import org.odftoolkit.odfdom.dom.element.text.TextParagraphElementBase;
import org.odftoolkit.odfdom.dom.style.props.OdfStylePropertiesSet;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberCurrencyStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberDateStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberPercentageStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberTimeStyle;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* A MultiCoomponent uses a single XML element to represent multiple components. This container can
* be used for spreadsheet row and cell components using repeated elements via an attribute.
*
* @author svante.schubertATgmail.com
* @param <T>
*/
class Cell<T> extends Component {
private static final String FORMULA_PREFIX = "of:";
public Cell(OdfElement componentElement, Component parent) {
super(componentElement, parent);
}
/**
* A multiple components can be represented by a single XML element
*
* @return the number of components the elements represents
*/
@Override
public int repetition() {
return mRootElement.getRepetition();
}
// CELL ONLY
// Map<String, Object> mInnerCellStyle = null;
//
// /** The inner style of a cell will be temporary saved at the cell.
// Whenever the cell content is deleted, the style is being merged/applied to the cell style */
// public Map<String, Object> getInternalCellStyle(){
// return mInnerCellStyle;
// }
//
//
// /** The inner style of a cell will be temporary saved at the cell.
// Whenever the cell content is deleted, the style is being merged/applied to the cell style */
// public void setInternalCellStyle(Map<String, Object> newStyles){
// mInnerCellStyle = newStyles;
// }
//
/** Adds the given component to the root element */
@Override
public void addChild(int index, Component c) {
mRootElement.insert(c.getRootElement(), index);
// 2DO: Svante: ARE THE ABOVE AND THE BELOW EQUIVALENT?
// OdfElement rootElement = c.getRootElement();
// if (index >= 0) {
// mRootElement.insertBefore(rootElement, ((OdfElement) mRootElement).receiveNode(index));
// } else {
// mRootElement.appendChild(rootElement);
// }
}
/** @return either a text node of size 1 or an element being the root element of a component */
@Override
public Node getChildNode(int index) {
return mRootElement.receiveNode(index);
}
/**
* Removes a component from the text element container. Removes either an element representing a
* component or text node of size 1
*/
@Override
public Node remove(int index) {
Node removedNode = null;
Node node = this.getChildNode(index);
if (node != null) {
removedNode = mRootElement.removeChild(node);
}
return removedNode;
}
/**
* All children of the root element will be traversed. If it is a text node the size is added, if
* it is an element and a component a size of one is added, if it is a marker, for known text
* marker elements (text:span, text:bookmark) the children are recursive checked
*
* @return the number of child components
*/
@Override
public int size() {
return mRootElement.componentSize();
}
private static final String FLOAT = "float";
private static final String STRING = "string";
private static final String CURRENCY = "currency";
private static final String DATE = "date";
private static final String TIME = "time";
private static final String PERCENTAGE = "percentage";
private static final String BOOLEAN = "boolean";
/**
* Adding cell content: either as formula or paragraph and text content, latter with default
* styles
*/
public TableTableCellElement addCellStyleAndContent(
Component rootComponent, Object value, JSONObject attrs) {
// see if the cell is repeated
TableTableCellElement cell = (TableTableCellElement) this.getRootElement();
OdfFileDom ownerDoc = (OdfFileDom) rootComponent.getOwnerDocument();
// save the URL as everyting else will be deleted
String url = reuseCellHyperlink(cell, attrs);
boolean setValueType = true;
boolean isNumberValue = true;
// exchanges the content if requested
if (value != null) {
cell.removeContent();
// if there is new content..
if (!value.equals(JSONObject.NULL)) {
String valueString = value.toString();
if (valueString.startsWith(Constants.EQUATION)) {
// How am I able to set the other values? What is the OOXML solution for this?
cell.setAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "office:value-type", FLOAT);
cell.setAttributeNS(
OdfDocumentNamespace.TABLE.getUri(),
"table:formula",
FORMULA_PREFIX.concat(valueString));
} else {
// insert a paragraph to store the text within..
TextParagraphElementBase newParagraph = addParagraph(this, 0, attrs);
if (url != null) {
attrs = addUrlToCharacterProps(attrs, url);
}
// if the formula was masked
if (value instanceof String
&& valueString.startsWith(Constants.APOSTROPHE_AND_EQUATION)) {
// cut the first apostrophe
valueString = valueString.substring(1);
}
// addChild the text & removes existing values
addText(newParagraph, 0, attrs, valueString);
if (value instanceof Integer || value instanceof Double || value instanceof Float) {
isNumberValue = true;
cell.setAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "office:value", valueString);
} else if (value instanceof String) {
cell.setAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "office:value-type", STRING);
setValueType = false;
}
}
}
}
if (attrs != null) {
// Format: Adding Styles to the element
addStyle(attrs, cell, ownerDoc);
if (cell.hasChildNodes()) {
NodeList children = cell.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child instanceof OdfElement) {
((OdfElement) child).markText(0, Integer.MAX_VALUE - 1, attrs);
}
}
} else {
if (url != null) {
TextPElement containerElement1 = new TextPElement(ownerDoc);
TextAElement containerElement2 = new TextAElement(ownerDoc);
containerElement2.setXlinkHrefAttribute(url);
cell.appendChild(containerElement1);
containerElement1.appendChild(containerElement2);
}
}
}
// value-type has to be set if
// - a number is set replacing a previous string-type
// - if a number is changed to a string (already done above ) or vice-versa
// - if attributes have been set (that contain a number format or a new style)
//
if (setValueType) {
String currentValueType = cell.getOfficeValueTypeAttribute();
boolean isStringType = currentValueType != null && currentValueType.equals("String");
boolean changedToNumber = isNumberValue && isStringType;
boolean numberFormatChanged = false;
if (attrs != null) {
JSONObject cellAttrs = attrs.optJSONObject("cell");
if (cellAttrs != null) {
numberFormatChanged = cellAttrs.has("formatCode");
}
if (!numberFormatChanged) {
String styleId = attrs.optString(OPK_STYLE_ID);
numberFormatChanged = styleId != null;
}
}
if (currentValueType == null || changedToNumber || numberFormatChanged) {
DataStyleElement dataStyle = cell.getOwnDataStyle();
if (dataStyle != null) {
String valueType = "";
String currencySymbol = "";
if (dataStyle instanceof OdfNumberStyle) {
valueType = FLOAT;
} else if (dataStyle instanceof OdfNumberCurrencyStyle) {
currencySymbol =
((OdfNumberCurrencyStyle) dataStyle).getCurrencySymbolElement().getTextContent();
valueType = CURRENCY;
} else if (dataStyle instanceof NumberTextStyleElement) {
valueType = STRING;
} else if (dataStyle instanceof OdfNumberDateStyle) {
valueType = DATE;
} else if (dataStyle instanceof OdfNumberTimeStyle) {
valueType = TIME;
} else if (dataStyle instanceof OdfNumberPercentageStyle) {
valueType = PERCENTAGE;
} else if (dataStyle instanceof NumberBooleanStyleElement) {
valueType = BOOLEAN;
}
if (!valueType.isEmpty()) {
cell.setOfficeValueTypeAttribute(valueType);
cell.setCalcextValueTypeAttribute(valueType);
cell.setOfficeCurrencyAttribute(currencySymbol);
// make sure that an appropriate value is available:
if (value == null && cell.getOfficeValueAttribute() == null) {
String oldDateValue = cell.getOfficeDateValueAttribute();
String oldTimeValue = cell.getOfficeTimeValueAttribute();
Boolean oldBooleanValue = cell.getOfficeBooleanValueAttribute();
Double newValue = null;
if (oldDateValue != null) {
newValue = new Double(MapHelper.dateToDouble(oldDateValue));
} else if (oldTimeValue != null) {
newValue = new Double(MapHelper.timeToDouble(oldTimeValue));
} else if (oldBooleanValue != null) {
newValue = new Double(oldBooleanValue.booleanValue() ? 1 : 0);
}
if (newValue != null) {
cell.setOfficeValueAttribute(newValue);
}
}
}
}
}
}
return cell;
}
/**
* To be able to reuse existing style on the full table, new cell hyperlinks will be stored in the
* cell text style properties as @xlink:href attribute and taken back when nothing new is set.
*/
private static String reuseCellHyperlink(TableTableCellElement cell, JSONObject attrs) {
String cellURL = null;
if (attrs != null) { // apply style changes to the cell
// apply new styles to the cell (modifying not overwriting)
if (attrs.has("character")) {
JSONObject charProps = attrs.optJSONObject("character");
if (charProps != null) {
if (charProps.has("url") && !charProps.get("url").equals(JSONObject.NULL)) {
cellURL = charProps.optString("url");
} else if (charProps.has("url")) {
// removeAnchors();
}
}
}
}
// if there is no new hyperlink given, check for an existing cached (in the properties)
if (cellURL == null || cellURL.isEmpty()) {
// check if there is still one given at the cell
OdfStyle autoStyle = cell.getAutomaticStyle();
if (autoStyle != null) {
OdfElement textProps = autoStyle.getPropertiesElement(OdfStylePropertiesSet.TextProperties);
if (textProps != null
&& textProps.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) {
cellURL = textProps.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
}
}
}
return cellURL;
}
private static JSONObject addUrlToCharacterProps(JSONObject attrs, String cellURL) {
JSONObject charProps = null;
if (cellURL != null && !cellURL.isEmpty()) {
if (attrs == null) {
attrs = new JSONObject();
}
if (!attrs.has("character")) {
charProps = new JSONObject();
try {
attrs.put("character", charProps);
} catch (JSONException ex) {
Logger.getLogger(JsonOperationConsumer.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
charProps = attrs.optJSONObject("character");
}
try {
charProps.put("url", cellURL);
} catch (JSONException ex) {
Logger.getLogger(JsonOperationConsumer.class.getName()).log(Level.SEVERE, null, ex);
}
}
return attrs;
}
}