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