MapHelper.java

/**
 * **********************************************************************
 *
 * <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * <p>Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * <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.changes;

import java.io.IOException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.doc.OdfTextDocument;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
import org.odftoolkit.odfdom.dom.attribute.office.OfficeValueTypeAttribute;
import org.odftoolkit.odfdom.dom.attribute.office.OfficeValueTypeAttribute.Value;
import org.odftoolkit.odfdom.dom.element.OdfStylableElement;
import org.odftoolkit.odfdom.dom.element.OdfStyleBase;
import org.odftoolkit.odfdom.dom.element.OdfStylePropertiesBase;
import org.odftoolkit.odfdom.dom.element.number.DataStyleElement;
import org.odftoolkit.odfdom.dom.element.style.StyleParagraphPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTabStopElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTabStopsElement;
import org.odftoolkit.odfdom.dom.style.props.OdfStylePropertiesSet;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.incubator.doc.office.OdfStylesBase;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfNamespace;
import org.odftoolkit.odfdom.type.Length;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class MapHelper {

  public static final String AUTO = "auto";
  public static final String NORMAL = "normal";
  public static final String BOLD = "bold";
  public static final String THIN = "thin";
  public static final String MEDIUM = "medium";
  public static final String THICK = "thick";
  public static final String HASH = "#";
  public static final String TRANSPARENT = "transparent";
  private static final String PERCENT = "%";
  private static final Logger LOG = Logger.getLogger(JsonOperationProducer.class.getName());
  private static Map<Integer, String> languageToLocaleMap = null;
  private static Map<String, Integer> localeToLanguageMap = null;
  // a color, which type is set to auto - adapting color to environment
  private static final Map<String, String> COLOR_MAP_AUTO =
      MapHelper.createColorMap(MapHelper.AUTO);

  /** map odf border strings to JSON border object see Changes API Border */
  public static JSONObject createBorderMap(String borderValue) {
    JSONObject border = new JSONObject();
    try {
      if (borderValue.equals("none")) {
        border.put("style", "none");
      } else {
        String[] tokens = borderValue.split("\\s+");
        boolean checkedColor = false;
        boolean checkedStyle = false;
        boolean checkedWidth = false;
        for (int i = 0; i < tokens.length; i++) {
          String token = tokens[i];
          if (!token.isEmpty()) {
            boolean isTokenTaken = false;
            if (!checkedColor) {
              if (isColor(token)) {
                checkedColor = mapColor(border, token);
                isTokenTaken = checkedColor;
              }
            }
            if (!isTokenTaken && !checkedStyle) {
              checkedStyle = mapStyle(border, token);
              isTokenTaken = checkedStyle;
            }
            if (!isTokenTaken && !checkedWidth) {
              checkedWidth = mapWidth(border, token, tokens);
            }
          }
        }
      }
    } catch (JSONException ex) {
      Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
    }
    return border;
  }

  public static boolean isColor(String color) {
    return color.startsWith(HASH) || color.equals(TRANSPARENT);
  }

  public static boolean mapColor(JSONObject border, String width) {
    boolean isColor = false;
    try {
      // try if the first token is a width
      border.put("color", createColorMap(width));

    } catch (JSONException ex) {
      Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
    }
    isColor = true;

    return isColor;
  }

  public static boolean mapStyle(JSONObject border, String style) throws JSONException {
    boolean isStyle = false;
    // solid The border is a solid line
    // groove    The border looks like it were carved into the canvas
    // ridge The border "comes out" of the canvas
    if (style.equals("solid") || style.equals("groove") || style.equals("ridge")) {
      style = "single";
      border.put("style", style);
      isStyle = true;

      // hidden   A hidden border
    } else if (style.equals("hidden")) {
      style = "none";
      border.put("style", style);
      isStyle = true;

    } else if (style.equals("double")
        || style.equals("dotted")
        || style.equals("dashed")
        || style.equals("outset")
        || style.equals("inset")) {
      border.put("style", style);
      isStyle = true;
    } else if (style.contains("-dash")) {
      border.put("style", "dashed");
      isStyle = true;
    } else if (style.contains("-dot")) {
      border.put("style", "dotted");
      isStyle = true;
    } else if (style.contains("double")) {
      border.put("style", "double");
      isStyle = true;
    }
    return isStyle;
  }

  /** see Changes API Color */
  public static Map<String, String> createColorMap(String rgbValue) {
    Map color = new HashMap<String, String>();
    if (rgbValue.contains(HASH)) {
      color.put("type", "rgb");
      rgbValue = rgbValue.subSequence(rgbValue.indexOf('#') + 1, rgbValue.length()).toString();
      color.put("value", rgbValue);
    } else if (rgbValue.equals(TRANSPARENT) || rgbValue.equals(AUTO)) {
      color.put("type", AUTO);
    }
    return color;
  }

  /**
   * <define name="lineWidth"> <choice> <value>auto</value> <value>normal</value>
   * <value>bold</value> <value>thin</value> <value>medium</value> <value>thick</value> <ref
   * name="positiveInteger"/> <ref name="percent"/> <ref name="positiveLength"/> </choice> </define>
   */
  public static boolean mapWidth(JSONObject border, String widthString, String[] tokens) {
    boolean isWidth = false;
    int width;
    try {
      if (widthString.equals(AUTO)) {
        width = 26; // my guess
      } else if (widthString.equals(NORMAL)) {
        width = 27; // my guess
      } else if (widthString.equals(BOLD)) {
        width = 54; // my guess
      } else if (widthString.equals(THIN)) {
        width = 26;
      } else if (widthString.equals(MEDIUM)) {
        width = 53;
      } else if (widthString.equals(THICK)) {
        width = 79;
      } else {
        width = normalizeLength(widthString);
      }
      // try if the first token is a width
      border.put("width", width);
      isWidth = true;
    } catch (Throwable t) {
      // there have to a a width with three tokens
      if (tokens.length == 3) {
        throw new RuntimeException(t);
      }
    }
    return isWidth;
  }

  /** Currently the normalized length is 100th millimeter (or 10 micrometer) */
  public static int normalizeLength(String value) {
    Length length = new Length(value);
    return (int) Math.round(length.getMicrometer() / 10.0);
  }

  public static JSONObject mapProperties(String styleFamilyGroup, Map<String, String> odfProps) {
    JSONObject newProps = null;
    JSONObject shapeProps = null;
    JSONObject imageProps = null;
    JSONObject lineProps = null;
    JSONObject fillProps = null;
    if (odfProps != null) {

      // ToDo: Recycle the maps... (??)
      newProps = new JSONObject();
      String propValue;

      // *** TEXT STYLES ***
      if (styleFamilyGroup.equals("character")) {
        boolean borderToBeDone = true;
        boolean paddingToBeDone = true;
        boolean marginToBeDone = true;
        for (String propName : odfProps.keySet()) {
          try {
            if (propName.contains("margin")) {
              if (marginToBeDone) {
                mapMargin(newProps, odfProps);
                marginToBeDone = false;
              }
            } else if (propName.contains("padding")) {
              if (paddingToBeDone) {
                mapPadding(newProps, odfProps);
                paddingToBeDone = false;
              }
            } else if (propName.contains("border")) {
              if (borderToBeDone) {
                mapBorder(newProps, odfProps);
                borderToBeDone = false;
              }
            } else if (propName.contains("letter-spacing")) {
              propValue = odfProps.get("fo:letter-spacing");
              if (propValue.equals("normal")) {
                newProps.put("letterSpacing", propValue);
              } else {
                Integer spacing = MapHelper.normalizeLength(propValue);
                newProps.put("letterSpacing", spacing);
              }
            } else if (propName.equals("fo:font-size")) {
              propValue = odfProps.get("fo:font-size");
              if (propValue.contains("%")) {
                LOG.fine("fo:font-size does have a percentage value, which we do not support!");
              } else {
                Length length = new Length(propValue);
                Double fontSize = length.getPoint();
                newProps.put("fontSize", fontSize);
              }
            } else if (propName.equals("style:font-size-asian")) {
              propValue = odfProps.get("style:font-size-asian");
              if (propValue.contains("%")) {
                LOG.fine("style:font-size-asia does have a percentage value!");
              } else {
                Length length = new Length(propValue);
                Double fontSize = length.getPoint();
                newProps.put("fontSizeAsian", fontSize);
              }
            } else if (propName.equals("style:font-size-complex")) {
              propValue = odfProps.get("style:font-size-complex");
              if (propValue.contains("%")) {
                LOG.fine("style:font-size-complex does have a percentage value!");
              } else {
                Length length = new Length(propValue);
                Double fontSize = length.getPoint();
                newProps.put("fontSizeAsian", fontSize);
              }
            } else if (propName.equals("style:font-name")) {
              propValue = odfProps.get("style:font-name");
              newProps.put("fontName", propValue);
            } else if (propName.equals("style:font-name-asian")) {
              propValue = odfProps.get("style:font-name-asian");
              newProps.put("fontNameAsian", propValue);
            } else if (propName.equals("style:font-name-complex")) {
              propValue = odfProps.get("style:font-name-complex");
              newProps.put("fontNameComplex", propValue);
            } else if (propName.equals("style:text-position")) {
              propValue = odfProps.get("style:text-position");
              if (propValue.contains("sub")) {
                propValue = "sub";
                newProps.put("vertAlign", propValue);
              } else if (propValue.contains("super")) {
                propValue = "super";
                newProps.put("vertAlign", propValue);
              } else if (propValue.equals("0%") || propValue.equals("0% 100%")) {
                propValue = "baseline";
                newProps.put("vertAlign", propValue);
              }
            } else if (propName.equals("fo:language")) {
              propValue = odfProps.get("fo:language");
              String country = odfProps.get("fo:country");
              if (propValue != null) {
                if (!propValue.equals("none")) {
                  if (country != null && !country.isEmpty() && !country.equals("none")) {
                    propValue = propValue + '-' + country;
                  }
                  newProps.put("language", propValue);
                } else {
                  newProps.put("noProof", true);
                  newProps.put("language", "none");
                }
              }
            }
            // TODO -- !!!!!!!!!!!!!!!!ROUNDTRIP WITH THESE VALUES!!!!!!!!!!!
            //  <define name="fontWeight">
            //      <choice>
            //          <value>normal</value>
            //          <value>bold</value>
            //          <value>100</value>
            //          <value>200</value>
            //          <value>300</value>
            //          <value>400</value>
            //          <value>500</value>
            //          <value>600</value>
            //          <value>700</value>
            //          <value>800</value>
            //          <value>900</value>
            //      </choice>
            //  </define>
            if (propName.equals("fo:font-weight")) {
              propValue = odfProps.get("fo:font-weight");
              if (propValue.equals("normal")) {
                newProps.put("bold", Boolean.FALSE);
              } else {
                newProps.put("bold", Boolean.TRUE);
              }
            } else if (propName.equals("style:font-weight-asian")) {
              propValue = odfProps.get("style:font-weight-asian");
              if (propValue.equals("normal")) {
                newProps.put("boldAsian", Boolean.FALSE);
              } else {
                newProps.put("boldAsian", Boolean.TRUE);
              }
            } else if (propName.equals("style:font-weight-complex")) {
              propValue = odfProps.get("style:font-weight-complex");
              if (propValue.equals("normal")) {
                newProps.put("boldComplex", Boolean.FALSE);
              } else {
                newProps.put("boldComplex", Boolean.TRUE);
              }
            } //    <define name="lineStyle">
            //      <choice>
            //          <value>none</value>
            //          <value>solid</value>
            //          <value>dotted</value>
            //          <value>dash</value>
            //          <value>long-dash</value>
            //          <value>dot-dash</value>
            //          <value>dot-dot-dash</value>
            //          <value>wave</value>
            //      </choice>
            //  </define>
            else if (propName.equals("style:text-underline-style")) {
              propValue = odfProps.get("style:text-underline-style");
              if (propValue.equals("none")) {
                newProps.put("underline", Boolean.FALSE);
              } else {
                newProps.put("underline", Boolean.TRUE);
              }

              //  <define name="fontStyle">
              //      <choice>
              //          <value>normal</value>
              //          <value>italic</value>
              //          <value>oblique</value>
              //      </choice>
              //  </define>
            } else if (propName.equals("fo:font-style")) {
              propValue = odfProps.get("fo:font-style");
              if (propValue.equals("normal")) {
                newProps.put("italic", Boolean.FALSE);
              } else {
                newProps.put("italic", Boolean.TRUE);
              }
            } else if (propName.equals("style:font-style-asian")) {
              propValue = odfProps.get("style:font-style-asian");
              if (propValue.equals("normal")) {
                newProps.put("italicAsian", Boolean.FALSE);
              } else {
                newProps.put("italicAsian", Boolean.TRUE);
              }
            } else if (propName.equals("style:font-style-complex")) {
              propValue = odfProps.get("style:font-style-complex");
              if (propValue.equals("normal")) {
                newProps.put("italicComplex", Boolean.FALSE);
              } else {
                newProps.put("italicComplex", Boolean.TRUE);
              }
            } // fo:color - text props only
            else if (propName.equals("fo:color")) {
              // "auto" color type wins..
              if (!newProps.has("color")) {
                propValue = odfProps.get("fo:color");
                Map<String, String> color = MapHelper.createColorMap(propValue);
                newProps.put("color", color);
              }
            } else if (propName.equals("style:use-window-font-color")) {
              propValue = odfProps.get("style:use-window-font-color");
              if (propValue.equals("true")) {
                newProps.put("color", COLOR_MAP_AUTO);
              }
            } else if (propName.equals("fo:background-color")) {
              propValue = odfProps.get("fo:background-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              newProps.put("fillColor", color);
            } else if (propName.equals("style:width")) {
              propValue = odfProps.get("style:width");
              if (!propValue.contains(PERCENT)) {
                newProps.put("width", MapHelper.normalizeLength(propValue));
              }
            } else if (propName.equals("style:writing-mode")) {
              propValue = odfProps.get("style:writing-mode");
              newProps.put("writingMode", propValue);

            } else if (propName.equals("style:text-line-through-style")) {
              /* the style might be 'none'*/
              propValue = odfProps.get("style:text-line-through-style");
              if (propValue.equals("none")) {
                newProps.put("strike", "none");
              } else {
                if (odfProps.containsKey("style:text-line-through-type")) {
                  propValue = odfProps.get("style:text-line-through-type");
                  /* The type is either "double" or different, than we are providing a "single" style */
                  if (propValue.equals("double")) {
                    newProps.put("strike", "double");
                  } else {
                    newProps.put("strike", "single");
                  }
                } else {
                  newProps.put("strike", "single");
                }
              }
            }
            // fo:letter-spacing="0.0104in" fo:hyphenate="false
            //                      "vertAlign":"super"}
          } catch (JSONException ex) {
            Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      } else if (styleFamilyGroup.equals("paragraph")) {
        boolean borderToBeDone = true;
        boolean paddingToBeDone = true;
        boolean marginToBeDone = true;
        JSONArray tabs = null;
        for (String propName : odfProps.keySet()) {
          try {
            if (propName.contains("margin")) {
              if (marginToBeDone) {
                mapMargin(newProps, odfProps);
                marginToBeDone = false;
              }
            } else if (propName.contains("padding")) {
              if (paddingToBeDone) {
                mapPadding(newProps, odfProps);
                paddingToBeDone = false;
              }
            } else if (propName.contains("border")) {
              if (borderToBeDone) {
                mapBorder(newProps, odfProps);
                borderToBeDone = false;
              }
            } else if (propName.equals("fo:background-color")) {
              propValue = odfProps.get("fo:background-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              newProps.put("fillColor", color);
              //              <attribute name="fo:line-height">
              //                  <choice>
              //                      <value>normal</value>
              //                      <ref name="nonNegativeLength"/>
              //                      <ref name="percent"/>
              //                  </choice>
              //              </attribute>
              // { type: 'percent', value: 100 }
            } else if (propName.equals("fo:line-height")) {
              String lineHeightValue = odfProps.get("fo:line-height");
              if (lineHeightValue != null && !lineHeightValue.isEmpty()) {
                JSONObject lineHeight = createLineHeightMap(lineHeightValue);
                newProps.put("lineHeight", lineHeight);
              }
            } else if (propName.equals("style:line-spacing")) {
              String lineLeadingValue = odfProps.get("style:line-spacing");
              if (lineLeadingValue != null && !lineLeadingValue.isEmpty()) {
                JSONObject lineHeightLeadingMap = new JSONObject();
                lineHeightLeadingMap.put("type", "leading");
                lineHeightLeadingMap.put("value", MapHelper.normalizeLength(lineLeadingValue));
                newProps.put("lineHeight", lineHeightLeadingMap);
              }
            } else if (propName.equals("style:line-height-at-least")) {
              String lineHeightAtLeastValue = odfProps.get("style:line-height-at-least");
              if (lineHeightAtLeastValue != null && !lineHeightAtLeastValue.isEmpty()) {
                JSONObject lineHeightAtLeastMap = new JSONObject();
                lineHeightAtLeastMap.put("type", "atLeast");
                lineHeightAtLeastMap.put(
                    "value", MapHelper.normalizeLength(lineHeightAtLeastValue));
                newProps.put("lineHeight", lineHeightAtLeastMap);
              }
            } // see Changes API Paragraph_Formatting_Attributes
            // One of 'left', 'center', 'right', or 'justify'.
            // start, end, left, right, center or justify.
            else if (propName.equals("fo:text-align")) {
              propValue = odfProps.get("fo:text-align");
              newProps.put("alignment", mapFoTextAlign(propValue));
            } else if (propName.equals("fo:text-indent")) {
              propValue = odfProps.get("fo:text-indent");
              if (propValue.contains("%")) {
                LOG.fine(
                    "WARNING: Found a 'fo:text-indent' with percentage we are not yet supporting in our API: "
                        + propValue);
              } else {
                newProps.put("indentFirstLine", MapHelper.normalizeLength(propValue));
              }
            } else if (propName.startsWith("tab_")) {
              int i = 0;
              boolean hasTabChar = false;
              boolean hasTabPos = false;
              boolean hasTabType = false;
              JSONObject tab = null;
              // addChild tab property only once
              if (!newProps.has("tabStops")) {
                while (true) {
                  if (odfProps.containsKey("tab_LeaderText" + i)) {
                    propValue = odfProps.get("tab_LeaderText" + i);
                    if (tab == null) {
                      tab = new JSONObject();
                    }
                    tab.put("fillChar", propValue);
                    hasTabChar = true;
                  } else {
                    hasTabChar = false;
                  }
                  if (odfProps.containsKey("tab_Pos" + i)) {
                    propValue = odfProps.get("tab_Pos" + i);
                    if (tab == null) {
                      tab = new JSONObject();
                    }
                    tab.put("pos", Integer.parseInt(propValue));
                    hasTabPos = true;
                  } else {
                    hasTabPos = false;
                  }
                  if (odfProps.containsKey("tab_Type" + i)) {
                    propValue = odfProps.get("tab_Type" + i);
                    if (tab == null) {
                      tab = new JSONObject();
                    }
                    if (propValue.equals("left")) {
                      propValue = null;
                      // as default
                    }
                    if (propValue != null) {
                      tab.put("value", (String) propValue);
                      hasTabType = true;
                    }
                  } else {
                    hasTabType = false;
                  }
                  if (!hasTabChar && !hasTabType && !hasTabPos) {
                    newProps.put("tabStops", tabs);
                    break;
                  } else {
                    if (tabs == null) {
                      tabs = new JSONArray();
                    }
                    tabs.put(tab);
                    hasTabType = false;
                    hasTabPos = false;
                    hasTabChar = false;
                    tab = null;
                  }
                  i++;
                }
              }
            } else if (propName.equals("style:tab-stop-distance")) {
              propValue = odfProps.get("style:tab-stop-distance");
              if (propValue != null && !propValue.isEmpty()) {
                JSONObject documentProps;
                if (newProps.has("document")) {
                  documentProps = newProps.getJSONObject("document");
                } else {
                  documentProps = new JSONObject();
                }
                documentProps.put("defaultTabStop", MapHelper.normalizeLength(propValue));
                newProps.put("document", documentProps);
              }
            } else if (propName.equals("fo:break-before")) {
              propValue = odfProps.get("fo:break-before");
              if (propValue.equals("page")) {
                newProps.put("pageBreakBefore", Boolean.TRUE);
              }
            } else if (propName.equals("fo:break-after")) {
              propValue = odfProps.get("fo:break-after");
              if (propValue.equals("page")) {
                newProps.put("pageBreakAfter", Boolean.TRUE);
              }
            }

          } catch (JSONException ex) {
            Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      } else if (styleFamilyGroup.equals("cell")) {
        boolean borderToBeDone = true;
        boolean paddingToBeDone = true;
        for (String propName : odfProps.keySet()) {
          try {
            // No margin
            if (propName.contains("padding")) {
              if (paddingToBeDone) {
                mapPadding(newProps, odfProps);
                paddingToBeDone = false;
              }
            } else if (propName.contains("border")) {
              if (borderToBeDone) {
                mapBorder(newProps, odfProps);
                borderToBeDone = false;
              }
            } else if (propName.equals("fo:background-color")) {
              propValue = odfProps.get("fo:background-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              newProps.put("fillColor", color);
            } else if (propName.equals("style:vertical-align")) {
              propValue = odfProps.get("style:vertical-align");
              newProps.put("alignVert", propValue);
            }
          } catch (JSONException ex) {
            Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      } else if (styleFamilyGroup.equals("column")) {
        for (String propName : odfProps.keySet()) {
          try {
            // No margin
            if (propName.equals("style:column-width")) {
              propValue = odfProps.get("style:column-width");
              newProps.put("width", MapHelper.normalizeLength(propValue));
            } else if (propName.contains("style:use-optimal-column-width")) {
              propValue = odfProps.get("style:use-optimal-column-width");
              newProps.put("customWidth", !Boolean.parseBoolean(propValue));
            }
          } catch (JSONException ex) {
            Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      } else if (styleFamilyGroup.equals("row")) {
        try {
          for (String propName : odfProps.keySet()) {
            if (propName.equals("fo:background-color")) {
              propValue = odfProps.get("fo:background-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              newProps.put("fillColor", color);
            } else if (propName.equals("style:min-row-height")) {
              propValue = odfProps.get("style:min-row-height");
              newProps.put("height", MapHelper.normalizeLength(propValue));
            } else if (propName.equals("style:row-height")) {
              propValue = odfProps.get("style:row-height");
              newProps.put("height", MapHelper.normalizeLength(propValue));
            } else if (propName.contains("style:use-optimal-row-height")) {
              propValue = odfProps.get("style:use-optimal-row-height");
              newProps.put("customHeight", !Boolean.parseBoolean(propValue));
            }
          }
        } catch (JSONException ex) {
          Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
        }
      } else if (styleFamilyGroup.equals("list")) {
        try {
          for (String propName : odfProps.keySet()) {
            if (propName.equals("style:font-name")) {
              propValue = odfProps.get("style:font-name");
              newProps.put("fontName", propValue);
            } else if (propName.equals("style:font-name-asian")) {
              propValue = odfProps.get("style:font-name-asian");
              newProps.put("fontNameAsian", propValue);
            } else if (propName.equals("style:font-name-complex")) {
              propValue = odfProps.get("style:font-name-complex");
              newProps.put("fontNameComplex", propValue);
            } else if (propName.equals("fo:text-align")) {
              propValue = odfProps.get("fo:text-align");

              if (propValue.equals("start") || propValue.equals("left")) {
                // ToDo: I8N requires a correspondonce to the writing direction
                propValue = "left";
              } else if (propValue.equals("end") || propValue.equals("right")) {
                // ToDo: I8N requires a correspondonce to the writing direction
                propValue = "right";
              }
              newProps.put("alignment", propValue);
            }
          }
        } catch (JSONException ex) {
          Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
        }
      } else if (styleFamilyGroup.equals("table")) {
        boolean marginToBeDone = true;
        for (String propName : odfProps.keySet()) {
          try {
            // no padding, no border
            if (propName.contains("margin")) {
              if (marginToBeDone) {
                mapMargin(newProps, odfProps);
                marginToBeDone = false;
              }
            } else if (propName.equals("style:width")) {
              propValue = odfProps.get("style:width");
              if (!propValue.contains(PERCENT)) {
                newProps.put("width", MapHelper.normalizeLength(propValue));
              }
            } else if (propName.equals("fo:background-color")) {
              propValue = odfProps.get("fo:background-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              newProps.put("fillColor", color);
            } else if (propName.equals("table:display")) {
              propValue = odfProps.get("table:display");
              newProps.put("visible", Boolean.parseBoolean(propValue));
            } else if (propName.equals("fo:break-before")) {
              propValue = odfProps.get("fo:break-before");
              newProps.put("pageBreakBefore", propValue.equals("page"));
            } else if (propName.equals("fo:break-after")) {
              propValue = odfProps.get("fo:break-after");
              newProps.put("pageBreakAfter", propValue.equals("page"));
            }
          } catch (JSONException ex) {
            Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      } else if (styleFamilyGroup.equals("page")) {
        boolean borderToBeDone = true;
        boolean paddingToBeDone = true;
        boolean marginToBeDone = true;
        for (String propName : odfProps.keySet()) {
          try {
            if (propName.contains("margin")) {
              if (marginToBeDone) {
                mapMargin(newProps, odfProps);
                marginToBeDone = false;
              }
            } else if (propName.contains("padding")) {
              if (paddingToBeDone) {
                mapPadding(newProps, odfProps);
                paddingToBeDone = false;
              }
            } else if (propName.contains("border")) {
              if (borderToBeDone) {
                mapBorder(newProps, odfProps);
                borderToBeDone = false;
              }
            } else if (propName.equals("fo:background-color")) {
              propValue = odfProps.get("fo:background-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              newProps.put("fillColor", color);
            } else if (propName.equals("fo:page-width")) {
              propValue = odfProps.get("fo:page-width");
              newProps.put("width", MapHelper.normalizeLength(propValue));
            } else if (propName.equals("fo:page-height")) {
              propValue = odfProps.get("fo:page-height");
              newProps.put("height", MapHelper.normalizeLength(propValue));
            } else if (propName.equals("style:print-orientation")) {
              propValue = odfProps.get("style:print-orientation");
              newProps.put("printOrientation", propValue);
            } else if (propName.equals("style:num-format")) {
              propValue = odfProps.get("style:num-format");
              newProps.put("numberFormat", propValue);
            }
          } catch (JSONException ex) {
            Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      } else if (styleFamilyGroup.equals("drawing")) {
        boolean borderToBeDone = true;
        boolean paddingToBeDone = true;
        boolean marginToBeDone = true;
        shapeProps = new JSONObject();
        imageProps = new JSONObject();
        lineProps = new JSONObject();
        fillProps = new JSONObject();
        for (String propName : odfProps.keySet()) {
          try {
            if (propName.contains("margin")) {
              if (marginToBeDone) {
                mapMargin(newProps, odfProps);
                marginToBeDone = false;
              }
            } else if (propName.contains("padding")) {
              if (paddingToBeDone) {
                mapPadding(shapeProps, odfProps);
                paddingToBeDone = false;
              }
            } else if (propName.contains("border")) {
              if (borderToBeDone) {
                mapBorder(newProps, odfProps);
                borderToBeDone = false;
              }
            } else if (propName.equals("fo:background-color")) {
              propValue = odfProps.get("fo:background-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              fillProps.put("type", "solid");
              fillProps.put("color", color);
              // http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#property-style_mirror
            } else if (propName.equals("draw:fill-color")) {
              propValue = odfProps.get("draw:fill-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              fillProps.put("type", "solid");
              fillProps.put("color", color);
            } else if (propName.equals("style:mirror")) {
              String mirror = odfProps.get("style:mirror");
              if (mirror.contains("horizontal") && !mirror.contains("-on-")) {
                newProps.put("flipH", Boolean.TRUE);
              }
              if (mirror.contains("vertical")) {
                newProps.put("flipV", Boolean.TRUE);
              }
              if (mirror.equals("none")) {
                newProps.put("flipV", Boolean.FALSE);
                newProps.put("flipH", Boolean.FALSE);
              }
              /*
              @style:horizontal-rel:
              The defined values for the style:horizontal-rel attribute are:
              * char: horizontal position of a frame is positioned relative to a character.
              * page: horizontal position of a frame is positioned relative to a page.
              * page-content: horizontal position of a frame is positioned relative to page-content.
              * page-start-margin: horizontal position of a frame is positioned relative to a page start margin.
              * page-end-margin: horizontal position of a frame is positioned relative to a page end margin.
              * frame: horizontal position of a frame is positioned relative to another frame.
              * frame-content: horizontal position of a frame is positioned relative to frame content.
              * frame-end-margin: horizontal position of a frame is positioned relative to a frame end margin.
              * frame-start-margin: horizontal position of a frame is positioned relative to a frame start margin
              * paragraph: horizontal position of a frame is positioned relative to a paragraph.
              * paragraph-content: horizontal position of a frame is positioned relative to paragraph content.
              * paragraph-end-margin: horizontal position of a frame is positioned relative to a paragraph end margin.
              * paragraph-start-margin:horizontal position of a frame is positioned relative to a paragraph start margin.

              @style:horizontal-pos:
              The defined values for the style:horizontal-pos attribute are:
              * center: horizontal alignment of a frame should be centered relative to the specified area.
              * anchorHorAlign=center
              * from-inside: on pages with an odd page number the left edge of the specific area is taken as the horizontal alignment of a frame. On pages with an even page number the right edge of the specified area is taken. Attribute svg:x associated with the frame element specifies the horizontal position of the frame from the edge which is taken.
              * UNSUPPORTED
              * from-left: the svg:x attribute associated with the frame element specifies the horizontal position of the frame from the left edge of the specified area.
              * anchorHorAlign=offset
              * inside: on pages with an odd page number the horizontal alignment of a frame is the same as for the attribute value left. On pages with an even page number the horizontal alignment of a frame is the same as for the attribute value right.
              * anchorHorAlign=inside
              * left: horizontal alignment of a frame should be left aligned relative to the specified area.
              * anchorHorAlign=left
              * outside: on pages with an odd page number the horizontal alignment of a frame is the same as for the attribute value right. On pages with an even page number the horizontal alignment of a frame is the same as for the attribute value left.
              * anchorHorAlign=outside
              * right: horizontal alignment of a frame should be right aligned relative to the specified area.
              * anchorHorAlign=right
              If the attribute value is not from-left and not from-inside, the svg:x attribute associated with the frame element is ignored for text documents.                       */
              // Changes API:
              // anchorHorAlign: Horizontal anchor position:  One of 'left', 'right', 'center',
              // 'inside', 'outside', or 'offset'.
              // anchorHorOffset: Horizontal position offset (only used if anchorHorAlign is set to
              // 'offset')
            } else if (propName.contains("horizontal-pos")) {
              String horizontalPos = odfProps.get("style:horizontal-pos");
              if (horizontalPos.equals("center")) {
                newProps.put("anchorHorAlign", "center");
              } else if (horizontalPos.equals("from-left")) {
                newProps.put("anchorHorAlign", "offset");
              } else if (horizontalPos.equals("left")) {
                newProps.put("anchorHorAlign", "left");
              } else if (horizontalPos.equals("right")) {
                newProps.put("anchorHorAlign", "right");
              } else if (horizontalPos.equals("inside")) {
                newProps.put("anchorHorAlign", "inside");
              } else if (horizontalPos.equals("outside")) {
                newProps.put("anchorHorAlign", "outside");
              }
              // anchorVertAlign   Vertical anchor position. One of 'top', 'bottom', 'center',
              // 'inside', 'outside', 'offset'.
            } else if (propName.contains("vertical-pos")) {
              String verticalPos = odfProps.get("style:vertical-pos");
              if (verticalPos.equals("center")) {
                newProps.put("anchorVertAlign", "center");
              } else if (verticalPos.equals("from-top")) {
                newProps.put("anchorVertAlign", "offset");
              } else if (verticalPos.equals("top")) {
                newProps.put("anchorVertAlign", "top");
              } else if (verticalPos.equals("bottom")) {
                newProps.put("anchorVertAlign", "bottom");
              } else if (verticalPos.equals("inside")) {
                newProps.put("anchorVertAlign", "inside");
              } else if (verticalPos.equals("outside")) {
                newProps.put("anchorVertAlign", "outside");
              }
            } else if (propName.contains("horizontal-rel")) {
              String horiRel = odfProps.get("style:horizontal-rel");

              if (horiRel.equals("char")) {
                newProps.put("anchorHorBase", "character");
              } else if (horiRel.equals("page-content")) {
                newProps.put("anchorHorBase", "margin");
              } else if (horiRel.equals("page-start-margin")) {
                newProps.put("anchorHorBase", "leftMargin");
              } else if (horiRel.equals("page-end-margin")) {
                newProps.put("anchorHorBase", "rightMargin");
              } else if (horiRel.equals("frame")) {
                newProps.put("anchorHorBase", "column");
              } else if (horiRel.equals("frame-content")
                  || horiRel.equals("frame-end-margin")
                  || horiRel.equals("frame-start-margin")) {
                newProps.put("anchorHorBase", "column");
              } else if (horiRel.equals("paragraph") || horiRel.equals("paragraph-content")) {
                newProps.put("anchorHorBase", "column");
              } else if (horiRel.equals("paragraph-end-margin")) {
                newProps.put("anchorHorBase", "rightMargin");
              } else if (horiRel.equals("paragraph-start-margin")) {
                newProps.put("anchorHorBase", "leftMargin");
              } else if (horiRel.equals("page")) {
                newProps.put("anchorHorBase", "page");
              }
            } else if (propName.contains("vertical-rel")) {
              // char,
              // frame, frame-content
              // line
              // page, page-content,
              // paragraph-content, paragraph,
              // text
              String verticalRel = odfProps.get("style:vertical-rel");
              if (verticalRel.equals("char")) {
                newProps.put("anchorVertBase", "line");
              } else if (verticalRel.equals("frame")) {
                newProps.put("anchorVertBase", "paragraph");
              } else if (verticalRel.equals("frame-content")) {
                newProps.put("anchorVertBase", "paragraph");
              } else if (verticalRel.equals("line")) {
                newProps.put("anchorVertBase", "line");
              } else if (verticalRel.equals("page")) {
                newProps.put("anchorVertBase", "page");
              } else if (verticalRel.equals("page-content")) {
                newProps.put("anchorVertBase", "margin");
              } else if (verticalRel.equals("paragraph")) {
                newProps.put("anchorVertBase", "paragraph");
              } else if (verticalRel.equals("paragraph-content")) {
                newProps.put("anchorVertBase", "paragraph");
              } else if (verticalRel.equals("text")) {
                newProps.put("anchorVertBase", "line");
              }
            } else if (propName.equals("svg:x")) {
              int x = MapHelper.normalizeLength(odfProps.get("svg:x"));
              if (x != 0) {
                newProps.put("anchorHorOffset", x);
                newProps.put("left", x);
              }
            } else if (propName.equals("svg:y")) {
              int y = MapHelper.normalizeLength(odfProps.get("svg:y"));
              if (y != 0) {
                newProps.put("anchorVertOffset", y);
                newProps.put("top", y);
              }

              /*
              Changes API:
              cropLeft   Left cropping in percent
              cropRight  Right cropping in percent
              cropTop        Top cropping in percent
              cropBottom Bottom cropping in percent
              http://www.w3.org/TR/CSS2/visufx.html#propdef-clip */
              // <top>, <right>, <bottom>, <left>
              // Value length or "auto"
              // e.g. @fo:clip="rect(0in, 0.07874in, 0in, 0.07874in)"
              // PROBLEM: The width & height is not accessible
            } else if (propName.equals("fo:clip")) {
              String clipping = odfProps.get("fo:clip");
              int start = clipping.indexOf("rect(");
              int end = clipping.indexOf(")");
              if (start > -1 && end > -1) {
                clipping = clipping.substring(start + 5, end);
              }
              String clips[] = clipping.split(", ");
              if (clips.length != 4) {
                // fallback as some documents to not behave as the standard explains
                clips = clipping.split(" ");
              }
              int clipTop = MapHelper.normalizeLength(clips[0]);
              imageProps.put("cropTop", clipTop);
              int clipRight = MapHelper.normalizeLength(clips[1]);
              imageProps.put("cropRight", clipRight);
              int clipBottom = MapHelper.normalizeLength(clips[2]);
              imageProps.put("cropBottom", clipBottom);
              int clipLeft = MapHelper.normalizeLength(clips[3]);
              imageProps.put("cropLeft", clipLeft);

              /* http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#property-style_wrap
              The style:wrap attribute specifies how text is displayed around a frame or graphic object.
              The defined values for the style:wrap attribute are:
              * biggest: text may wrap around the shape where the difference to the left or right page or column border is largest.
              * Mode=square Side=largest
              * dynamic: text may wrap around both sides of the shape. The space for wrapping is set by the style:wrap-dynamic-threshold attribute. 20.393
              * UNSUPPORTED
              * left: text wraps around the left side of the shape.
              * Mode=square  Side=left
              * none: text does not wrap around the shape.
              * Mode=topAndBottom
              * parallel: text wraps around both sides of the shape.
              * Mode=square  Side=both
              * right: text wraps around the right side of the shape.
              * Mode=square  Side=right
              * run-through: text runs through the shape.
              * Mode=through Side=both   */
              /*
              Changes API: textWrapMode   One of 'none', 'square', 'tight', 'through', or 'topAndBottom'. (IMHO 'none' == 'topAndBottom', but the latter is implemented)
              Changes API: textWrapSide   Sides where text wraps around the image (only used if textWrapMode is set to 'square', 'tight', or 'through') (1). One of ['both', 'left', 'right', 'largest'   */
            } else if (propName.equals("style:wrap")) {
              String wrap = odfProps.get("style:wrap");

              if (wrap.equals("biggest")) {
                newProps.put("textWrapMode", "square");
                newProps.put("textWrapSide", "largest");
              } else if (wrap.equals("left")) {
                newProps.put("textWrapMode", "square");
                newProps.put("textWrapSide", "left");
              } else if (wrap.equals("none")) {
                newProps.put("textWrapMode", "topAndBottom");
              } else if (wrap.equals("parallel")) {
                newProps.put("textWrapMode", "square");
                newProps.put("textWrapSide", "both");
              } else if (wrap.equals("right")) {
                newProps.put("textWrapMode", "square");
                newProps.put("textWrapSide", "right");
              } else if (wrap.equals("run-through")) {
                newProps.put("textWrapMode", "through");
                newProps.put("textWrapSide", "both");
              }
            } else if (propName.equals("draw:stroke")) {
              String stroke = odfProps.get("draw:stroke");
              if (stroke.equals("none")) {
                lineProps.put("style", "none");
              } else {
                lineProps.put("style", stroke.equals("solid") ? "solid" : "dashed");
                // TODO: map draw:stroke-dash into the line type
                if (!lineProps.has("width")) {
                  lineProps.put("width", 1);
                }
                if (!lineProps.has("type")) {
                  lineProps.put("type", "solid");
                }
              }
            } else if (propName.equals("svg:stroke-color")) {
              String color = odfProps.get("svg:stroke-color");
              lineProps.put("color", MapHelper.createColorMap(color));
            } else if (propName.equals("svg:stroke-width")) {
              lineProps.put("width", MapHelper.normalizeLength(odfProps.get("svg:stroke-width")));
            } else if (propName.equals("style:run-through")) {
              String runThrough = odfProps.get(propName);
              if ("background".equals(runThrough)) {
                newProps.put("anchorBehindDoc", true);
              }
            }
          } catch (JSONException ex) {
            Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
        // border handling: text frames have a border attribute, shapes _can_ have stroke
        try {
          if (newProps.has("border") || newProps.has("borderTop")) {
            // convert to "line"
            JSONObject currentBorder =
                newProps.has("border")
                    ? newProps.getJSONObject("border")
                    : newProps.getJSONObject("borderTop");
            if (currentBorder.has("style")) {
              String style = currentBorder.getString("style");
              if (style.equals("none")) {
                lineProps.put("type", "none");
              } else {
                if (style.equals("dashed")) {
                  lineProps.put("style", "dashed");
                } else if (style.equals("dotted")) {
                  lineProps.put("style", "dotted");
                } else {
                  lineProps.put("style", "solid");
                }
                lineProps.put("type", "solid");
              }
            }
            if (currentBorder.has("width")) {
              lineProps.put("width", currentBorder.get("width"));
            }
            if (currentBorder.has("color")) {
              lineProps.put("color", currentBorder.get("color"));
            }
          }
          if (newProps.has("borderTop")) {
            newProps.remove("borderTop");
          }
          if (newProps.has("borderBottom")) {
            newProps.remove("borderBottom");
          }
          if (newProps.has("borderLeft")) {
            newProps.remove("borderLeft");
          }
          if (newProps.has("borderRight")) {
            newProps.remove("borderRight");
          }
          if (newProps.has("border")) {
            newProps.remove("border");
          }
        } catch (JSONException e) {
          // no handline required
        }
      } // NOTE: HEADER FOOTER ARE SOMEHOW (ASYMETRIC) NESTED AMONG PAGE PROPERTIES.. (ToDo: Unused
      // yet!)
      else if (styleFamilyGroup.equals("headerFooter")) {
        boolean borderToBeDone = true;
        boolean paddingToBeDone = true;
        boolean marginToBeDone = true;
        for (String propName : odfProps.keySet()) {
          try {
            if (propName.contains("margin")) {
              if (marginToBeDone) {
                mapMargin(newProps, odfProps);
                marginToBeDone = false;
              }
            } else if (propName.contains("padding")) {
              if (paddingToBeDone) {
                mapPadding(newProps, odfProps);
                paddingToBeDone = false;
              }
            } else if (propName.contains("border")) {
              if (borderToBeDone) {
                mapBorder(newProps, odfProps);
                borderToBeDone = false;
              }
            } else if (propName.equals("fo:background-color")) {
              propValue = odfProps.get("fo:background-color");
              Map<String, String> color = MapHelper.createColorMap(propValue);
              newProps.put("fillColor", color);
            }
          } catch (JSONException ex) {
            Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      }
    }
    JSONObject retObject = new JSONObject();
    try {
      retObject.put(styleFamilyGroup, newProps);
      if (shapeProps != null && shapeProps.length() > 0) {
        retObject.put("shape", shapeProps);
      }
      if (imageProps != null && imageProps.length() > 0) {
        retObject.put("image", imageProps);
      }
      if (lineProps != null) {
        retObject.put("line", lineProps);
      }
      if (fillProps != null && fillProps.length() > 0) {
        retObject.put("fill", fillProps);
      }
    } catch (JSONException e) {
      // no handling required
    }
    return retObject;
  }

  /** see Changes API LineHeight */
  private static JSONObject createLineHeightMap(String lineHeightValue) {
    JSONObject lineHeight = new JSONObject();
    try {
      /**
       * Usually normal is given by the font and 110% to 120% of the font-size (see
       * http://www.w3.org/TR/CSS2/visudet.html#line-height), browser even show 1.1 to 1.3, but in
       * office applications normal is ALWAYS 100%, therefore it will be mapped see as well
       * http://www.w3.org/TR/xsl/#line-height
       */
      if (lineHeightValue.equals("normal")) {
        lineHeight.put("type", "percent");
        lineHeight.put("value", "100");
      } else if (lineHeightValue.contains("%")) {
        lineHeight.put("type", "percent");
        lineHeight.put(
            "value",
            Integer.parseInt(
                lineHeightValue.subSequence(0, lineHeightValue.indexOf('%')).toString()));
      } else {
        lineHeight.put("type", "fixed");
        lineHeight.put("value", MapHelper.normalizeLength(lineHeightValue));
      }
    } catch (JSONException ex) {
      Logger.getLogger(JsonOperationProducer.class.getName()).log(Level.SEVERE, null, ex);
    }
    return lineHeight;
  }

  private static void mapBorder(JSONObject newProps, Map<String, String> odfProps)
      throws JSONException {
    String propValue;
    JSONObject defaultBorder = null;
    Integer defaultSpace = null;
    // fo:border="0.5pt solid #000000"
    if (odfProps.containsKey("fo:border")) {
      propValue = odfProps.get("fo:border");
      defaultBorder = MapHelper.createBorderMap(propValue);
      if (odfProps.containsKey("fo:padding")) {
        propValue = odfProps.get("fo:padding");
        if (!propValue.contains(PERCENT)) {
          defaultSpace = MapHelper.normalizeLength(propValue);
        }
        defaultBorder.put("space", defaultSpace);
      }
    }
    if (odfProps.containsKey("fo:border-left")) {
      propValue = odfProps.get("fo:border-left");
      JSONObject border = MapHelper.createBorderMap(propValue);
      if (odfProps.containsKey("fo:padding-left")) {
        propValue = odfProps.get("fo:padding-left");
        if (!propValue.contains(PERCENT)) {
          border.put("space", MapHelper.normalizeLength(propValue));
        }
      } else if (defaultSpace != null) {
        border.put("space", defaultSpace);
      }
      newProps.put("borderLeft", border);
    } else {
      newProps.put("borderLeft", defaultBorder);
    }
    if (odfProps.containsKey("fo:border-top")) {
      propValue = odfProps.get("fo:border-top");
      JSONObject border = MapHelper.createBorderMap(propValue);
      if (odfProps.containsKey("fo:padding-top")) {
        propValue = odfProps.get("fo:padding-top");
        if (!propValue.contains(PERCENT)) {
          border.put("space", MapHelper.normalizeLength(propValue));
        }
      } else if (defaultSpace != null) {
        border.put("space", defaultSpace);
      }
      newProps.put("borderTop", border);
    } else {
      newProps.put("borderTop", defaultBorder);
    }
    if (odfProps.containsKey("fo:border-right")) {
      propValue = odfProps.get("fo:border-right");
      JSONObject border = MapHelper.createBorderMap(propValue);
      if (odfProps.containsKey("fo:padding-right")) {
        propValue = odfProps.get("fo:padding-right");
        if (!propValue.contains(PERCENT)) {
          border.put("space", MapHelper.normalizeLength(propValue));
        }
      } else if (defaultSpace != null) {
        border.put("space", defaultSpace);
      }
      newProps.put("borderRight", border);
    } else {
      newProps.put("borderRight", defaultBorder);
    }
    if (odfProps.containsKey("fo:border-bottom")) {
      propValue = odfProps.get("fo:border-bottom");
      JSONObject border = MapHelper.createBorderMap(propValue);
      if (odfProps.containsKey("fo:padding-bottom")) {
        propValue = odfProps.get("fo:padding-bottom");
        if (!propValue.contains(PERCENT)) {
          border.put("space", MapHelper.normalizeLength(propValue));
        }
      } else if (defaultSpace != null) {
        border.put("space", defaultSpace);
      }
      newProps.put("borderBottom", border);
    } else {
      newProps.put("borderBottom", defaultBorder);
    }
  }

  public static String mapFoTextAlign(String propValue) {
    if (propValue.equals("start") || propValue.equals("left")) {
      // ToDo: I8N requires a correspondonce to the writing direction
      propValue = "left";
    } else if (propValue.equals("end") || propValue.equals("right")) {
      // ToDo: I8N requires a correspondonce to the writing direction
      propValue = "right";
    }
    return propValue;
  }

  /** To do, we should add the String directly for mapping instead adding the Map */
  private static void mapMargin(JSONObject newProps, Map<String, String> odfProps)
      throws JSONException {
    String propValue;
    Integer defaultLength = null;
    if (odfProps.containsKey("fo:margin")) {
      propValue = odfProps.get("fo:margin");
      if (!propValue.contains(PERCENT)) {
        defaultLength = MapHelper.normalizeLength(propValue);
      }
    }
    if (odfProps.containsKey("fo:margin-left")) {
      propValue = odfProps.get("fo:margin-left");
      if (!propValue.contains(PERCENT)) {
        newProps.put("marginLeft", MapHelper.normalizeLength(propValue));
        newProps.put("indentLeft", MapHelper.normalizeLength(propValue)); // FIX API
      }
    } else {
      if (defaultLength != null) {
        newProps.put("marginLeft", defaultLength);
        newProps.put("indentLeft", defaultLength); // FIX API
      }
    }
    if (odfProps.containsKey("fo:margin-top")) {
      propValue = odfProps.get("fo:margin-top");
      if (!propValue.contains(PERCENT)) {
        newProps.put("marginTop", MapHelper.normalizeLength(propValue));
      }
    } else {
      if (defaultLength != null) {
        newProps.put("marginTop", defaultLength);
      }
    }
    if (odfProps.containsKey("fo:margin-right")) {
      propValue = odfProps.get("fo:margin-right");

      if (!propValue.contains(PERCENT)) {
        newProps.put("marginRight", MapHelper.normalizeLength(propValue));
        newProps.put("indentRight", MapHelper.normalizeLength(propValue)); // FIX API
      }
    } else {
      if (defaultLength != null) {
        newProps.put("marginRight", defaultLength);
        newProps.put("indentRight", defaultLength); // FIX API
      }
    }
    if (odfProps.containsKey("fo:margin-bottom")) {
      propValue = odfProps.get("fo:margin-bottom");
      if (!propValue.contains(PERCENT)) {
        newProps.put("marginBottom", MapHelper.normalizeLength(propValue));
      }
    } else {
      if (defaultLength != null) {
        newProps.put("marginBottom", defaultLength);
      }
    }
  }

  private static void mapPadding(JSONObject newProps, Map<String, String> odfProps)
      throws JSONException {
    String propValue;
    Integer defaultLength = null;
    if (odfProps.containsKey("fo:padding")) {
      propValue = odfProps.get("fo:padding");
      if (!propValue.contains(PERCENT)) {
        defaultLength = MapHelper.normalizeLength(propValue);
      }
    }
    if (odfProps.containsKey("fo:padding-left")) {
      propValue = odfProps.get("fo:padding-left");
      if (!propValue.contains(PERCENT)) {
        newProps.put("paddingLeft", MapHelper.normalizeLength(propValue));
      }
    } else {
      if (defaultLength != null) {
        newProps.put("paddingLeft", defaultLength);
      }
    }
    if (odfProps.containsKey("fo:padding-top")) {
      propValue = odfProps.get("fo:padding-top");
      if (!propValue.contains(PERCENT)) {
        newProps.put("paddingTop", MapHelper.normalizeLength(propValue));
      }
    } else {
      if (defaultLength != null) {
        newProps.put("paddingTop", defaultLength);
      }
    }
    if (odfProps.containsKey("fo:padding-right")) {
      propValue = odfProps.get("fo:padding-right");

      if (!propValue.contains(PERCENT)) {
        newProps.put("paddingRight", MapHelper.normalizeLength(propValue));
      }
    } else {
      if (defaultLength != null) {
        newProps.put("paddingRight", defaultLength);
      }
    }
    if (odfProps.containsKey("fo:padding-bottom")) {
      propValue = odfProps.get("fo:padding-bottom");
      if (!propValue.contains(PERCENT)) {
        newProps.put("paddingBottom", MapHelper.normalizeLength(propValue));
      }
    } else {
      if (defaultLength != null) {
        newProps.put("paddingBottom", defaultLength);
      }
    }
  }

  public static Map<String, Object> mapStyleProperties(
      OdfStylableElement styleElement, Map<String, Map<String, String>> allOdfProps) {
    // returns the set of allowed properties, e.g. cell, paragraph, text for a cell with properties
    Map<String, OdfStylePropertiesSet> familyPropertyGroups =
        Component.getAllStyleGroupingIdProperties(styleElement);
    return mapStyleProperties(familyPropertyGroups, allOdfProps);
  }

  public static Map<String, Object> mapStyleProperties(
      Map<String, OdfStylePropertiesSet> familyPropertyGroups,
      Map<String, Map<String, String>> allOdfProps) {

    Map<String, Object> allProps = new HashMap<String, Object>();
    for (String styleFamilyKey : familyPropertyGroups.keySet()) {
      // NOTE: Perhaps we should first inherit everything from the parents and map afterwards
      // the ODF properties of one family group
      JSONObject mappedProps =
          MapHelper.mapProperties(styleFamilyKey, allOdfProps.get(styleFamilyKey));
      try {
        if (mappedProps != null) {
          if (mappedProps.has(styleFamilyKey)
              && mappedProps.getJSONObject(styleFamilyKey).length() != 0) {
            allProps.put(styleFamilyKey, mappedProps.getJSONObject(styleFamilyKey));
          }
          if (styleFamilyKey.equals("drawing")) {
            if (mappedProps.has("shape") && mappedProps.getJSONObject("shape").length() != 0) {
              allProps.put("shape", mappedProps.getJSONObject("shape"));
            }
            if (mappedProps.has("image") && mappedProps.getJSONObject("image").length() != 0) {
              allProps.put("image", mappedProps.getJSONObject("image"));
            }
            if (mappedProps.has("line") && mappedProps.getJSONObject("line").length() != 0) {
              allProps.put("line", mappedProps.getJSONObject("line"));
            }
            if (mappedProps.has("fill") && mappedProps.getJSONObject("fill").length() != 0) {
              allProps.put("fill", mappedProps.getJSONObject("fill"));
            }
          }
        }

      } catch (JSONException e) {
        // no handling required
      }
    }
    return allProps;
  }

  public static Map<String, Object> getMappedStyleProperties(OdfStyle style) {
    // Mapped properties
    Map<String, Object> mappedFormatting = null;
    if (style != null) {
      // Intermediate ODF properties
      Map<String, Map<String, String>> allOdfProps = new HashMap<String, Map<String, String>>();
      // The property groups for this component, e.g. cell, paragraph, text for a cell with
      // properties
      Map<String, OdfStylePropertiesSet> familyPropertyGroups =
          Component.getAllStyleGroupingIdProperties(style.getFamily());
      // get all ODF properties from this style
      getStyleProperties(style, familyPropertyGroups, allOdfProps);
      // mapping the ODF attribute style props to our component properties
      mappedFormatting = mapStyleProperties(familyPropertyGroups, allOdfProps);
    }
    return mappedFormatting;
  }

  public static void getStyleProperties(
      OdfStyleBase style,
      OdfStylableElement styleElement,
      Map<String, Map<String, String>> allOdfProps) {
    // returns the set of allowed properties, e.g. cell, paragraph, text for a cell with properties
    Map<String, OdfStylePropertiesSet> familyPropertyGroups =
        Component.getAllStyleGroupingIdProperties(styleElement);
    getStyleProperties(style, familyPropertyGroups, allOdfProps);
  }

  public static void getStyleProperties(
      OdfStyleBase style,
      Map<String, OdfStylePropertiesSet> familyPropertyGroups,
      Map<String, Map<String, String>> allOdfProps) {
    if (style != null) {
      for (String styleFamilyKey : familyPropertyGroups.keySet()) {
        // the ODF properties of one family group
        Map<String, String> odfProps = new HashMap<String, String>();
        OdfStylePropertiesSet key = familyPropertyGroups.get(styleFamilyKey);
        OdfStylePropertiesBase propsElement = style.getPropertiesElement(key);
        if (propsElement != null) {
          NamedNodeMap attrs = propsElement.getAttributes();
          String name = null;
          for (int i = 0; i < attrs.getLength(); i++) {
            name = null;
            Attr prop = (Attr) attrs.item(i);

            // normalize XML prefix of ODF attributes to the prefixes used in the specification
            name = OdfNamespace.getNamespace(prop.getNamespaceURI()).getPrefix();
            if (name == null) {
              name = prop.getPrefix();
            }
            if (name != null) {
              name = name + ":" + prop.getName();
            } else {
              name = prop.getName();
            }
            odfProps.put(name, prop.getValue());
          }
          if (propsElement instanceof StyleParagraphPropertiesElement) {
            StyleParagraphPropertiesElement paraPropsElement =
                (StyleParagraphPropertiesElement) propsElement;
            NodeList tabStops =
                paraPropsElement.getElementsByTagNameNS(
                    OdfDocumentNamespace.STYLE.getUri(), "tab-stops");
            if (tabStops.getLength() > 0) {
              StyleTabStopsElement tabStopsElement = (StyleTabStopsElement) tabStops.item(0);
              NodeList tabStopList =
                  tabStopsElement.getElementsByTagNameNS(
                      OdfDocumentNamespace.STYLE.getUri(), "tab-stop");
              int size = tabStopList.getLength();
              int tabNumber = -1;
              for (int i = 0; i < size; i++) {
                Node child = tabStopList.item(i);
                if (!(child instanceof Element)) {
                  // avoid line breaks, when XML is indented
                  continue;
                } else {
                  tabNumber++;
                  extractTabulatorLeaderText((StyleTabStopElement) child, odfProps, tabNumber);
                  extractTabulatorPosition((StyleTabStopElement) child, odfProps, tabNumber);
                  extractTabulatorType((StyleTabStopElement) child, odfProps, tabNumber);
                }
              }
            }
          }
        }
        if (!odfProps.isEmpty()) {
          allOdfProps.put(styleFamilyKey, odfProps);
        }
      }
    }
  }

  /**
   * style:type fillChar String Type of fill character. 'dot' Tab will be filled with dot chars: . .
   * . . . . . . . . . 'hyphen' Tab will be filled with hyphen chars: - - - - - - - - - 'underscore'
   * Tab will be filled with underscore chars _ _ _ _ _ _ _ none Tab is just empty space.
   */
  private static void extractTabulatorType(
      StyleTabStopElement tabStopElement, Map<String, String> odfProps, int tabNumber) {
    String tabType = tabStopElement.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "type");

    if (tabType != null && !tabType.isEmpty()) {
      if (tabType.equals("char")) {
        tabType = "decimal";
      }
      odfProps.put("tab_Type" + tabNumber, tabType);
    }
  }

  /**
   * style:char fillChar String Type of fill character. 'dot' Tab will be filled with dot chars: . .
   * . . . . . . . . . 'hyphen' Tab will be filled with hyphen chars: - - - - - - - - - 'underscore'
   * Tab will be filled with underscore chars _ _ _ _ _ _ _ none Tab is just empty space.
   */
  private static void extractTabulatorLeaderText(
      StyleTabStopElement tabStopElement, Map<String, String> odfProps, int tabNumber) {
    String tabLeaderText =
        tabStopElement.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "leader-text");

    if (tabLeaderText != null & !tabLeaderText.isEmpty()) {
      if (tabLeaderText.equals(Constants.DOT_CHAR)) {
        odfProps.put("tab_LeaderText" + tabNumber, Constants.DOT);
      } else if (tabLeaderText.equals(Constants.HYPHEN_CHAR)) {
        odfProps.put("tab_LeaderText" + tabNumber, Constants.HYPHEN);
      } else if (tabLeaderText.equals(Constants.UNDERSCORE_CHAR)) {
        odfProps.put("tab_LeaderText" + tabNumber, Constants.UNDERSCORE);
      } else if (tabLeaderText.equals(Constants.SPACE_CHAR)) {
        odfProps.put("tab_LeaderText" + tabNumber, Constants.NONE);
      }
    }
  }

  /**
   * In LO/AO Tabs are counted from the start of text and the fo:left-margin have to be added.
   * Microsoft Office does not behave this way. For the filter a compatibility flag was added to the
   * settings.xml <config:config-item config:name="TabsRelativeToIndent"
   * config:type="boolean">false</config:config-item>
   */
  private static boolean hasTabsRelativeToIndent(OdfElement element) {
    boolean isTabsRelativeToIndent = false;
    OdfSchemaDocument schemaDoc =
        (OdfSchemaDocument) ((OdfFileDom) element.getOwnerDocument()).getDocument();
    if (schemaDoc instanceof OdfTextDocument) {
      isTabsRelativeToIndent = ((OdfTextDocument) schemaDoc).hasTabsRelativeToIndent();
    }
    return isTabsRelativeToIndent;
  }

  /** style:position pos Integer 0 Tab stop position in 1/100th mm. */
  private static void extractTabulatorPosition(
      StyleTabStopElement tabStopElement, Map<String, String> odfProps, int tabNumber) {
    String tabPos = tabStopElement.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "position");
    int tabIndent = 0;
    /**
     * >20 years ago someone in the StarOffice Writer team thought it was a brilliant idea to align
     * tabs from the real text start. Therefore to ODF from LO/AO we need to addChild the
     * left-margin to the tab-position, to get the REAL tab-position. If the tabs are relative to
     * indent (margin) we need to addChild the left margin. But there is a compatibility mode for
     * the MS Office formats filter, where this can be disabled: <config:config-item
     * config:name="TabsRelativeToIndent" config:type="boolean">false</config:config-item>
     */
    if (odfProps.containsKey("fo:margin-left") && hasTabsRelativeToIndent(tabStopElement)) {
      // get the variable from the text document
      String propValue = odfProps.get("fo:margin-left");
      if (!propValue.contains(PERCENT)) {
        tabIndent = MapHelper.normalizeLength(propValue);
      }
    }
    if (tabPos.isEmpty()) {
      LOG.severe("There should be only be a length, but it has been: '" + tabPos + "'");
    } else {
      odfProps.put(
          "tab_Pos" + tabNumber, Integer.toString(MapHelper.normalizeLength(tabPos) + tabIndent));
    }
  }

  // convert xmlschema-2 date to double
  public static Double dateToDouble(Object value) {
    Double ret = new Double(0.);
    if (value != null && value instanceof String) {
      // ISO 8601 formatter for date-time without time zone.
      FastDateFormat fdf = DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT;
      Date date = null;
      try {
        date = fdf.parse((String) value);
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        long diff = cal.getTimeInMillis() + 2209161600000l; // 30.12.1899
        ret = diff / 86400000.;
      } catch (ParseException ex) {
        Logger.getLogger(MapHelper.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    return ret;
  }
  // convert xmlschema-2 duration to double

  public static Double timeToDouble(Object value) {
    Double ret = new Double(0.);
    if (value != null && value instanceof String) {
      // PThhHmmMss.sssS
      try {
        String duration = (String) value;
        int length = duration.length();
        double hours = 0;
        double mins = 0;
        double millisecs = 0.;
        if (length >= 11) {
          int hPos = duration.indexOf('H');
          int mPos = duration.indexOf('M');

          hours = Integer.parseInt(duration.substring(2, hPos));
          mins = Integer.parseInt(duration.substring(hPos + 1, mPos));
          millisecs = Double.parseDouble(duration.substring(mPos + 1, length - 1));
          ret = hours / 24. + mins / 1440. + millisecs / 86400.;
        }
      } catch (IllegalArgumentException e) {

      }
    }
    return ret;
  }

  /**
   * get number format attribute via data-style-name attribute from one of the different number
   * format style elements either from office styles or automatic styles names are (implicitly!)
   * unique so it can only be found in one of the two containers
   *
   * @param jsonStyleProperties
   * @param stringProperties
   * @param autoStyle
   * @param autoStyles auto style interface, is allowed to b null
   * @param officeStyles should always be set
   * @return
   */
  public static boolean putNumberFormat(
      Map<String, Object> jsonStyleProperties,
      Map<String, Map<String, String>> stringProperties,
      OdfStyle autoStyle,
      OdfStylesBase autoStyles,
      OdfStylesBase officeStyles) {
    boolean ret = false;
    if (autoStyle != null && autoStyle.hasAttribute("style:data-style-name")) {
      String dataStyleName = autoStyle.getAttribute("style:data-style-name");
      String formatCode = "";
      DataStyleElement dataStyleBase = null;
      dataStyleBase = officeStyles.getAllDataStyles().get(dataStyleName);
      if (dataStyleBase == null && autoStyles != null) {
        dataStyleBase = autoStyles.getAllDataStyles().get(dataStyleName);
      }
      if (dataStyleBase != null) {
        formatCode = dataStyleBase.getFormat(true);
      }
      if (!formatCode.isEmpty()) {
        ret = true;
        JSONObject jsonCellProps = new JSONObject();
        if (jsonStyleProperties != null) {
          if (jsonStyleProperties.containsKey("cell")) {
            jsonCellProps = (JSONObject) jsonStyleProperties.get("cell");
          }
          try {
            jsonCellProps.put("formatCode", formatCode);
          } catch (JSONException e) {
          }
          jsonStyleProperties.put("cell", jsonCellProps);
        } else {
          Map<String, String> stringCellProps = new HashMap<String, String>();
          if (stringProperties.containsKey("cell")) {
            stringCellProps = stringProperties.get("cell");
          }
          stringCellProps.put("numberformat_code", formatCode);
          stringProperties.put("cell", stringCellProps);
        }
      }
    }
    return ret;
  }

  public static String removeQuotedAndColor(String compareCode) {
    while (compareCode.contains("\"")) {
      int firstQuote = compareCode.indexOf("\"");
      if (firstQuote == compareCode.length() - 1) {
        break;
      }
      int secondQuote = compareCode.indexOf("\"", firstQuote + 1);
      if (secondQuote < 0) {
        break;
      }
      String tmp = compareCode.substring(0, firstQuote);
      tmp += compareCode.substring(secondQuote + 1);
      compareCode = tmp;
    }
    int openBracket = compareCode.indexOf("[");
    boolean hasCurrency = false;
    while (openBracket >= 0) {
      int closeBracket = compareCode.indexOf("]", openBracket);
      if (closeBracket > openBracket) {
        String innerText = compareCode.substring(openBracket + 1, closeBracket);
        if (innerText.startsWith("$") && innerText.length() > 1) {
          hasCurrency = true;
        }
        compareCode =
            compareCode.substring(0, openBracket) + compareCode.substring(closeBracket + 1);
      }
      openBracket = compareCode.indexOf("[");
    }
    if (hasCurrency) {
      compareCode += "[$]"; // reinsert currency marker
    }
    return compareCode;
  }

  public static Value detectFormatType(String code) {
    String compareCode = code.replaceAll("AM", "xx9999xx");
    compareCode = compareCode.replaceAll("PM", "xx9999xx");
    // remove quoted parts
    compareCode = MapHelper.removeQuotedAndColor(compareCode);
    Value type = OfficeValueTypeAttribute.Value.VOID;
    if (compareCode.contains("@")) {
      type = OfficeValueTypeAttribute.Value.STRING;
    } else if (compareCode.equals("BOOLEAN")) {
      type = OfficeValueTypeAttribute.Value.BOOLEAN;
    } else if (compareCode.contains("[$")) {
      type = OfficeValueTypeAttribute.Value.CURRENCY;
    } else if (compareCode.contains("%")) {
      type = OfficeValueTypeAttribute.Value.PERCENTAGE;
    } else if (compareCode.contains("D")
        || compareCode.contains("d")
        || compareCode.contains("M")
        || compareCode.contains("Y")
        || compareCode.contains("y")
        || compareCode.contains("WW")
        || compareCode.contains("ww")) {
      type = OfficeValueTypeAttribute.Value.DATE;
    } else if (compareCode.contains("h")
        || compareCode.contains("H")
        || compareCode.contains("m")
        || compareCode.contains("s")
        || compareCode.contains("S")
        || compareCode.contains("xx9999xx")) {
      type = OfficeValueTypeAttribute.Value.TIME;
    } else {
      type = OfficeValueTypeAttribute.Value.FLOAT;
    }
    return type;
  }

  public static String findOrCreateDataStyle(String code, long id, OdfFileDom fileDom) {
    OdfDocument odfDocument = (OdfDocument) fileDom.getDocument();
    String ret = "";
    try {
      OdfContentDom contentDom = odfDocument.getContentDom();
      OdfOfficeStyles officeStyles = odfDocument.getStylesDom().getOfficeStyles();
      OdfOfficeAutomaticStyles autoStyles = contentDom.getAutomaticStyles();
      Value type =
          OfficeValueTypeAttribute.Value
              .VOID; // .BOOLEAN CURRENCY DATE FLOAT PERCENTAGE STRING TIME
      // if id > -1 then assign an appropriate format and type
      if (id > 0 && id < 164) {
        switch ((int) id) {
          case 1:
            code = "0";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 2:
            code = "0.00";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 3:
            code = "#,##0";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 4:
            code = "#,##0.00";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 5:
            code = "#,##0[$$-409]";
            type = OfficeValueTypeAttribute.Value.CURRENCY;
            break;
          case 6:
            code = "#,##0[$$-409];[RED]-#,##0[$$-409]";
            type = OfficeValueTypeAttribute.Value.CURRENCY;
            break;
          case 7:
            code = "#,##0.00[$$-409]";
            type = OfficeValueTypeAttribute.Value.CURRENCY;
            break;
          case 8:
            code = "#,##0.00[$$-409];[RED]-#,##0.00[$$-409]";
            type = OfficeValueTypeAttribute.Value.CURRENCY;
            break;
          case 9:
            code = "0%";
            type = OfficeValueTypeAttribute.Value.PERCENTAGE;
            break;
          case 10:
            code = "0.00%";
            type = OfficeValueTypeAttribute.Value.PERCENTAGE;
            break;
          case 11:
            code = "0.00E+00";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 12:
            code = "# ?/?";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 13:
            code = "# ??/??";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 14:
            code = "MM/DD/YYYY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 15:
            code = "D-MMM-YY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 16:
            code = "D-MMM";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 17:
            code = "MMM-YY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 18:
            code = "H:MM AM/PM";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 19:
            code = "H:MM:SS AM/PM";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 20:
            code = "H:MM";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 21:
            code = "H:MM:SS";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 22:
            code = "M/D/YYYY H:MM";
            type = OfficeValueTypeAttribute.Value.DATE;
            break; // "DATETIME_SYSTEM_SHORT_HHMM,
          case 23: // code = "0";                                      type =
            // OfficeValueTypeAttribute.Value.FLOAT; break;
          case 24: // code = "0";                                      type =
            // OfficeValueTypeAttribute.Value.FLOAT; break;
          case 25: // code = "0";                                      type =
            // OfficeValueTypeAttribute.Value.FLOAT; break;
          case 26:
            code = "0";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 27:
          case 28:
          case 29:
          case 30:
          case 31:
            code = "M/D/YYYY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 32:
          case 33:
          case 34:
          case 35:
            code = "HH:MM:SS";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 36:
            code = "M/D/YYYY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 37:
            code = "#,##0_);(#,##0)";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 38:
            code = "#,##0_);[RED](#,##0)";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 39:
            code = "#,##0.00_);(#,##0.00)";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 40:
            code = "#,##0.00_);[RED](#,##0.00)";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 41:
            code = "_-* #,##0 _\u20ac_-;-* #,##0 _\u20ac_-;_-* \"-\" _\u20ac_-;_-@_-";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 42:
            code = "_-* #,##0 \"\u20ac\"_-;-* #,##0 \"\u20ac\"_-;_-* \"-\" \"\u20ac\"_-;_-@_-";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 43:
            code = "_-* #,##0.00 _\u20ac_-;-* #,##0.00 _\u20ac_-;_-* \"-\"?? _\u20ac_-;_-@_-";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 44:
            code =
                "_-* #,##0.00 \"\u20ac\"_-;-* #,##0.00 \"\u20ac\"_-;_-* \"-\"?? \"\u20ac\"_-;_-@_-";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 45:
            code = "MM:SS";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 46:
            code = "[h]:mm:ss";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 47:
            code = "mm:ss.0";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 48:
            code = "##0.0E+0";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 49:
            code = "@";
            type = OfficeValueTypeAttribute.Value.STRING;
            break;
          case 50:
            code = "M/D/YYYY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 51:
          case 52:
          case 53:
          case 54:
          case 55:
          case 56:
          case 57:
          case 58:
            code = "M/D/YYYY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 59:
            code = "0";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 60:
            code = "0.00";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 61:
            code = "#,##0";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 62:
            code = "#,##0.00";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 63:
            code = "#,##0 \"\u20ac\";-#,##0 \"\u20ac\"";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 64:
            code = "#,##0 \"\u20ac\";[RED]-#,##0 \"\u20ac\"";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 65:
            code = "#,##0.00 \"\u20ac\";-#,##0.00 \"\u20ac\"";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 66:
            code = "#,##0.00 \"\u20ac\";[RED]-#,##0.00 \"\u20ac\"";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 67:
            code = "0%";
            type = OfficeValueTypeAttribute.Value.PERCENTAGE;
            break;
          case 68:
            code = "0.00%";
            type = OfficeValueTypeAttribute.Value.PERCENTAGE;
            break;
          case 69:
            code = "0.00E+00";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 70:
            code = "# ?/?";
            type = OfficeValueTypeAttribute.Value.FLOAT;
            break;
          case 71: // code = "M/D/YYYY"; type = OfficeValueTypeAttribute.Value.DATE; break;
          case 72:
            code = "M/D/YYYY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 73:
            code = "D-MMM-YY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 74:
            code = "D-MMM";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 75:
            code = "MMM-YY";
            type = OfficeValueTypeAttribute.Value.DATE;
            break;
          case 76:
            code = "HH:MM";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 77:
            code = "HH:MM:SS";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 78:
            code = "M/D/YYYY H:MM";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 79:
            code = "mm:ss";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 80:
            code = "[h]:mm:ss";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
          case 81:
            code = "mm:ss.0";
            type = OfficeValueTypeAttribute.Value.TIME;
            break;
        }
      }
      if (!code.isEmpty() && (type == OfficeValueTypeAttribute.Value.VOID)) {
        type = detectFormatType(code);
      }
      if (!code.isEmpty() && type != OfficeValueTypeAttribute.Value.VOID) {
        String foundStyleName = null;
        boolean foundInAutoStyles = false;
        HashMap<String, DataStyleElement> officeDataStyles = officeStyles.getAllDataStyles();
        HashMap<String, DataStyleElement> autoDataStyles = autoStyles.getAllDataStyles();
        for (Entry<String, DataStyleElement> autoNumberStyle : autoDataStyles.entrySet()) {
          if (autoNumberStyle.getValue().getFormat(true).equals(code)) {
            foundStyleName = autoNumberStyle.getKey();
            ret = foundStyleName;
            foundInAutoStyles = true;
            break;
          }
        }
        if (!foundInAutoStyles) {
          for (Entry<String, DataStyleElement> officeNumberStyle : officeDataStyles.entrySet()) {
            if (officeNumberStyle.getValue().getFormat(true).equals(code)) {
              foundStyleName = officeNumberStyle.getKey();
              ret = foundStyleName;
              break;
            }
          }
        }
        if (foundStyleName == null) {
          // find free name
          int newIndex = autoDataStyles.size() + officeDataStyles.size();
          String newDataStyleName = "N" + newIndex;
          while (autoDataStyles.containsKey(newDataStyleName)
              || officeDataStyles.containsKey(newDataStyleName)) {
            newDataStyleName = "N" + ++newIndex;
          }
          final DataStyleElement newStyle =
              autoStyles.createDataStyle(type, code, newDataStyleName);
          if (id == 14) {
            newStyle.setAttributeNS(
                OdfDocumentNamespace.NUMBER.getUri(), "number:automatic-order", "true");
            newStyle.removeAttributeNS(OdfDocumentNamespace.NUMBER.getUri(), "format-source");
          }
          ret = newDataStyleName;
        }
      }
    } catch (SAXException e) {
      Logger.getLogger(MapHelper.class.getName()).log(Level.SEVERE, null, e);
    } catch (IOException ex) {
      Logger.getLogger(MapHelper.class.getName()).log(Level.SEVERE, null, ex);
    }
    return ret;
  }

  private static void fillLocaleMaps() {
    if (localeToLanguageMap == null) {
      localeToLanguageMap = new HashMap<String, Integer>();
      languageToLocaleMap = new HashMap<Integer, String>();

      class StringAndInt {

        public String locale;
        public int msValue;

        public StringAndInt(String s, int v) {
          locale = s;
          msValue = v;
        }
      }

      StringAndInt mapping[] = {
        new StringAndInt("af-ZA", 0x436),
        new StringAndInt("sq-AL", 0x41c),
        new StringAndInt("gsw-FR", 0x484),
        new StringAndInt("am-ET", 0x45e),
        new StringAndInt("ar-DZ", 0x1401),
        new StringAndInt("ar-BH", 0x3c01),
        new StringAndInt("ar-EG", 0xc01),
        new StringAndInt("ar-IQ", 0x801),
        new StringAndInt("ar-JO", 0x2c01),
        new StringAndInt("ar-KW", 0x3401),
        new StringAndInt("ar-LB", 0x3001),
        new StringAndInt("ar-LY", 0x1001),
        new StringAndInt("ar-MA", 0x1801),
        new StringAndInt("ar-OM", 0x2001),
        new StringAndInt("ar-QA", 0x4001),
        new StringAndInt("ar-SA", 0x401),
        new StringAndInt("ar-SY", 0x2801),
        new StringAndInt("ar-TN", 0x1c01),
        new StringAndInt("ar-AE", 0x3801),
        new StringAndInt("ar-YE", 0x2401),
        new StringAndInt("ar", 0x1),
        new StringAndInt("hy-AM", 0x42b),
        new StringAndInt("as-IN", 0x44d),
        new StringAndInt("az", 0x2c),
        new StringAndInt("az-cyrillic", 0x82c),
        new StringAndInt("az-AZ", 0x42c),
        new StringAndInt("ba-RU", 0x46d),
        new StringAndInt("eu", 0x42d),
        new StringAndInt("be-BY", 0x423),
        new StringAndInt("bn-IN", 0x445),
        new StringAndInt("bn-BD", 0x845),
        new StringAndInt("bs-BA", 0x141a),
        //            new StringAndInt("", 0x201a),
        new StringAndInt("br-FR", 0x47e),
        new StringAndInt("bg-BG", 0x402),
        new StringAndInt("my-MM", 0x455),
        new StringAndInt("ca-ES", 0x403),
        new StringAndInt("chr-US", 0x45c),
        new StringAndInt("zh", 0x4),
        new StringAndInt("zh-HK", 0xc04),
        new StringAndInt("zh-MO", 0x1404),
        new StringAndInt("zh-CN", 0x804),
        new StringAndInt("zh-SG", 0x1004),
        new StringAndInt("zh-TW", 0x404),
        new StringAndInt("co-FR", 0x483),
        new StringAndInt("hr-HR", 0x41a),
        new StringAndInt("hr-BA", 0x101a),
        new StringAndInt("cs-CZ", 0x405),
        new StringAndInt("da-DK", 0x406),
        new StringAndInt("gbz-AF", 0x48c),
        new StringAndInt("dv-MV", 0x465),
        new StringAndInt("nl-NL", 0x413),
        new StringAndInt("nl-BE", 0x813),
        new StringAndInt("bin-NG", 0x466),
        new StringAndInt("en", 0x9),
        new StringAndInt("en-AU", 0xc09),
        new StringAndInt("en-BZ", 0x2809),
        new StringAndInt("en-CA", 0x1009),
        new StringAndInt("en-BS", 0x2409),
        new StringAndInt("en-IE", 0x1809),
        new StringAndInt("en-HK", 0x3c09),
        new StringAndInt("en-IN", 0x4009),
        new StringAndInt("en-ID", 0x3809),
        new StringAndInt("en-JM", 0x2009),
        new StringAndInt("en-MY", 0x4409),
        new StringAndInt("en-NZ", 0x1409),
        new StringAndInt("en-PH", 0x3409),
        new StringAndInt("en-ZA", 0x1c09),
        new StringAndInt("en-SG", 0x4809),
        new StringAndInt("en-TT", 0x2c09),
        new StringAndInt("en-GB", 0x809),
        new StringAndInt("en-US", 0x409),
        new StringAndInt("en-ZW", 0x3009),
        new StringAndInt("et-EE", 0x425),
        new StringAndInt("fo-FO", 0x438),
        new StringAndInt("fa-IR", 0x429),
        new StringAndInt("fil-PH", 0x464),
        new StringAndInt("fi-FI", 0x40b),
        new StringAndInt("fr-FR", 0x40c),
        new StringAndInt("fr-BE", 0x80c),
        new StringAndInt("fr-CM", 0x2c0c),
        new StringAndInt("fr-CA", 0xc0c),
        new StringAndInt("fr-CI", 0x300c),
        new StringAndInt("fr-HT", 0x3c0c),
        new StringAndInt("fr-LU", 0x140c),
        new StringAndInt("fr-ML", 0x340c),
        new StringAndInt("fr-MC", 0x180c),
        new StringAndInt("fr-MA", 0x380c),
        new StringAndInt("fr", 0xe40c),
        new StringAndInt("fr-RE", 0x200c),
        new StringAndInt("fr-SN", 0x280c),
        new StringAndInt("fr-CH", 0x100c),
        new StringAndInt("fr", 0x1c0c),
        new StringAndInt("fr-CD", 0x240c),
        new StringAndInt("fy-NL", 0x462),
        new StringAndInt("ff-NG", 0x467),
        new StringAndInt("ga-IE", 0x83c),
        new StringAndInt("gd-GB", 0x43c),
        new StringAndInt("gl-ES", 0x456),
        new StringAndInt("ka-GE", 0x437),
        new StringAndInt("de-DE", 0x407),
        new StringAndInt("de-AT", 0xc07),
        new StringAndInt("de-LI", 0x1407),
        new StringAndInt("de-LU", 0x1007),
        new StringAndInt("de-CH", 0x807),
        new StringAndInt("el-GR", 0x408),
        new StringAndInt("gug-PY", 0x474),
        new StringAndInt("gu-IN", 0x447),
        new StringAndInt("ha-NG", 0x468),
        new StringAndInt("haw-US", 0x475),
        new StringAndInt("he-IL", 0x40d),
        new StringAndInt("hi-IN", 0x439),
        new StringAndInt("hu-HU", 0x40e),
        //            new StringAndInt("", 0x469),
        new StringAndInt("is-IS", 0x40f),
        new StringAndInt("ig-NG", 0x470),
        new StringAndInt("id-ID", 0x421),
        //            new StringAndInt("", 0x45d),
        new StringAndInt("iu-CA", 0x85d),
        new StringAndInt("it-IT", 0x410),
        new StringAndInt("it-CH", 0x810),
        new StringAndInt("ja-JP", 0x411),
        new StringAndInt("kl-GL", 0x46f),
        new StringAndInt("kn-IN", 0x44b),
        new StringAndInt("kr-NG", 0x471),
        new StringAndInt("ks", 0x460),
        new StringAndInt("ks-IN", 0x860),
        new StringAndInt("kk-KZ", 0x43f),
        new StringAndInt("km-KH", 0x453),
        new StringAndInt("qut-GT", 0x486),
        new StringAndInt("rw-RW", 0x487),
        new StringAndInt("ky-KG", 0x440),
        new StringAndInt("kok-IN", 0x457),
        new StringAndInt("ko-KR", 0x412),
        new StringAndInt("ko-KR", 0x812),
        new StringAndInt("lo-LA", 0x454),
        new StringAndInt("la-VA", 0x476),
        new StringAndInt("lv-LV", 0x426),
        new StringAndInt("lt-LT", 0x427),
        new StringAndInt("lt-LT", 0x827),
        new StringAndInt("lb-LU", 0x46e),
        new StringAndInt("mk-MK", 0x42f),
        new StringAndInt("ms", 0x3e),
        new StringAndInt("ml-IN", 0x44c),
        new StringAndInt("ms-BN", 0x83e),
        new StringAndInt("ms-MY", 0x43e),
        new StringAndInt("mt-MT", 0x43a),
        new StringAndInt("mni-IN", 0x458),
        new StringAndInt("mi-NZ", 0x481),
        new StringAndInt("arn-CL", 0x47a),
        new StringAndInt("mr-IN", 0x44e),
        new StringAndInt("moh-CA", 0x47c),
        new StringAndInt("mn-MN", 0x450),
        new StringAndInt("mn-MN", 0x850),
        new StringAndInt("ne-NP", 0x461),
        new StringAndInt("ne-IN", 0x861),
        new StringAndInt("no-NO", 0x14),
        new StringAndInt("nb-NO", 0x414),
        new StringAndInt("nn-NO", 0x814),
        new StringAndInt("oc-FR", 0x482),
        new StringAndInt("or-IN", 0x448),
        new StringAndInt("om-ET", 0x472),
        new StringAndInt("pap-AN", 0x479),
        new StringAndInt("ps-AF", 0x463),
        new StringAndInt("pl-PL", 0x415),
        new StringAndInt("pt-PT", 0x816),
        new StringAndInt("pt-BR", 0x416),
        new StringAndInt("pa-IN", 0x446),
        new StringAndInt("lah-PK", 0x846),
        new StringAndInt("qu-BO", 0x46b),
        new StringAndInt("qu-EC", 0x86b),
        new StringAndInt("qu-PE", 0xc6b),
        new StringAndInt("rm-CH", 0x417),
        new StringAndInt("ro-RO", 0x418),
        new StringAndInt("ro-MD", 0x818),
        new StringAndInt("ru-RU", 0x419),
        new StringAndInt("mo-MD", 0x819),
        new StringAndInt("se-NO", 0x43b),
        new StringAndInt("smn-FI", 0x243b),
        new StringAndInt("smj-NO", 0x103b),
        new StringAndInt("smj-SE", 0x143b),
        new StringAndInt("se-FI", 0xc3b),
        new StringAndInt("se-SE", 0x83b),
        new StringAndInt("sms-FI", 0x203b),
        new StringAndInt("sma-NO", 0x183b),
        new StringAndInt("sma-SE", 0x1c3b),
        new StringAndInt("sa-IN", 0x44f),
        new StringAndInt("nso-ZA", 0x46c),
        new StringAndInt("sr", 0x1a),
        new StringAndInt("sr-YU", 0xc1a),
        new StringAndInt("sr-BA", 0x1c1a),
        new StringAndInt("sh-YU", 0x81a),
        new StringAndInt("sh-BA", 0x181a),
        new StringAndInt("sh", 0x7c1a),
        new StringAndInt("st-ZA", 0x430),
        new StringAndInt("sd-IN", 0x459),
        new StringAndInt("sd-PK", 0x859),
        new StringAndInt("si-LK", 0x45b),
        new StringAndInt("sk-SK", 0x41b),
        new StringAndInt("sl-SI", 0x424),
        new StringAndInt("so-SO", 0x477),
        new StringAndInt("hsb-DE", 0x42e),
        new StringAndInt("dsb-DE", 0x82e),
        new StringAndInt("es-ES", 0x40a),
        new StringAndInt("es-AR", 0x2c0a),
        new StringAndInt("es-BO", 0x400a),
        new StringAndInt("es-CL", 0x340a),
        new StringAndInt("es-CO", 0x240a),
        new StringAndInt("es-CR", 0x140a),
        new StringAndInt("es-DO", 0x1c0a),
        new StringAndInt("es-EC", 0x300a),
        new StringAndInt("es-SV", 0x440a),
        new StringAndInt("es-GT", 0x100a),
        new StringAndInt("es-HN", 0x480a),
        new StringAndInt("es", 0xe40a),
        new StringAndInt("es-MX", 0x80a),
        new StringAndInt("es-ES", 0xc0a),
        new StringAndInt("es-NI", 0x4c0a),
        new StringAndInt("es-PA", 0x180a),
        new StringAndInt("es-PY", 0x3c0a),
        new StringAndInt("es-PE", 0x280a),
        new StringAndInt("es-PR", 0x500a),
        new StringAndInt("es-US", 0x540a),
        new StringAndInt("es-UY", 0x380a),
        new StringAndInt("es-VE", 0x200a),
        new StringAndInt("sw-KE", 0x441),
        new StringAndInt("sv-SE", 0x41d),
        new StringAndInt("sv-FI", 0x81d),
        new StringAndInt("syr-TR", 0x45a),
        new StringAndInt("tg-TJ", 0x428),
        //            new StringAndInt("", 0x45f),
        //            new StringAndInt("", 0x85f),
        new StringAndInt("ta-IN", 0x449),
        new StringAndInt("tt-RU", 0x444),
        new StringAndInt("te-IN", 0x44a),
        new StringAndInt("th-TH", 0x41e),
        new StringAndInt("bo-CN", 0x451),
        new StringAndInt("dz-BT", 0x851),
        new StringAndInt("ti-ER", 0x873),
        new StringAndInt("ti-ET", 0x473),
        new StringAndInt("ts-ZA", 0x431),
        new StringAndInt("tn-ZA", 0x432),
        new StringAndInt("tr-TR", 0x41f),
        new StringAndInt("tk-TM", 0x442),
        new StringAndInt("ug-CN", 0x480),
        new StringAndInt("uk-UA", 0x422),
        new StringAndInt("ur", 0x20),
        new StringAndInt("ur-IN", 0x820),
        new StringAndInt("ur-PK", 0x420),
        //            new StringAndInt("", 0x843),
        new StringAndInt("uz-UZ", 0x443),
        new StringAndInt("ve-ZA", 0x433),
        new StringAndInt("vi-VN", 0x42a),
        new StringAndInt("cy-GB", 0x452),
        new StringAndInt("wo-SN", 0x488),
        new StringAndInt("xh-ZA", 0x434),
        new StringAndInt("sah-RU", 0x485),
        new StringAndInt("ii-CN", 0x478),
        new StringAndInt("yi-IL", 0x43d),
        new StringAndInt("yo-NG", 0x46a),
        new StringAndInt("zu-ZA", 0x435),
        null
      };
      int index = 0;
      while (mapping[index] != null) {
        localeToLanguageMap.put(mapping[index].locale, mapping[index].msValue);
        languageToLocaleMap.put(mapping[index].msValue, mapping[index].locale);
        ++index;
      }
    }
  }

  /**
   * converts a language code from a string and interpretes the number as hex value and returns the
   * a locale or null if either the format is wrong or no locale is known
   */
  public static String getLocaleFromLangCode(String languageCode) {
    if (languageToLocaleMap == null) {
      fillLocaleMaps();
    }
    String ret = "";
    try {
      int languageValue = Integer.parseInt(languageCode, 16);
      ret = languageToLocaleMap.get(languageValue);
    } catch (NumberFormatException e) {
      // no handling required
    }
    return ret;
  }

  /**
   * converts locale information to a hex value string but without a hex marker like '0x' returns
   * null if no value can be found
   */
  public static String getMSLangCode(String language, String country) {
    if (localeToLanguageMap == null) {
      fillLocaleMaps();
    }
    String cmpString = new String(language);
    if (!country.isEmpty()) {
      cmpString += "-" + country;
    }
    Integer entry = localeToLanguageMap.get(cmpString);
    if (entry != null) {
      return Integer.toHexString(entry.intValue());
    }
    return "";
  }

  public static void moveParaToCell(Map<String, Object> allHardFormatting) {
    // move some paragraph properties to cell properties
    if (allHardFormatting != null && allHardFormatting.containsKey("paragraph")) {
      JSONObject paraProps = (JSONObject) allHardFormatting.get("paragraph");
      if (paraProps.has("alignment")) {
        if (!allHardFormatting.containsKey("cell")) {
          allHardFormatting.put("cell", new JSONObject());
        }
        JSONObject cellProps = (JSONObject) allHardFormatting.get("cell");
        try {
          cellProps.put("alignHor", paraProps.opt("alignment"));
          paraProps.remove("alignment");
        } catch (JSONException e) {
          // no handling required
        }
      }
    }
  }
}