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