/* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you 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 . */ package org.openoffice.xmerge.converter.xml.sxc; import java.awt.Color; import org.w3c.dom.NodeList; import org.w3c.dom.Node; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Element; import org.openoffice.xmerge.converter.xml.Style; import org.openoffice.xmerge.converter.xml.StyleCatalog; import org.openoffice.xmerge.util.Debug; /** * Represents a text {@code Style} in an OpenOffice document. */ public class CellStyle extends Style implements Cloneable { private Format fmt = new Format(); /** * Constructor for use when going from DOM to client device format. * * @param node The style:style {@code Node} containing the * {@code Style}. (This {@code Node} is assumed have a * family attribute of text). * @param sc The {@code StyleCatalog}, which is used for looking up * ancestor {@code Style} objects. */ public CellStyle(Node node, StyleCatalog sc) { super(node, sc); // Run through the attributes of this node, saving // the ones we're interested in. NamedNodeMap attrNodes = node.getAttributes(); if (attrNodes != null) { int len = attrNodes.getLength(); for (int i = 0; i < len; i++) { Node attr = attrNodes.item(i); handleAttribute(attr.getNodeName(), attr.getNodeValue()); } } // Look for children. Only ones we care about are "style:properties" // nodes. If any are found, recursively traverse them, passing // along the style element to add properties to. if (node.hasChildNodes()) { NodeList children = node.getChildNodes(); int len = children.getLength(); for (int i = 0; i < len; i++) { Node child = children.item(i); String nodeName = child.getNodeName(); if (nodeName.equals("style:properties")) { NamedNodeMap childAttrNodes = child.getAttributes(); if (childAttrNodes != null) { int nChildAttrNodes = childAttrNodes.getLength(); for (int j = 0; j < nChildAttrNodes; j++) { Node attr = childAttrNodes.item(j); handleAttribute(attr.getNodeName(), attr.getNodeValue()); } } } } } } /** * Constructor for use when going from client device format to DOM. * * @param name Name of cell {@code Style}. Can be {@code null}. * @param family Family of text {@code Style} (usually text). Can * be {@code null}. * @param parent Name of parent text {@code Style}, or {@code null} for * none. * @param fmt size in points. * @param sc The {@code StyleCatalog}, which is used for looking up * ancestor {@code Style} objects. */ public CellStyle(String name, String family, String parent,Format fmt, StyleCatalog sc) { super(name, family, parent, sc); this.fmt = fmt; } /** * Returns the {@code Format} object for this particular style. * * @return the {@code Format} object */ public Format getFormat() { return fmt; } /** * Parse a color specification of the form {@literal #rrggbb}. * * @param value {@code Color} specification to parse. * * @return The {@code Color} associated the value. */ private Color parseColorString(String value) { int red = 0; int green = 0; int blue = 0; try { // Assume color value is of form #rrggbb red = Integer.parseInt(value.substring(1, 3), 16); green = Integer.parseInt(value.substring(3, 5), 16); blue = Integer.parseInt(value.substring(5, 7), 16); } catch (NumberFormatException e) { Debug.log(Debug.ERROR, "Problem parsing a color string", e); } catch (IndexOutOfBoundsException e) { Debug.log(Debug.ERROR, "Problem parsing a color string", e); } return new Color(red, green, blue, 0); } /** * Set an attribute. * * @param attr The attribute to set. * @param value The attribute value to set. */ private void handleAttribute(String attr, String value) { if (attr.equals("fo:font-weight")) { fmt.setAttribute(Format.BOLD, value.equals("bold")); } else if (attr.equals("fo:font-style")) { if (value.equals("italic") || value.equals("oblique")) fmt.setAttribute(Format.ITALIC, true); else if (value.equals("normal")) fmt.setAttribute(Format.ITALIC, false); } else if (attr.equals("style:text-underline")) { fmt.setAttribute(Format.UNDERLINE, !value.equals("none")); } else if (attr.equals("style:text-crossing-out")) { fmt.setAttribute(Format.STRIKETHRU, !value.equals("none")); } else if (attr.equals("style:text-position")) { if (value.startsWith("super ")) fmt.setAttribute(Format.SUPERSCRIPT, true); else if (value.startsWith("sub ")) fmt.setAttribute(Format.SUBSCRIPT, true); else if (value.startsWith("0% ")) fmt.setAttribute(Format.SUPERSCRIPT | Format.SUBSCRIPT, false); else { String firstPart = value.substring(0, value.indexOf(' ')); if (firstPart.endsWith("%")) { firstPart = firstPart.substring(0, value.indexOf('%')); int amount; try { amount = Integer.parseInt(firstPart); } catch (NumberFormatException e) { amount = 0; Debug.log(Debug.ERROR, "Problem with style:text-position tag", e); } if (amount < 0) fmt.setAttribute(Format.SUBSCRIPT, true); else if (amount > 0) fmt.setAttribute(Format.SUPERSCRIPT, false); } } } else if (attr.equals("fo:font-size")) { if (value.endsWith("pt")) { String num = value.substring(0, value.length() - 2); fmt.setFontSize(Integer.parseInt(num)); } } else if (attr.equals("style:font-name")) fmt.setFontName(value); else if (attr.equals("fo:color")) fmt.setForeground(parseColorString(value)); else if (attr.equals("fo:background-color")) fmt.setBackground(parseColorString(value)); else if (attr.equals("fo:text-align")) { if(value.equals("center")) { fmt.setAlign(Format.CENTER_ALIGN); } else if(value.equals("end")) { fmt.setAlign(Format.RIGHT_ALIGN); } else if(value.equals("start")) { fmt.setAlign(Format.LEFT_ALIGN); } } else if (attr.equals("fo:vertical-align")) { if(value.equals("top")) { fmt.setVertAlign(Format.TOP_ALIGN); } else if(value.equals("middle")) { fmt.setVertAlign(Format.MIDDLE_ALIGN); } else if(value.equals("bottom")) { fmt.setVertAlign(Format.BOTTOM_ALIGN); } } else if (attr.equals("fo:border")) { fmt.setAttribute(Format.TOP_BORDER, !value.equals("none")); fmt.setAttribute(Format.BOTTOM_BORDER, !value.equals("none")); fmt.setAttribute(Format.LEFT_BORDER, !value.equals("none")); fmt.setAttribute(Format.RIGHT_BORDER, !value.equals("none")); } else if (attr.equals("fo:border-top")) { fmt.setAttribute(Format.TOP_BORDER, !value.equals("none")); } else if (attr.equals("fo:border-bottom")) { fmt.setAttribute(Format.BOTTOM_BORDER, !value.equals("none")); } else if (attr.equals("fo:border-left")) { fmt.setAttribute(Format.LEFT_BORDER, !value.equals("none")); } else if (attr.equals("fo:border-right")) { fmt.setAttribute(Format.RIGHT_BORDER, !value.equals("none")); } else if (attr.equals("fo:wrap-option")) { fmt.setAttribute(Format.WORD_WRAP, value.equals("wrap")); } else if (isIgnored(attr)) {} else { Debug.log(Debug.INFO, "CellStyle Unhandled: " + attr + "=" + value); } } /** * Return a {@code Style} object corresponding to this one, but with all of * the inherited information from parent {@code Style} objects filled in. * *

The object returned will be a new object, not a reference to this * object, even if it does not need any information added.

* * @return The {@code StyleCatalog} in which to look up ancestors. */ @Override public Style getResolved() { // Create a new object to return, which is a clone of this one. CellStyle resolved = null; try { resolved = (CellStyle)this.clone(); } catch (Exception e) { Debug.log(Debug.ERROR, "Can't clone", e); } // Look up the parentStyle. (If there is no style catalog // specified, we can't do any lookups.) CellStyle parentStyle = null; if (sc != null) { if (parent != null) { parentStyle = (CellStyle)sc.lookup(parent, family, null, this.getClass()); if (parentStyle == null) Debug.log(Debug.ERROR, "parent style lookup of " + parent + " failed!"); else parentStyle = (CellStyle)parentStyle.getResolved(); } else if (!name.equals("DEFAULT_STYLE")) { parentStyle = (CellStyle)sc.lookup("DEFAULT_STYLE", null, null, this.getClass()); } } // If we found a parent, for any attributes which we don't have // set, try to get the values from the parent. if (parentStyle != null) { parentStyle = (CellStyle)parentStyle.getResolved(); Format parentFormat = parentStyle.getFormat(); Format resolvedFormat = resolved.getFormat(); if ((fmt.getAlign() == Format.LEFT_ALIGN) && (parentFormat.getAlign() != Format.LEFT_ALIGN)) resolvedFormat.setAlign(parentFormat.getAlign()); if ((fmt.getVertAlign() == Format.BOTTOM_ALIGN) && (parentFormat.getVertAlign() != Format.BOTTOM_ALIGN)) resolvedFormat.setVertAlign(parentFormat.getVertAlign()); if ((fmt.getFontSize() == 0) && (parentFormat.getFontSize() != 0)) resolvedFormat.setFontSize(parentFormat.getFontSize()); if ((fmt.getFontName() == null) && (parentFormat.getFontName() != null)) resolvedFormat.setFontName(parentFormat.getFontName()); if ((fmt.getForeground() == null) && (parentFormat.getForeground() != null)) resolvedFormat.setForeground(parentFormat.getForeground()); if ((fmt.getBackground() == null) && (parentFormat.getBackground() != null)) resolvedFormat.setBackground(parentFormat.getBackground()); for (int m = Format.BOLD; m <= Format.SUBSCRIPT; m = m << 1) { if ((fmt.getAttribute(m)) && (parentFormat.getAttribute(m))) { resolvedFormat.setAttribute(m, parentFormat.getAttribute(m)); } } } return resolved; } /** * Create a new {@code Node} in the {@code Document}, and write this * {@code Style} to it. * * @param parentDoc Parent {@code Document} of the {@code Node} to create. * @param name Name to use for the new {@code Node} (e.g. * style:style) * * @return Created {@code Node}. */ @Override public Node createNode(org.w3c.dom.Document parentDoc, String name) { Element node = parentDoc.createElement(name); writeAttributes(node); return node; } /** * Return {@code true} if {@code style} specifies as much or less than this * {@code Style}, and nothing it specifies contradicts this {@code Style}. * * @param style The {@code Style} to check. * * @return {@code true} if {@code style} is a subset, {@code false} * otherwise. */ @Override public boolean isSubset(Style style) { if (style.getClass() != this.getClass()) return false; CellStyle tStyle = (CellStyle)style; Format rhs = tStyle.getFormat(); return fmt.isSubset(rhs); } /** * Write this {@code Style} object's attributes to a {@code Node} in the * {@code Document}. * * @param node The {@code Node} to add {@code Style} attributes. */ private void writeAttributes(Element node) { if (fmt.getAlign()==Format.RIGHT_ALIGN) node.setAttribute("fo:text-align", "end"); if (fmt.getAlign()==Format.LEFT_ALIGN) node.setAttribute("fo:text-align", "start"); if (fmt.getAlign()==Format.CENTER_ALIGN) node.setAttribute("fo:text-align", "center"); if (fmt.getVertAlign()==Format.TOP_ALIGN) node.setAttribute("fo:vertical-align", "top"); if (fmt.getVertAlign()==Format.MIDDLE_ALIGN) node.setAttribute("fo:vertical-align", "middle"); if (fmt.getVertAlign()==Format.BOTTOM_ALIGN) node.setAttribute("fo:vertical-align", "bottom"); if (fmt.getAttribute(Format.BOLD)) node.setAttribute("fo:font-weight", "bold"); if (fmt.getAttribute(Format.ITALIC)) node.setAttribute("fo:font-style", "italic"); if (fmt.getAttribute(Format.UNDERLINE)) node.setAttribute("style:text-underline", "single"); if (fmt.getAttribute(Format.STRIKETHRU)) node.setAttribute("style:text-crossing-out", "single-line"); if (fmt.getAttribute(Format.SUPERSCRIPT)) node.setAttribute("style:text-position", "super 58%"); if (fmt.getAttribute(Format.SUBSCRIPT)) node.setAttribute("style:text-position", "sub 58%"); if (fmt.getFontSize() != 0) { node.setAttribute("fo:font-size", Integer.toString(fmt.getFontSize()) + "pt"); } if (fmt.getFontName() != null) node.setAttribute("style:font-name", fmt.getFontName()); if (fmt.getForeground() != null) node.setAttribute("fo:color", buildColorString(fmt.getForeground())); if (fmt.getBackground() != null) node.setAttribute("fo:background-color", buildColorString(fmt.getBackground())); if (fmt.getAttribute(Format.TOP_BORDER)) node.setAttribute("fo:border-top", "0.0008inch solid #000000"); if (fmt.getAttribute(Format.BOTTOM_BORDER)) node.setAttribute("fo:border-bottom", "0.0008inch solid #000000"); if (fmt.getAttribute(Format.RIGHT_BORDER)) node.setAttribute("fo:border-right", "0.0008inch solid #000000"); if (fmt.getAttribute(Format.LEFT_BORDER)) node.setAttribute("fo:border-left", "0.0008inch solid #000000"); if (fmt.getAttribute(Format.WORD_WRAP)) node.setAttribute("fo:wrap-option", "wrap"); } /** * Given a {@code Color}, return a string of the form {@literal #rrggbb}. * * @param c The {@code Color} value. * * @return The {@code Color} value in the form {@literal #rrggbb}. */ private String buildColorString(Color c) { return String.format("#%06X", c.getRGB() & 0x00FFFFFF); } private static String[] ignored = { "style:text-autospace", "style:text-underline-color", "fo:margin-left", "fo:margin-right", "fo:text-indent", "fo:margin-top", "fo:margin-bottom", "text:line-number", "text:number-lines", "style:country-asian", "style:font-size-asian", "style:font-name-complex", "style:language-complex", "style:country-complex", "style:font-size-complex", "style:punctuation-wrap", "fo:language", "fo:country", "style:font-name-asian", "style:language-asian", "style:line-break", "fo:keep-with-next" }; /** * This code checks whether an attribute is one that we intentionally ignore. * * @param attribute The attribute to check. * * @return {@code true} if {@code attribute} can be ignored, otherwise * {@code false}. */ private boolean isIgnored(String attribute) { for (int i = 0; i < ignored.length; i++) { if (ignored[i].equals(attribute)) return true; } return false; } }