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; 25 26 import org.w3c.dom.NodeList; 27 import org.w3c.dom.Node; 28 import org.w3c.dom.NamedNodeMap; 29 import org.w3c.dom.Element; 30 31 import org.openoffice.xmerge.util.Debug; 32 33 34 abstract class conversionAlgorithm { I(String val)35 int I(String val) { 36 return 0; 37 } 38 } 39 40 /* 41 * This algorithm expects only values in millimeters, e.g. "20.3mm". 42 */ 43 class horizSize extends conversionAlgorithm { I(String value)44 int I(String value) { 45 if (value.endsWith("mm")) { 46 float size = (float)0.0; 47 String num = value.substring(0, value.length() - 2); 48 try { 49 size = Float.parseFloat(num); 50 } catch (Exception e) { 51 Debug.log(Debug.ERROR, "Error parsing " + value, e); 52 } 53 size *= 100; 54 return (int)size; 55 } else { 56 Debug.log(Debug.ERROR, "Unexpected value (" + value 57 + ") in horizSize.I()"); 58 return 0; 59 } 60 } 61 } 62 63 64 /* 65 * This algorithm does line height - can be either millimeters or 66 * a percentage. 67 */ 68 class lineHeight extends conversionAlgorithm { I(String value)69 int I(String value) { 70 if (value.endsWith("mm")) { 71 float size = (float)0.0; 72 String num = value.substring(0, value.length() - 2); 73 try { 74 size = Float.parseFloat(num); 75 } catch (Exception e) { 76 Debug.log(Debug.ERROR, "Error parsing " + value, e); 77 } 78 size *= 100; 79 return (int)size; 80 } else if (value.endsWith("%")) { 81 float size = (float)0.0; 82 String num = value.substring(0, value.length() - 1); 83 try { 84 size = Float.parseFloat(num); 85 } catch (Exception e) { 86 Debug.log(Debug.ERROR, "Error parsing " + value, e); 87 } 88 int retval = (int) size; 89 retval |= ParaStyle.LH_PCT; 90 return retval; 91 } 92 return 0; 93 } 94 } 95 96 97 /** 98 * This class converts alignment values. 99 */ 100 class alignment extends conversionAlgorithm { I(String value)101 int I(String value) { 102 if (value.equals("end")) 103 return ParaStyle.ALIGN_RIGHT; 104 if (value.equals("right")) 105 return ParaStyle.ALIGN_RIGHT; 106 if (value.equals("center")) 107 return ParaStyle.ALIGN_CENTER; 108 if (value.equals("justify")) 109 return ParaStyle.ALIGN_JUST; 110 if (value.equals("justified")) 111 return ParaStyle.ALIGN_JUST; 112 if (value.equals("start")) 113 return ParaStyle.ALIGN_LEFT; 114 if (value.equals("left")) 115 return ParaStyle.ALIGN_LEFT; 116 Debug.log(Debug.ERROR, "Unknown string (" 117 + value + ") in alignment.I()"); 118 return ParaStyle.ALIGN_LEFT; 119 } 120 } 121 122 123 /** 124 * <p>This class represents a paragraph <code>Style</code>.</p> 125 * 126 * <p><table border="1" cellpadding="1"> 127 * <caption>Properties</caption> 128 * <tr><th>Attribute</th><th>Value</th></tr> 129 * <tr><td>MARGIN_LEFT </td><td>mm * 100</td></tr> 130 * <tr><td>MARGIN_RIGHT </td><td>mm * 100</td></tr> 131 * <tr><td>MARGIN_TOP </td><td>mm * 100 (space on top of paragraph)</td></tr> 132 * <tr><td>MARGIN_BOTTOM </td><td>mm * 100</td></tr> 133 * <tr><td>TEXT_INDENT </td><td>mm * 100 (first line indent)</td></tr> 134 * <tr><td>LINE_HEIGHT </td><td>mm * 100, unless or'ed with LH_PCT, in which 135 * case it is a percentage (e.g. 200% for double spacing) 136 * Can also be or'ed with LH_ATLEAST. Value is stored 137 * in bits indicated by LH_VALUEMASK. 138 * </td></tr> 139 * <tr><td>TEXT_ALIGN </td><td>ALIGN_RIGHT, ALIGN_CENTER, ALIGN_JUST, ALIGN_LEFT</td></tr> 140 * </table> 141 * 142 * @author David Proulx 143 */ 144 public class ParaStyle extends Style implements Cloneable { 145 146 /** The left margin property. */ 147 public static final int MARGIN_LEFT = 0; 148 /** The right margin property. */ 149 public static final int MARGIN_RIGHT = 1; 150 /** The top margin property. */ 151 public static final int MARGIN_TOP = 2; 152 /** The bottom margin property. */ 153 public static final int MARGIN_BOTTOM = 3; 154 /** Indent left property. */ 155 public static final int TEXT_INDENT = 4; 156 /** Indent right property. */ 157 public static final int LINE_HEIGHT = 5; 158 /** Align text property. */ 159 public static final int TEXT_ALIGN = 6; 160 // This must always be one more than highest property 161 /** Total number of properties. */ 162 protected static final int NR_PROPERTIES = 7; 163 164 /** 165 * Array of flags indicating which attributes are set for this 166 * paragraph <code>Style</code>. 167 */ 168 protected boolean isSet[] = new boolean[NR_PROPERTIES]; 169 /** Array of attribute values for this paragraph <code>tyle</code>. */ 170 protected int value[] = new int[NR_PROPERTIES]; 171 /** Array of attribute names for this paragraph <code>Style</code>. */ 172 protected String attrName[] = { 173 "fo:margin-left", 174 "fo:margin-right", 175 "fo:margin-top", 176 "fo:margin-bottom", 177 "fo:text-indent", 178 "fo:line-height", 179 "fo:text-align" 180 }; 181 182 /** Array of attribute structures for this paragraph <code>Style</code>. */ 183 protected Class algor[] = { 184 horizSize.class, 185 horizSize.class, 186 horizSize.class, 187 horizSize.class, 188 horizSize.class, 189 lineHeight.class, 190 alignment.class 191 }; 192 193 /** Align right. */ 194 public static final int ALIGN_RIGHT = 1; 195 /** Align center. */ 196 public static final int ALIGN_CENTER = 2; 197 /** Align justified. */ 198 public static final int ALIGN_JUST = 3; 199 /** Align left. */ 200 public static final int ALIGN_LEFT = 4; 201 202 /** Line height percentage. */ 203 public static final int LH_PCT = 0x40000000; 204 /** Line height minimum value. */ 205 public static final int LH_ATLEAST = 0x20000000; 206 /** Line height mask. */ 207 public static final int LH_VALUEMASK = 0x00FFFFFF; 208 209 /** Ignored tags. */ 210 private static String[] ignored = { 211 "style:font-name", "fo:font-size", "fo:font-weight", "fo:color", 212 "fo:language", "fo:country", "style:font-name-asian", 213 "style:font-size-asian", "style:language-asian", 214 "style:country-asian", "style:font-name-complex", 215 "style:font-size-complex", "style:language-complex", 216 "style:country-complex", "style:text-autospace", "style:punctuation-wrap", 217 "style:line-break", "fo:keep-with-next", "fo:font-style", 218 "text:number-lines", "text:line-number" 219 }; 220 221 222 /** 223 * Constructor for use when going from DOM to client device format. 224 * 225 * @param node A <i>style:style</i> <code>Node</code> which, which 226 * is assumed to have <i>family</i> attribute of 227 * <i>paragraph</i>. 228 * @param sc The <code>StyleCatalog</code>, which is used for 229 * looking up ancestor <code>Style</code> objects. 230 */ ParaStyle(Node node, StyleCatalog sc)231 public ParaStyle(Node node, StyleCatalog sc) { 232 233 super(node, sc); 234 235 // Look for children. Only ones we care about are "style:properties" 236 // nodes. If any are found, recursively traverse them, passing 237 // along the style element to add properties to. 238 // 239 if (node.hasChildNodes()) { 240 NodeList children = node.getChildNodes(); 241 int len = children.getLength(); 242 for (int i = 0; i < len; i++) { 243 Node child = children.item(i); 244 String name = child.getNodeName(); 245 if (name.equals("style:properties")) { 246 NamedNodeMap childAttrNodes = child.getAttributes(); 247 if (childAttrNodes != null) { 248 int nChildAttrNodes = childAttrNodes.getLength(); 249 for (int j = 0; j < nChildAttrNodes; j++) { 250 Node attr = childAttrNodes.item(j); 251 setAttribute(attr.getNodeName(), attr.getNodeValue()); 252 } 253 } 254 } 255 } 256 } 257 } 258 259 260 /** 261 * Constructor for use when going from client device format to DOM. 262 * 263 * @param name Name of the <code>Style</code>. Can be null. 264 * @param familyName Family of the <code>Style</code> - usually 265 * <i>paragraph</i>, <i>text</i>, etc. Can be null. 266 * @param parentName Name of the parent <code>Style</code>, or null 267 * if none. 268 * @param attribs Array of attributes to set. 269 * @param values Array of values to set. 270 * @param sc The <code>StyleCatalog</code>, which is used for 271 * looking up ancestor <code>Style</code> objects. 272 */ ParaStyle(String name, String familyName, String parentName, String attribs[], String values[], StyleCatalog sc)273 public ParaStyle(String name, String familyName, String parentName, 274 String attribs[], String values[], StyleCatalog sc) { 275 super(name, familyName, parentName, sc); 276 if (attribs != null) 277 for (int i = 0; i < attribs.length; i++) 278 setAttribute(attribs[i], values[i]); 279 } 280 281 282 /** 283 * Alternate constructor for use when going from client device 284 * format to DOM. 285 * 286 * @param name Name of the <code>Style</code>. Can be null. 287 * @param familyName Family of the <code>Style</code> - usually 288 * <i>paragraph</i>, <i>text</i>, etc. Can be null. 289 * @param parentName Name of the parent <code>Style</code>, or 290 * null if none. 291 * @param attribs Array of attributes indices to set. 292 * @param values Array of values to set. 293 * @param lookup The <code>StyleCatalog</code>, which is used for 294 * looking up ancestor <code>Style</code> objects. 295 */ ParaStyle(String name, String familyName, String parentName, int attribs[], String values[], StyleCatalog lookup)296 public ParaStyle(String name, String familyName, String parentName, 297 int attribs[], String values[], StyleCatalog lookup) { 298 super(name, familyName, parentName, lookup); 299 if (attribs != null) 300 for (int i = 0; i < attribs.length; i++) 301 setAttribute(attribs[i], values[i]); 302 } 303 304 305 /** 306 * This code checks whether an attribute is one that we 307 * intentionally ignore. 308 * 309 * @param attribute The attribute to check. 310 * 311 * @return true if attribute can be ignored, false otherwise. 312 */ isIgnored(String attribute)313 private boolean isIgnored(String attribute) { 314 for (int i = 0; i < ignored.length; i++) { 315 if (ignored[i].equals(attribute)) 316 return true; 317 } 318 return false; 319 } 320 321 322 /** 323 * Set an attribute for this paragraph <code>Style</code>. 324 * 325 * @param attr The attribute to set. 326 * @param value The attribute value to set. 327 */ setAttribute(String attr, String value)328 public void setAttribute(String attr, String value) { 329 for (int i = 0; i < NR_PROPERTIES; i++) { 330 if (attr.equals(attrName[i])) { 331 setAttribute(i, value); 332 return; 333 } 334 } 335 if (!isIgnored(attr)) 336 Debug.log(Debug.INFO, "ParaStyle Unhandled: " + attr + "=" + value); 337 } 338 339 340 /** 341 * Check whether an attribute is set in this <code>Style</code>. 342 * 343 * @param attrIndex The attribute index to check. 344 * 345 * @return true if the attribute at specified index is set, 346 * false otherwise. 347 */ isAttributeSet(int attrIndex)348 public boolean isAttributeSet(int attrIndex) { 349 return isSet[attrIndex]; 350 } 351 352 353 /** 354 * Get the value of an integer attribute. 355 * 356 * @param attrIndex Index of the attribute. 357 * 358 * @return Value of the attribute, 0 if not set. 359 */ getAttribute(int attrIndex)360 public int getAttribute(int attrIndex) { 361 if (isSet[attrIndex]) 362 return value[attrIndex]; 363 else return 0; 364 } 365 366 367 /** 368 * Set an attribute for this paragraph <code>Style</code>. 369 * 370 * @param attr The attribute index to set. 371 * @param value The attribute value to set. 372 */ setAttribute(int attr, String value)373 public void setAttribute(int attr, String value) { 374 isSet[attr] = true; 375 try { 376 this.value[attr] = (((conversionAlgorithm)algor[attr].newInstance())).I(value); 377 } catch (Exception e) { 378 Debug.log(Debug.ERROR, "Instantiation error", e); 379 } 380 } 381 382 383 /** 384 * Return the <code>Style</code> in use. 385 * 386 * @return The fully-resolved copy of the <code>Style</code> in use. 387 */ getResolved()388 public Style getResolved() { 389 ParaStyle resolved = null; 390 try { 391 resolved = (ParaStyle)this.clone(); 392 } catch (Exception e) { 393 Debug.log(Debug.ERROR, "Can't clone", e); 394 } 395 396 // Look up the parent style. (If there is no style catalog 397 // specified, we can't do any lookups). 398 ParaStyle parentStyle = null; 399 if (sc != null) { 400 if (parent != null) { 401 parentStyle = (ParaStyle)sc.lookup(parent, family, null, 402 this.getClass()); 403 if (parentStyle == null) 404 Debug.log(Debug.ERROR, "parent style lookup of " 405 + parent + " failed!"); 406 else 407 parentStyle = (ParaStyle)parentStyle.getResolved(); 408 } else if (!name.equals("DEFAULT_STYLE")) { 409 parentStyle = (ParaStyle)sc.lookup("DEFAULT_STYLE", null, null, 410 this.getClass()); 411 } 412 } 413 414 // If we found a parent, for any attributes which we don't have 415 // set, try to get the values from the parent. 416 if (parentStyle != null) { 417 parentStyle = (ParaStyle)parentStyle.getResolved(); 418 for (int i = 0; i < NR_PROPERTIES; i++) { 419 if (!isSet[i] && parentStyle.isSet[i]) { 420 resolved.isSet[i] = true; 421 resolved.value[i] = parentStyle.value[i]; 422 } 423 } 424 } 425 return resolved; 426 } 427 428 429 /** 430 * Private function to return the value as an element in 431 * a Comma Separated Value (CSV) format. 432 * 433 * @param value The value to format. 434 * 435 * @return The formatted value. 436 */ toCSV(String value)437 private static String toCSV(String value) { 438 if (value != null) 439 return "\"" + value + "\","; 440 else 441 return "\"\","; 442 } 443 444 445 /** 446 * Private function to return the value as a last element in 447 * a Comma Separated Value (CSV) format. 448 * 449 * @param value The value to format. 450 * 451 * @return The formatted value. 452 */ toLastCSV(String value)453 private static String toLastCSV(String value) { 454 if (value != null) 455 return "\"" + value + "\""; 456 else 457 return "\"\""; 458 } 459 460 461 /** 462 * Print a Comma Separated Value (CSV) header line for the 463 * spreadsheet dump. 464 */ dumpHdr()465 public static void dumpHdr() { 466 System.out.println(toCSV("Name") + toCSV("Family") + toCSV("parent") 467 + toCSV("left mgn") + toCSV("right mgn") 468 + toCSV("top mgn") + toCSV("bottom mgn") + toCSV("txt indent") 469 + toCSV("line height") + toLastCSV("txt align")); 470 } 471 472 473 /** 474 * Dump this <code>Style</code> as a Comma Separated Value (CSV) 475 * line. 476 */ dumpCSV()477 public void dumpCSV() { 478 String attributes = ""; 479 for (int index = 0; index <= 6; index++) { 480 if (isSet[index]) { 481 attributes += toCSV("" + value[index]); 482 } 483 else 484 attributes += toCSV(null); // unspecified 485 } 486 System.out.println(toCSV(name) + toCSV(family) + toCSV(parent) 487 + attributes + toLastCSV(null)); 488 } 489 490 491 /** 492 * Create the <code>Node</code> with the specified elements. 493 * 494 * @param parentDoc <code>Document</code> of the 495 * <code>Node</code> to create. 496 * @param name Name of the <code>Node</code>. 497 * 498 * @return The created <code>Node</code>. 499 */ createNode(org.w3c.dom.Document parentDoc, String name)500 public Node createNode(org.w3c.dom.Document parentDoc, String name) { 501 Element node = parentDoc.createElement(name); 502 writeAttributes(node); 503 return node; 504 } 505 506 507 /** 508 * Return true if <code>style</code> is a subset of the 509 * <code>Style</code>. 510 * 511 * @param style <code>Style</code> to check. 512 * 513 * @return true if <code>style</code> is a subset, false 514 * otherwise. 515 */ isSubset(Style style)516 public boolean isSubset(Style style) { 517 518 if (!super.isSubset(style)) 519 return false; 520 if (!this.getClass().isAssignableFrom(style.getClass())) 521 return false; 522 ParaStyle ps = (ParaStyle)style; 523 524 for (int i = 0; i < NR_PROPERTIES; i++) { 525 if (ps.isSet[i]) { 526 // if (!isSet[i]) return false; 527 528 if (i < NR_PROPERTIES - 1) { 529 // Compare the actual values. We allow a margin of error 530 // here because the conversion loses precision. 531 int diff; 532 if (value[i] > ps.value[i]) 533 diff = value[i] - ps.value[i]; 534 else 535 diff = ps.value[i] - value[i]; 536 if (diff > 32) 537 return false; 538 } else { 539 if (i == TEXT_ALIGN) 540 if ((value[i] == 0) && (ps.value[i] == 4)) 541 continue; 542 if (value[i] != ps.value[i]) 543 return false; 544 } 545 } 546 } 547 return true; 548 } 549 550 551 /** 552 * Add <code>Style</code> attributes to the given 553 * <code>Node</code>. This may involve writing child 554 * <code>Node</code> objects as well. 555 * 556 * @param node The <code>Node</code> to add <code>Style</code> 557 * attributes. 558 */ writeAttributes(Element node)559 public void writeAttributes(Element node) { 560 for (int i = 0; i <= TEXT_INDENT; i++) { 561 if (isSet[i]) { 562 double temp = value[i] / 100.0; 563 String stringVal = (new Double(temp)).toString() + "mm"; 564 node.setAttribute(attrName[i], stringVal); 565 } 566 } 567 568 if (isSet[LINE_HEIGHT]) { 569 String stringVal; 570 if ((value[LINE_HEIGHT] & LH_PCT) != 0) 571 stringVal = (new Integer(value[LINE_HEIGHT] & LH_VALUEMASK)).toString() + "%"; 572 else { 573 double temp = (value[LINE_HEIGHT] & LH_VALUEMASK) / 100.0; 574 stringVal = (new Double(temp)).toString() + "mm"; 575 } 576 node.setAttribute(attrName[LINE_HEIGHT], stringVal); 577 } 578 579 if (isSet[TEXT_ALIGN]) { 580 String val; 581 switch (value[TEXT_ALIGN]) { 582 case ALIGN_RIGHT: val = "end"; break; 583 case ALIGN_CENTER: val = "center"; break; 584 case ALIGN_JUST: val = "justify"; break; 585 case ALIGN_LEFT: val = "left"; break; 586 default: val = "unknown"; break; 587 } 588 node.setAttribute(attrName[TEXT_ALIGN], val); 589 } 590 } 591 } 592 593