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