All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.odftoolkit.odfdom.changes.Cell Maven / Gradle / Ivy

Go to download

ODFDOM is an OpenDocument Format (ODF) framework. Its purpose is to provide an easy common way to create, access and manipulate ODF files, without requiring detailed knowledge of the ODF specification. It is designed to provide the ODF developer community with an easy lightwork programming API portable to any object-oriented language. The current reference implementation is written in Java.

There is a newer version: 1.0.0-BETA1
Show newest version
/*
 * 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.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import org.odftoolkit.odfdom.doc.OdfDocument;
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.table.TableTableColumnElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowElement;
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.OdfStyleFamily;
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.office.OdfOfficeStyles;
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;
import org.xml.sax.SAXException;

/**
 * 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 
 */
class Cell 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 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 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 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 = getCellDataStyle(cell);
        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.setAttributeNS(
                OdfDocumentNamespace.CALCEXT.getUri(), "calcext:value-type", 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 = MapHelper.dateToDouble(oldDateValue);
              } else if (oldTimeValue != null) {
                newValue = MapHelper.timeToDouble(oldTimeValue);
              } else if (oldBooleanValue != null) {
                newValue = Double.valueOf(oldBooleanValue ? 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;
  }

  public static DataStyleElement getCellDataStyle(TableTableCellElement cell) {
    try {
      String styleName = cell.getAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "style-name");
      String dataStyleName = "";
      OdfFileDom xDoc = (OdfFileDom) cell.getOwnerDocument();
      OdfDocument odfDoc = (OdfDocument) xDoc.getDocument();
      OdfOfficeStyles officeStyles = odfDoc.getStylesDom().getOfficeStyles();
      if (styleName == null || styleName.isEmpty()) {
        TableTableRowElement row = (TableTableRowElement) cell.getParentNode();
        TableTableCellElement rowCell = (TableTableCellElement) row.getFirstChild();
        int cellIndex = 0;
        while (rowCell != null) {
          if (rowCell.equals(cell)) {
            break;
          }
          cellIndex += rowCell.getRepetition();
          rowCell = (TableTableCellElement) rowCell.getNextSibling();
        }

        Node tableNode = row.getParentNode();
        TableTableColumnElement columnNode =
            OdfElement.findFirstChildNode(TableTableColumnElement.class, tableNode);
        int colIndex = 0;
        while (columnNode != null) {
          TableTableColumnElement column = columnNode;
          if (colIndex <= cellIndex && cellIndex <= colIndex + column.getRepetition() - 1) {
            styleName = column.getTableDefaultCellStyleNameAttribute();
            break;
          }
          columnNode = (TableTableColumnElement) columnNode.getNextSibling();
        }
      }
      OdfStyle ownStyle = officeStyles.getStyle(styleName, OdfStyleFamily.TableCell);
      if (ownStyle == null) {
        ownStyle =
            odfDoc
                .getContentDom()
                .getAutomaticStyles()
                .getStyle(styleName, OdfStyleFamily.TableCell);
      }
      dataStyleName =
          ownStyle.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "data-style-name");
      if (dataStyleName != null) {
        DataStyleElement dataStyle = officeStyles.getAllDataStyles().get(dataStyleName);
        if (dataStyle == null) {
          dataStyle =
              odfDoc.getContentDom().getAutomaticStyles().getAllDataStyles().get(dataStyleName);
        }
        return dataStyle;
      }
    } catch (SAXException e) {
      Logger.getLogger(TableTableCellElement.class.getName()).log(Level.SEVERE, null, e);
    } catch (IOException ex) {
      Logger.getLogger(TableTableCellElement.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy