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