1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 package org.openoffice.xmerge.converter.xml.sxc; 25 26 import java.awt.Color; 27 28 import org.w3c.dom.NodeList; 29 import org.w3c.dom.Node; 30 import org.w3c.dom.NamedNodeMap; 31 import org.w3c.dom.Element; 32 33 import org.openoffice.xmerge.converter.xml.Style; 34 import org.openoffice.xmerge.converter.xml.StyleCatalog; 35 import org.openoffice.xmerge.util.Debug; 36 37 /** 38 * Represents a text <code>Style</code> in an OpenOffice document. 39 * 40 * @author Martin Maher 41 */ 42 public class CellStyle extends Style implements Cloneable { 43 44 private Format fmt = new Format(); 45 46 /** 47 * Constructor for use when going from DOM to client device format. 48 * 49 * @param node The <i>style:style</i> <code>Node</code> containing 50 * the <code>Style</code>. (This <code>Node</code> is 51 * assumed have a <i>family</i> attribute of <i>text</i>). 52 * @param sc The <code>StyleCatalog</code>, which is used for 53 * looking up ancestor <code>Style</code> objects. 54 */ CellStyle(Node node, StyleCatalog sc)55 public CellStyle(Node node, StyleCatalog sc) { 56 super(node, sc); 57 58 // Run through the attributes of this node, saving 59 // the ones we're interested in. 60 NamedNodeMap attrNodes = node.getAttributes(); 61 if (attrNodes != null) { 62 int len = attrNodes.getLength(); 63 for (int i = 0; i < len; i++) { 64 Node attr = attrNodes.item(i); 65 handleAttribute(attr.getNodeName(), attr.getNodeValue()); 66 } 67 } 68 69 // Look for children. Only ones we care about are "style:properties" 70 // nodes. If any are found, recursively traverse them, passing 71 // along the style element to add properties to. 72 if (node.hasChildNodes()) { 73 NodeList children = node.getChildNodes(); 74 int len = children.getLength(); 75 for (int i = 0; i < len; i++) { 76 Node child = children.item(i); 77 String name = child.getNodeName(); 78 if (name.equals("style:properties")) { 79 NamedNodeMap childAttrNodes = child.getAttributes(); 80 if (childAttrNodes != null) { 81 int nChildAttrNodes = childAttrNodes.getLength(); 82 for (int j = 0; j < nChildAttrNodes; j++) { 83 Node attr = childAttrNodes.item(j); 84 handleAttribute(attr.getNodeName(), 85 attr.getNodeValue()); 86 } 87 } 88 } 89 } 90 } 91 } 92 93 94 /** 95 * Constructor for use when going from client device format to DOM 96 * 97 * @param name Name of cell <code>Style</code>. Can be null. 98 * @param family Family of text <code>Style</code> (usually 99 * <i>text</i>). Can be null. 100 * @param parent Name of parent text <code>Style</code>, or null 101 * for none. 102 * @param fmt size in points. 103 * @param sc The <code>StyleCatalog</code>, which is used for 104 * looking up ancestor <code>Style</code> objects. 105 */ CellStyle(String name, String family, String parent,Format fmt, StyleCatalog sc)106 public CellStyle(String name, String family, String parent,Format fmt, StyleCatalog sc) { 107 super(name, family, parent, sc); 108 this.fmt = fmt; 109 } 110 111 /** 112 * Returns the <code>Format</code> object for this particular style 113 * 114 * @return the <code>Format</code> object 115 */ getFormat()116 public Format getFormat() { 117 return fmt; 118 } 119 120 /** 121 * Parse a color specification of the form <i>#rrggbb</i> 122 * 123 * @param value <code>Color</code> specification to parse. 124 * 125 * @return The <code>Color</code> associated the value. 126 */ parseColorString(String value)127 private Color parseColorString(String value) { 128 // Assume color value is of form #rrggbb 129 String r = value.substring(1, 3); 130 String g = value.substring(3, 5); 131 String b = value.substring(5, 7); 132 int red = 0; 133 int green = 0; 134 int blue = 0; 135 try { 136 red = Integer.parseInt(r, 16); 137 green = Integer.parseInt(g, 16); 138 blue = Integer.parseInt(b, 16); 139 } catch (NumberFormatException e) { 140 Debug.log(Debug.ERROR, "Problem parsing a color string", e); 141 } 142 return new Color(red, green, blue, 0); 143 } 144 145 146 /** 147 * Set an attribute. 148 * 149 * @param attr The attribute to set. 150 * @param value The attribute value to set. 151 */ handleAttribute(String attr, String value)152 private void handleAttribute(String attr, String value) { 153 154 if (attr.equals("fo:font-weight")) { 155 fmt.setAttribute(Format.BOLD, value.equals("bold")); 156 } 157 158 else if (attr.equals("fo:font-style")) { 159 if (value.equals("italic") || value.equals("oblique")) 160 fmt.setAttribute(Format.ITALIC, true); 161 else if (value.equals("normal")) 162 fmt.setAttribute(Format.ITALIC, false); 163 } 164 165 else if (attr.equals("style:text-underline")) { 166 fmt.setAttribute(Format.UNDERLINE, !value.equals("none")); 167 } 168 169 else if (attr.equals("style:text-crossing-out")) { 170 fmt.setAttribute(Format.STRIKETHRU, !value.equals("none")); 171 } 172 173 else if (attr.equals("style:text-position")) { 174 if (value.startsWith("super ")) 175 fmt.setAttribute(Format.SUPERSCRIPT, true); 176 else if (value.startsWith("sub ")) 177 fmt.setAttribute(Format.SUBSCRIPT, true); 178 else if (value.startsWith("0% ")) 179 fmt.setAttribute(Format.SUPERSCRIPT | Format.SUBSCRIPT, false); 180 else { 181 String firstPart = value.substring(0, value.indexOf(" ")); 182 if (firstPart.endsWith("%")) { 183 firstPart = firstPart.substring(0, value.indexOf("%")); 184 int amount; 185 try { 186 amount = Integer.parseInt(firstPart); 187 } catch (NumberFormatException e) { 188 amount = 0; 189 Debug.log(Debug.ERROR, "Problem with style:text-position tag", e); 190 } 191 if (amount < 0) fmt.setAttribute(Format.SUBSCRIPT, true); 192 else if (amount > 0) fmt.setAttribute(Format.SUPERSCRIPT, false); 193 } 194 } 195 } 196 197 else if (attr.equals("fo:font-size")) { 198 if (value.endsWith("pt")) { 199 String num = value.substring(0, value.length() - 2); 200 fmt.setFontSize(Integer.parseInt(num)); 201 } 202 } 203 204 else if (attr.equals("style:font-name")) 205 fmt.setFontName(value); 206 207 else if (attr.equals("fo:color")) 208 fmt.setForeground(parseColorString(value)); 209 210 else if (attr.equals("fo:background-color")) 211 fmt.setBackground(parseColorString(value)); 212 213 else if (attr.equals("fo:text-align")) { 214 if(value.equals("center")) { 215 fmt.setAlign(Format.CENTER_ALIGN); 216 } else if(value.equals("end")) { 217 fmt.setAlign(Format.RIGHT_ALIGN); 218 } else if(value.equals("start")) { 219 fmt.setAlign(Format.LEFT_ALIGN); 220 } 221 } 222 223 else if (attr.equals("fo:vertical-align")) { 224 if(value.equals("top")) { 225 fmt.setVertAlign(Format.TOP_ALIGN); 226 } else if(value.equals("middle")) { 227 fmt.setVertAlign(Format.MIDDLE_ALIGN); 228 } else if(value.equals("bottom")) { 229 fmt.setVertAlign(Format.BOTTOM_ALIGN); 230 } 231 } 232 233 else if (attr.equals("fo:border")) { 234 fmt.setAttribute(Format.TOP_BORDER, !value.equals("none")); 235 fmt.setAttribute(Format.BOTTOM_BORDER, !value.equals("none")); 236 fmt.setAttribute(Format.LEFT_BORDER, !value.equals("none")); 237 fmt.setAttribute(Format.RIGHT_BORDER, !value.equals("none")); 238 } 239 else if (attr.equals("fo:border-top")) { 240 fmt.setAttribute(Format.TOP_BORDER, !value.equals("none")); 241 } 242 else if (attr.equals("fo:border-bottom")) { 243 fmt.setAttribute(Format.BOTTOM_BORDER, !value.equals("none")); 244 } 245 else if (attr.equals("fo:border-left")) { 246 fmt.setAttribute(Format.LEFT_BORDER, !value.equals("none")); 247 } 248 else if (attr.equals("fo:border-right")) { 249 fmt.setAttribute(Format.RIGHT_BORDER, !value.equals("none")); 250 } 251 else if (attr.equals("fo:wrap-option")) { 252 fmt.setAttribute(Format.WORD_WRAP, value.equals("wrap")); 253 } 254 255 else if (isIgnored(attr)) {} 256 257 else { 258 Debug.log(Debug.INFO, "CellStyle Unhandled: " + attr + "=" + value); 259 } 260 } 261 262 263 /** 264 * Return a <code>Style</code> object corresponding to this one, 265 * but with all of the inherited information from parent 266 * <code>Style</code> objects filled in. The object returned will 267 * be a new object, not a reference to this object, even if it does 268 * not need any information added. 269 * 270 * @return The <code>StyleCatalog</code> in which to look up 271 * ancestors. 272 */ getResolved()273 public Style getResolved() { 274 // Create a new object to return, which is a clone of this one. 275 CellStyle resolved = null; 276 try { 277 resolved = (CellStyle)this.clone(); 278 } catch (Exception e) { 279 Debug.log(Debug.ERROR, "Can't clone", e); 280 } 281 282 // Look up the parentStyle. (If there is no style catalog 283 // specified, we can't do any lookups.) 284 CellStyle parentStyle = null; 285 if (sc != null) { 286 if (parent != null) { 287 parentStyle = (CellStyle)sc.lookup(parent, family, null, 288 this.getClass()); 289 if (parentStyle == null) 290 Debug.log(Debug.ERROR, "parent style lookup of " 291 + parent + " failed!"); 292 else 293 parentStyle = (CellStyle)parentStyle.getResolved(); 294 295 } else if (!name.equals("DEFAULT_STYLE")) { 296 parentStyle = (CellStyle)sc.lookup("DEFAULT_STYLE", null, 297 null, this.getClass()); 298 } 299 } 300 301 // If we found a parent, for any attributes which we don't have 302 // set, try to get the values from the parent. 303 if (parentStyle != null) { 304 parentStyle = (CellStyle)parentStyle.getResolved(); 305 Format parentFormat = parentStyle.getFormat(); 306 Format resolvedFormat = resolved.getFormat(); 307 308 if ((fmt.getAlign() == Format.LEFT_ALIGN) && (parentFormat.getAlign() != Format.LEFT_ALIGN)) 309 resolvedFormat.setAlign(parentFormat.getAlign()); 310 if ((fmt.getVertAlign() == Format.BOTTOM_ALIGN) && (parentFormat.getVertAlign() != Format.BOTTOM_ALIGN)) 311 resolvedFormat.setVertAlign(parentFormat.getVertAlign()); 312 if ((fmt.getFontSize() == 0) && (parentFormat.getFontSize() != 0)) 313 resolvedFormat.setFontSize(parentFormat.getFontSize()); 314 if ((fmt.getFontName() == null) && (parentFormat.getFontName() != null)) 315 resolvedFormat.setFontName(parentFormat.getFontName()); 316 if ((fmt.getForeground() == null) && (parentFormat.getForeground() != null)) 317 resolvedFormat.setForeground(parentFormat.getForeground()); 318 if ((fmt.getBackground() == null) && (parentFormat.getBackground() != null)) 319 resolvedFormat.setBackground(parentFormat.getBackground()); 320 for (int m = Format.BOLD; m <= Format.SUBSCRIPT; m = m << 1) { 321 if ((fmt.getAttribute(m)) && (parentFormat.getAttribute(m))) { 322 resolvedFormat.setAttribute(m, parentFormat.getAttribute(m)); 323 } 324 } 325 326 } 327 return resolved; 328 } 329 330 331 /** 332 * Create a new <code>Node</code> in the <code>Document</code>, and 333 * write this <code>Style</code> to it. 334 * 335 * @param parentDoc Parent <code>Document</code> of the 336 * <code>Node</code> to create. 337 * @param name Name to use for the new <code>Node</code> (e.g. 338 * <i>style:style</i>) 339 * 340 * @return Created <code>Node</code>. 341 */ createNode(org.w3c.dom.Document parentDoc, String name)342 public Node createNode(org.w3c.dom.Document parentDoc, String name) { 343 Element node = parentDoc.createElement(name); 344 writeAttributes(node); 345 return node; 346 } 347 348 349 /** 350 * Return true if <code>style</code> specifies as much or less 351 * than this <code>Style</code>, and nothing it specifies 352 * contradicts this <code>Style</code>. 353 * 354 * @param style The <code>Style</code> to check. 355 * 356 * @return true if <code>style</code> is a subset, false 357 * otherwise. 358 */ isSubset(Style style)359 public boolean isSubset(Style style) { 360 if (style.getClass() != this.getClass()) 361 return false; 362 CellStyle tStyle = (CellStyle)style; 363 364 Format rhs = tStyle.getFormat(); 365 366 if(!fmt.isSubset(rhs)) 367 return false; 368 369 return true; 370 } 371 372 373 /** 374 * Write this <code>Style</code> object's attributes to a 375 * <code>Node</code> in the <code>Document</code>. 376 * 377 * @param node The <code>Node</code> to add <code>Style</code> 378 * attributes. 379 */ writeAttributes(Element node)380 public void writeAttributes(Element node) { 381 382 if (fmt.getAlign()==Format.RIGHT_ALIGN) 383 node.setAttribute("fo:text-align", "end"); 384 385 if (fmt.getAlign()==Format.LEFT_ALIGN) 386 node.setAttribute("fo:text-align", "start"); 387 388 if (fmt.getAlign()==Format.CENTER_ALIGN) 389 node.setAttribute("fo:text-align", "center"); 390 391 if (fmt.getVertAlign()==Format.TOP_ALIGN) 392 node.setAttribute("fo:vertical-align", "top"); 393 394 if (fmt.getVertAlign()==Format.MIDDLE_ALIGN) 395 node.setAttribute("fo:vertical-align", "middle"); 396 397 if (fmt.getVertAlign()==Format.BOTTOM_ALIGN) 398 node.setAttribute("fo:vertical-align", "bottom"); 399 400 if (fmt.getAttribute(Format.BOLD)) 401 node.setAttribute("fo:font-weight", "bold"); 402 403 if (fmt.getAttribute(Format.ITALIC)) 404 node.setAttribute("fo:font-style", "italic"); 405 406 if (fmt.getAttribute(Format.UNDERLINE)) 407 node.setAttribute("style:text-underline", "single"); 408 409 if (fmt.getAttribute(Format.STRIKETHRU)) 410 node.setAttribute("style:text-crossing-out", "single-line"); 411 412 if (fmt.getAttribute(Format.SUPERSCRIPT)) 413 node.setAttribute("style:text-position", "super 58%"); 414 415 if (fmt.getAttribute(Format.SUBSCRIPT)) 416 node.setAttribute("style:text-position", "sub 58%"); 417 418 if (fmt.getFontSize() != 0) { 419 Integer fs = new Integer(fmt.getFontSize()); 420 node.setAttribute("fo:font-size", fs.toString() + "pt"); 421 } 422 423 if (fmt.getFontName() != null) 424 node.setAttribute("style:font-name", fmt.getFontName()); 425 426 if (fmt.getForeground() != null) 427 node.setAttribute("fo:color", buildColorString(fmt.getForeground())); 428 429 if (fmt.getBackground() != null) 430 node.setAttribute("fo:background-color", 431 buildColorString(fmt.getBackground())); 432 433 if (fmt.getAttribute(Format.TOP_BORDER)) 434 node.setAttribute("fo:border-top", "0.0008inch solid #000000"); 435 436 if (fmt.getAttribute(Format.BOTTOM_BORDER)) 437 node.setAttribute("fo:border-bottom", "0.0008inch solid #000000"); 438 439 if (fmt.getAttribute(Format.RIGHT_BORDER)) 440 node.setAttribute("fo:border-right", "0.0008inch solid #000000"); 441 442 if (fmt.getAttribute(Format.LEFT_BORDER)) 443 node.setAttribute("fo:border-left", "0.0008inch solid #000000"); 444 445 if (fmt.getAttribute(Format.WORD_WRAP)) 446 node.setAttribute("fo:wrap-option", "wrap"); 447 448 } 449 450 451 /** 452 * Given a <code>Color</code>, return a string of the form 453 * <i>#rrggbb</i>. 454 * 455 * @param c The <code>Color</code> value. 456 * 457 * @return The <code>Color</code> value in the form <i>#rrggbb</i>. 458 */ buildColorString(Color c)459 private String buildColorString(Color c) { 460 int v[] = new int[3]; 461 v[0] = c.getRed(); 462 v[1] = c.getGreen(); 463 v[2] = c.getBlue(); 464 String colorString = new String("#"); 465 for (int i = 0; i <= 2; i++) { 466 String xx = Integer.toHexString(v[i]); 467 if (xx.length() < 2) 468 xx = "0" + xx; 469 colorString += xx; 470 } 471 return colorString; 472 } 473 474 475 private static String[] ignored = { 476 "style:text-autospace", "style:text-underline-color", 477 "fo:margin-left", "fo:margin-right", "fo:text-indent", 478 "fo:margin-top", "fo:margin-bottom", "text:line-number", 479 "text:number-lines", "style:country-asian", 480 "style:font-size-asian", "style:font-name-complex", 481 "style:language-complex", "style:country-complex", 482 "style:font-size-complex", "style:punctuation-wrap", 483 "fo:language", "fo:country", 484 "style:font-name-asian", "style:language-asian", 485 "style:line-break", "fo:keep-with-next" 486 }; 487 488 489 /* 490 * This code checks whether an attribute is one that we 491 * intentionally ignore. 492 * 493 * @param attribute The attribute to check. 494 * 495 * @return true if <code>attribute</code> can be ignored, 496 * otherwise false. 497 */ isIgnored(String attribute)498 private boolean isIgnored(String attribute) { 499 for (int i = 0; i < ignored.length; i++) { 500 if (ignored[i].equals(attribute)) 501 return true; 502 } 503 return false; 504 } 505 } 506 507