DataStyleElement.java
/**
* **********************************************************************
*
* <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* <p>Use is subject to license terms.
*
* <p>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. You can also obtain a copy of the License at
* http://odftoolkit.org/docs/license.txt
*
* <p>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.
*
* <p>See the License for the specific language governing permissions and limitations under the
* License.
*
* <p>**********************************************************************
*/
package org.odftoolkit.odfdom.dom.element.number;
import java.util.ArrayList;
import java.util.List;
import org.odftoolkit.odfdom.changes.MapHelper;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.element.style.StyleMapElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTextPropertiesElement;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
/**
* interface of styles that represent the different number formats that are referenced from
* styles/auto styles with the data-style-name attribute
*/
public abstract class DataStyleElement extends OdfElement {
public enum TokenType {
TOKEN_TEXT,
TOKEN_NUMBER,
TOKEN_COLOR,
TOKEN_CURRENCY
}
public enum NumberFormatType {
FORMAT_CURRENCY,
FORMAT_TEXT,
FORMAT_PERCENT,
FORMAT_DATE,
FORMAT_TIME,
FORMAT_NUMBER
}
public static class StringToken {
public TokenType type;
public String text;
StringToken(String tx, TokenType t) {
text = tx;
type = t;
}
}
DataStyleElement(OdfFileDom ownerDoc, OdfName elementName) {
super(ownerDoc, elementName);
}
protected String getMapping(StyleMapElement mapElement) {
String mappedResult = "";
String condition = mapElement.getStyleConditionAttribute();
String applyStyleName = mapElement.getStyleApplyStyleNameAttribute();
org.w3c.dom.Node parent = getParentNode();
if (applyStyleName != null) {
DataStyleElement applyStyle = null;
if (parent instanceof OdfOfficeStyles) {
applyStyle = ((OdfOfficeStyles) parent).getAllDataStyles().get(applyStyleName);
} else {
applyStyle = ((OdfOfficeAutomaticStyles) parent).getAllDataStyles().get(applyStyleName);
}
if (applyStyle != null) {
String localFormat = applyStyle.getFormat(true);
if (condition != null && condition.length() >= 9 && condition.startsWith("value()")) {
// always starts with "value()"
String operator2 = condition.substring(8, 9); // '=', "<>", '<', "<=", '>', ">="
int opLength = 1;
if (operator2.equals("=") || operator2.equals("<") || operator2.equals(">")) {
opLength = 2;
}
try {
double opValue = Double.parseDouble(condition.substring(7 + opLength));
if (opValue != 0.) {
mappedResult += '[';
mappedResult += condition.substring(7, 7 + opLength);
mappedResult += Double.toString(opValue);
mappedResult += ']';
}
} catch (NumberFormatException e) {
}
}
mappedResult += localFormat;
}
}
return mappedResult;
}
/**
* converts a color attribute to a color token of a number format string
*
* @param e
* @return the resulting color token
*/
public String getColorFromElement(StyleTextPropertiesElement e) {
// contains e.g. fo:color
String ret = "";
String color = e.getAttributeNS(OdfDocumentNamespace.FO.getUri(), "color");
if (color != null) {
if (color.equalsIgnoreCase("#0000ff")) {
ret = "[BLUE]";
} else if (color.equalsIgnoreCase("#00FF00")) {
ret = "[GREEN]";
} else if (color.equalsIgnoreCase("#FF0000")) {
ret = "[RED]";
} else if (color.equalsIgnoreCase("#FFFFFF")) {
ret = "[WHITE]";
} else if (color.equalsIgnoreCase("#FF00FF")) {
ret = "[MAGENTA]";
} else if (color.equalsIgnoreCase("#FFFF00")) {
ret = "[YELLOW]";
} else if (color.equalsIgnoreCase("#00FFFF")) {
ret = "[CYAN]";
} else if (color.equalsIgnoreCase("#000000")) {
ret = "[BLACK]";
}
}
return ret;
}
/**
* @param colorToken color name used in number format
* @return resulting color value or empty string
*/
protected static String getColorElement(String colorToken) {
String ret = "";
if (colorToken.equalsIgnoreCase("RED")) {
ret = "#ff0000";
} else if (colorToken.equalsIgnoreCase("BLUE")) {
ret = "#0000ff";
} else if (colorToken.equalsIgnoreCase("GREEN")) {
ret = "#00ff00";
} else if (colorToken.equalsIgnoreCase("WHITE")) {
ret = "#ffffff";
} else if (colorToken.equalsIgnoreCase("MAGENTA")) {
ret = "#ff00ff";
} else if (colorToken.equalsIgnoreCase("YELLOW")) {
ret = "#ffff00";
} else if (colorToken.equalsIgnoreCase("CYAN")) {
ret = "#00ffff";
} else if (colorToken.equalsIgnoreCase("BLACK")) {
ret = "#000000";
}
return ret;
}
/**
* creates tokens from a number format
*
* @param format
* @return tokens to be converted to OdfElements
* <p>TODO: at first only detecting currencies -
*/
protected static List<StringToken> tokenize(String format, NumberFormatType type) {
ArrayList<StringToken> tokens = new ArrayList<StringToken>();
boolean hasNumber =
false; // only one number token can be created, next one will be part of a text token
String currentTextToken = "";
for (int pos = 0; pos < format.length(); ++pos) {
char c = format.charAt(pos);
if (c == '"') {
// add all characters until the next quotation to the current text token
currentTextToken += c;
while (pos < format.length() - 1) {
c = format.charAt(pos + 1);
if (c == '\\') {
currentTextToken += c;
if ((pos > format.length() - 2)) break; // invalid!
currentTextToken += c;
currentTextToken += format.charAt(pos + 2);
++pos;
} else if (c == '"') {
currentTextToken += c;
++pos;
break;
} else {
currentTextToken += c;
}
++pos;
}
} else if (c == '[') {
if (!currentTextToken.isEmpty()) {
tokens.add(new StringToken(currentTextToken, TokenType.TOKEN_TEXT));
currentTextToken = "";
}
int closePos = format.indexOf(']', pos);
if (closePos < 0
|| closePos < pos + 2) { // minimum two characters inside, closing bracket not quoted
break; // invalid
}
String bracketToken = format.substring(pos, closePos + 1);
if (tokens.isEmpty()
&& !getColorElement(bracketToken.substring(1, bracketToken.length() - 1)).isEmpty()) {
tokens.add(new StringToken(bracketToken, TokenType.TOKEN_COLOR));
} else if (bracketToken.charAt(1) == '$') {
tokens.add(new StringToken(bracketToken, TokenType.TOKEN_CURRENCY));
} else {
break; // invalid
}
pos = closePos;
} else if (!hasNumber && (c == '#' || c == '0' || c == '.')) {
if (!currentTextToken.isEmpty()) {
tokens.add(new StringToken(currentTextToken, TokenType.TOKEN_TEXT));
currentTextToken = "";
}
final int numPos = pos;
while (++pos < format.length()) {
// TODO: hashes can only be at the beginning interrupted by comma
c = format.charAt(pos);
if ((c != '#' && c != '.' && c != ',' && c != '0') || (pos == (format.length() - 1))) {
String numberToken = format.substring(numPos, pos + 1);
if (numberToken.charAt(numberToken.length() - 1) == '.') {
// add replacement chars
int spacePos = format.indexOf(' ', pos - 1);
int bracketPos = format.indexOf('[', pos - 1);
if (spacePos < 0) {
spacePos = bracketPos;
} else if (bracketPos < 0) {
bracketPos = spacePos;
}
spacePos = Math.min(spacePos, bracketPos);
if (spacePos
< 0) { // if not found add the rest of the format TODO: Are there other delimiters
// than space and bracket? ?
spacePos = format.length();
}
if (spacePos > 0) {
numberToken += format.substring(pos, spacePos);
pos = spacePos;
}
}
tokens.add(new StringToken(numberToken, TokenType.TOKEN_NUMBER));
break;
}
}
} else if (c == '\\') {
// add this and the next character to the text token
currentTextToken += c;
if (pos > format.length() - 2) {
break; // invalid
}
currentTextToken += format.charAt(pos + 1);
++pos;
} else {
currentTextToken += c;
}
}
if (!currentTextToken.isEmpty()) {
tokens.add(new StringToken(currentTextToken, TokenType.TOKEN_TEXT));
}
return tokens;
}
protected void emitTokens(List<StringToken> tokens, NumberFormatType type) {
for (StringToken token : tokens) {
switch (token.type) {
case TOKEN_CURRENCY:
{
if (NumberFormatType.FORMAT_CURRENCY == type) {
emitCurrency(token.text);
} else {
emitText(token.text);
}
}
break;
case TOKEN_TEXT:
emitText(token.text);
break;
case TOKEN_COLOR:
{
emitColor(token.text);
}
break;
case TOKEN_NUMBER:
{
if (NumberFormatType.FORMAT_PERCENT == type && token.text.endsWith("%")) {
emitNumber(token.text.substring(0, token.text.length() - 1), true);
final NumberTextElement numberText =
new NumberTextElement((OdfFileDom) this.getOwnerDocument());
numberText.setTextContent("%");
this.appendChild(numberText);
} else if (NumberFormatType.FORMAT_CURRENCY == type) {
emitNumber(token.text, true);
} else {
emitNumber(token.text, false);
}
}
break;
}
}
}
protected void emitCurrency(String currencyToken) {
String innerText = currencyToken.substring(1, currencyToken.length() - 1);
String currencySymbol = "";
String languageCode = "";
if (innerText.startsWith("$") && innerText.length() > 1) {
int dashPos = innerText.indexOf("-");
currencySymbol = innerText.substring(1, dashPos);
if (dashPos > 0) {
languageCode = innerText.substring(dashPos + 1);
}
}
if (!currencySymbol.isEmpty()) {
OdfFileDom dom = (OdfFileDom) this.getOwnerDocument();
NumberCurrencySymbolElement cSymbol = new NumberCurrencySymbolElement(dom);
String locale = MapHelper.getLocaleFromLangCode(languageCode);
if (!locale.isEmpty()) {
int dashPos = locale.indexOf("-");
cSymbol.setAttributeNS(
OdfDocumentNamespace.NUMBER.getUri(), "number:language", locale.substring(0, dashPos));
if (dashPos > 0) {
cSymbol.setAttributeNS(
OdfDocumentNamespace.NUMBER.getUri(),
"number:country",
locale.substring(dashPos + 1));
}
}
cSymbol.setTextContent(currencySymbol);
this.appendChild(cSymbol);
}
}
protected void emitColor(String colorToken) {
String color = getColorElement(colorToken.substring(1, colorToken.length() - 1));
if (!color.isEmpty()) {
OdfFileDom dom = (OdfFileDom) this.getOwnerDocument();
StyleTextPropertiesElement cProperties = new StyleTextPropertiesElement(dom);
cProperties.setAttributeNS(OdfDocumentNamespace.FO.getUri(), "fo:color", color);
this.appendChild(cProperties);
}
}
protected void emitNumber(String numberToken, boolean forceCreateDecimalPlaces) {
OdfFileDom dom = (OdfFileDom) this.getOwnerDocument();
NumberNumberElement number = new NumberNumberElement(dom);
/* Process part before the decimal point (if any) */
int nDigits = 0;
char ch;
int pos;
for (pos = 0; pos < numberToken.length() && (ch = numberToken.charAt(pos)) != '.'; pos++) {
if (ch == ',') {
number.setNumberGroupingAttribute(true);
} else if (ch == '0') {
nDigits++;
}
}
number.setNumberMinIntegerDigitsAttribute(nDigits);
/* Number of decimal places is the length after the decimal */
if (pos < numberToken.length()) {
number.setNumberDecimalPlacesAttribute(numberToken.length() - (pos + 1));
if (pos < numberToken.length() - 1 && numberToken.charAt(pos + 1) != '0') {
number.setNumberDecimalReplacementAttribute(numberToken.substring(pos + 1));
}
} else if (forceCreateDecimalPlaces) {
number.setNumberDecimalPlacesAttribute(0);
}
this.appendChild(number);
}
/**
* Place pending text into a <number:text> element.
*
* @param textBuffer pending text
*/
protected void emitText(String textBuffer) {
NumberTextElement textElement;
if (!textBuffer.equals("")) {
textElement = new NumberTextElement((OdfFileDom) this.getOwnerDocument());
textElement.setTextContent(textBuffer);
this.appendChild(textElement);
}
}
public String getNumberFormat() {
String result = "";
NumberNumberElement number = OdfElement.findFirstChildNode(NumberNumberElement.class, this);
boolean isGroup = number.getNumberGroupingAttribute();
int decimalPos =
(number.getNumberDecimalPlacesAttribute() == null)
? 0
: number.getNumberDecimalPlacesAttribute().intValue();
int minInt =
(number.getNumberMinIntegerDigitsAttribute() == null)
? 0
: number.getNumberMinIntegerDigitsAttribute().intValue();
String decimalReplacement = number.getNumberDecimalReplacementAttribute();
int i;
if (minInt == 0) {
result = "#";
}
for (i = 0; i < minInt; i++) {
if (((i + 1) % 3) == 0 && isGroup) {
result = ",0" + result;
} else {
result = "0" + result;
}
}
while (isGroup && (result.indexOf(',') == -1)) {
if (((i + 1) % 3) == 0 && isGroup) {
result = "#,#" + result;
} else {
result = "#" + result;
}
i++;
}
if (decimalReplacement != null) {
result += '.' + decimalReplacement;
} else if (decimalPos > 0) {
result += ".";
for (i = 0; i < decimalPos; i++) {
result += "0";
}
}
return result;
}
/**
* Get the format string that represents this style.
*
* @param caps use capitals
* @return the format string
*/
public abstract String getFormat(boolean caps);
/**
* Get the format string that represents this style. Uses capitals by default
*
* @return the format string
*/
public String getFormat() {
return getFormat(Boolean.FALSE);
}
/**
* Get the format string that represents this style.
*
* @param format the format string
* @return the format string
*/
public abstract void setFormat(String format);
}