blob: ce61d6c2f91e01b932c15f5731649795c860fc12 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
package org.openoffice.xmerge.converter.xml;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Element;
import org.openoffice.xmerge.util.Debug;
abstract class conversionAlgorithm {
int I(String val) {
return 0;
}
}
/*
* This algorithm expects only values in millimeters, e.g. "20.3mm".
*/
class horizSize extends conversionAlgorithm {
int I(String value) {
if (value.endsWith("mm")) {
float size = (float)0.0;
String num = value.substring(0, value.length() - 2);
try {
size = Float.parseFloat(num);
} catch (Exception e) {
Debug.log(Debug.ERROR, "Error parsing " + value, e);
}
size *= 100;
return (int)size;
} else {
Debug.log(Debug.ERROR, "Unexpected value (" + value
+ ") in horizSize.I()");
return 0;
}
}
}
/*
* This algorithm does line height - can be either millimeters or
* a percentage.
*/
class lineHeight extends conversionAlgorithm {
int I(String value) {
if (value.endsWith("mm")) {
float size = (float)0.0;
String num = value.substring(0, value.length() - 2);
try {
size = Float.parseFloat(num);
} catch (Exception e) {
Debug.log(Debug.ERROR, "Error parsing " + value, e);
}
size *= 100;
return (int)size;
} else if (value.endsWith("%")) {
float size = (float)0.0;
String num = value.substring(0, value.length() - 1);
try {
size = Float.parseFloat(num);
} catch (Exception e) {
Debug.log(Debug.ERROR, "Error parsing " + value, e);
}
int retval = (int) size;
retval |= ParaStyle.LH_PCT;
return retval;
}
return 0;
}
}
/**
* This class converts alignment values.
*/
class alignment extends conversionAlgorithm {
int I(String value) {
if (value.equals("end"))
return ParaStyle.ALIGN_RIGHT;
if (value.equals("right"))
return ParaStyle.ALIGN_RIGHT;
if (value.equals("center"))
return ParaStyle.ALIGN_CENTER;
if (value.equals("justify"))
return ParaStyle.ALIGN_JUST;
if (value.equals("justified"))
return ParaStyle.ALIGN_JUST;
if (value.equals("start"))
return ParaStyle.ALIGN_LEFT;
if (value.equals("left"))
return ParaStyle.ALIGN_LEFT;
Debug.log(Debug.ERROR, "Unknown string ("
+ value + ") in alignment.I()");
return ParaStyle.ALIGN_LEFT;
}
}
/**
* <p>This class represents a paragraph <code>Style</code>.</p>
*
* <p><table border="1" cellpadding="1"><tr><td>
* Attribute </td><td>Value
* </td></tr><tr><td>
* MARGIN_LEFT </td><td>mm * 100
* </td></tr><tr><td>
* MARGIN_RIGHT </td><td>mm * 100
* </td></tr><tr><td>
* MARGIN_TOP </td><td>mm * 100 (space on top of paragraph)
* </td></tr><tr><td>
* MARGIN_BOTTOM </td><td>mm * 100
* </td></tr><tr><td>
* TEXT_INDENT </td><td>mm * 100 (first line indent)
* </td></tr><tr><td>
* LINE_HEIGHT </td><td>mm * 100, unless or'ed with LH_PCT, in which
* case it is a percentage (e.g. 200% for double spacing)
* Can also be or'ed with LH_ATLEAST. Value is stored
* in bits indicated by LH_VALUEMASK.
* </td></tr><tr><td>
* TEXT_ALIGN </td><td>ALIGN_RIGHT, ALIGN_CENTER, ALIGN_JUST, ALIGN_LEFT
* </td></tr></table></p>
*
* @author David Proulx
*/
public class ParaStyle extends Style implements Cloneable {
/** The left margin property. */
public static final int MARGIN_LEFT = 0;
/** The right margin property. */
public static final int MARGIN_RIGHT = 1;
/** The top margin property. */
public static final int MARGIN_TOP = 2;
/** The bottom margin property. */
public static final int MARGIN_BOTTOM = 3;
/** Indent left property. */
public static final int TEXT_INDENT = 4;
/** Indent right property. */
public static final int LINE_HEIGHT = 5;
/** Align text property. */
public static final int TEXT_ALIGN = 6;
// This must always be one more than highest property
/** Total number of properties. */
protected static final int NR_PROPERTIES = 7;
/**
* Array of flags indicating which attributes are set for this
* paragraph <code>Style</code>.
*/
protected boolean isSet[] = new boolean[NR_PROPERTIES];
/** Array of attribute values for this paragraph <code>tyle</code>. */
protected int value[] = new int[NR_PROPERTIES];
/** Array of attribute names for this paragraph <code>Style</code>. */
protected String attrName[] = {
"fo:margin-left",
"fo:margin-right",
"fo:margin-top",
"fo:margin-bottom",
"fo:text-indent",
"fo:line-height",
"fo:text-align"
};
/** Array of attribute structures for this paragraph <code>Style</code>. */
protected Class algor[] = {
horizSize.class,
horizSize.class,
horizSize.class,
horizSize.class,
horizSize.class,
lineHeight.class,
alignment.class
};
/** Align right. */
public static final int ALIGN_RIGHT = 1;
/** Align center. */
public static final int ALIGN_CENTER = 2;
/** Align justified. */
public static final int ALIGN_JUST = 3;
/** Align left. */
public static final int ALIGN_LEFT = 4;
/** Line height percentage. */
public static final int LH_PCT = 0x40000000;
/** Line height minimum value. */
public static final int LH_ATLEAST = 0x20000000;
/** Line height mask. */
public static final int LH_VALUEMASK = 0x00FFFFFF;
/** Ignored tags. */
private static String[] ignored = {
"style:font-name", "fo:font-size", "fo:font-weight", "fo:color",
"fo:language", "fo:country", "style:font-name-asian",
"style:font-size-asian", "style:language-asian",
"style:country-asian", "style:font-name-complex",
"style:font-size-complex", "style:language-complex",
"style:country-complex", "style:text-autospace", "style:punctuation-wrap",
"style:line-break", "fo:keep-with-next", "fo:font-style",
"text:number-lines", "text:line-number"
};
/**
* Constructor for use when going from DOM to client device format.
*
* @param node A <i>style:style</i> <code>Node</code> which, which
* is assumed to have <i>family</i> attribute of
* <i>paragraph</i>.
* @param sc The <code>StyleCatalog</code>, which is used for
* looking up ancestor <code>Style</code> objects.
*/
public ParaStyle(Node node, StyleCatalog sc) {
super(node, sc);
// Look for children. Only ones we care about are "style:properties"
// nodes. If any are found, recursively traverse them, passing
// along the style element to add properties to.
//
if (node.hasChildNodes()) {
NodeList children = node.getChildNodes();
int len = children.getLength();
for (int i = 0; i < len; i++) {
Node child = children.item(i);
String name = child.getNodeName();
if (name.equals("style:properties")) {
NamedNodeMap childAttrNodes = child.getAttributes();
if (childAttrNodes != null) {
int nChildAttrNodes = childAttrNodes.getLength();
for (int j = 0; j < nChildAttrNodes; j++) {
Node attr = childAttrNodes.item(j);
setAttribute(attr.getNodeName(), attr.getNodeValue());
}
}
}
}
}
}
/**
* Constructor for use when going from client device format to DOM.
*
* @param name Name of the <code>Style</code>. Can be null.
* @param family Family of the <code>Style</code> - usually
* <i>paragraph</i>, <i>text</i>, etc. Can be null.
* @param parent Name of the parent <code>Style</code>, or null
* if none.
* @param attribs Array of attributes to set.
* @param values Array of values to set.
* @param sc The <code>StyleCatalog</code>, which is used for
* looking up ancestor <code>Style</code> objects.
*/
public ParaStyle(String name, String familyName, String parentName,
String attribs[], String values[], StyleCatalog sc) {
super(name, familyName, parentName, sc);
if (attribs != null)
for (int i = 0; i < attribs.length; i++)
setAttribute(attribs[i], values[i]);
}
/**
* Alternate constructor for use when going from client device
* format to DOM.
*
* @param name Name of the <code>Style</code>. Can be null.
* @param family Family of the <code>Style</code> - usually
* <i>paragraph</i>, <i>text</i>, etc. Can be null.
* @param parent Name of the parent <code>Style</code>, or
* null if none.
* @param attribs Array of attributes indices to set.
* @param values Array of values to set.
* @param sc The <code>StyleCatalog</code>, which is used for
* looking up ancestor <code>Style</code> objects.
*/
public ParaStyle(String name, String familyName, String parentName,
int attribs[], String values[], StyleCatalog lookup) {
super(name, familyName, parentName, lookup);
if (attribs != null)
for (int i = 0; i < attribs.length; i++)
setAttribute(attribs[i], values[i]);
}
/**
* This code checks whether an attribute is one that we
* intentionally ignore.
*
* @param attribute The attribute to check.
*
* @return true if attribute can be ignored, false otherwise.
*/
private boolean isIgnored(String attribute) {
for (int i = 0; i < ignored.length; i++) {
if (ignored[i].equals(attribute))
return true;
}
return false;
}
/**
* Set an attribute for this paragraph <code>Style</code>.
*
* @param attr The attribute to set.
* @param value The attribute value to set.
*/
public void setAttribute(String attr, String value) {
for (int i = 0; i < NR_PROPERTIES; i++) {
if (attr.equals(attrName[i])) {
setAttribute(i, value);
return;
}
}
if (!isIgnored(attr))
Debug.log(Debug.INFO, "ParaStyle Unhandled: " + attr + "=" + value);
}
/**
* Check whether an attribute is set in this <code>Style</code>.
*
* @param attrIndex The attribute index to check.
*
* @return true if the attribute at specified index is set,
* false otherwise.
*/
public boolean isAttributeSet(int attrIndex) {
return isSet[attrIndex];
}
/**
* Get the value of an integer attribute.
*
* @param attrIndex Index of the attribute.
*
* @return Value of the attribute, 0 if not set.
*/
public int getAttribute(int attrIndex) {
if (isSet[attrIndex])
return value[attrIndex];
else return 0;
}
/**
* Set an attribute for this paragraph <code>Style</code>.
*
* @param attr The attribute index to set.
* @apram value The attribute value to set.
*/
public void setAttribute(int attr, String value) {
isSet[attr] = true;
try {
this.value[attr] = (((conversionAlgorithm)algor[attr].newInstance())).I(value);
} catch (Exception e) {
Debug.log(Debug.ERROR, "Instantiation error", e);
}
}
/**
* Return the <code>Style</code> in use.
*
* @return The fully-resolved copy of the <code>Style</code> in use.
*/
public Style getResolved() {
ParaStyle resolved = null;
try {
resolved = (ParaStyle)this.clone();
} catch (Exception e) {
Debug.log(Debug.ERROR, "Can't clone", e);
}
// Look up the parent style. (If there is no style catalog
// specified, we can't do any lookups).
ParaStyle parentStyle = null;
if (sc != null) {
if (parent != null) {
parentStyle = (ParaStyle)sc.lookup(parent, family, null,
this.getClass());
if (parentStyle == null)
Debug.log(Debug.ERROR, "parent style lookup of "
+ parent + " failed!");
else
parentStyle = (ParaStyle)parentStyle.getResolved();
} else if (!name.equals("DEFAULT_STYLE")) {
parentStyle = (ParaStyle)sc.lookup("DEFAULT_STYLE", null, null,
this.getClass());
}
}
// If we found a parent, for any attributes which we don't have
// set, try to get the values from the parent.
if (parentStyle != null) {
parentStyle = (ParaStyle)parentStyle.getResolved();
for (int i = 0; i < NR_PROPERTIES; i++) {
if (!isSet[i] && parentStyle.isSet[i]) {
resolved.isSet[i] = true;
resolved.value[i] = parentStyle.value[i];
}
}
}
return resolved;
}
/**
* Private function to return the value as an element in
* a Comma Separated Value (CSV) format.
*
* @param value The value to format.
*
* @return The formatted value.
*/
private static String toCSV(String value) {
if (value != null)
return "\"" + value + "\",";
else
return "\"\",";
}
/**
* Private function to return the value as a last element in
* a Comma Separated Value (CSV) format.
*
* @param value The value to format.
*
* @return The formatted value.
*/
private static String toLastCSV(String value) {
if (value != null)
return "\"" + value + "\"";
else
return "\"\"";
}
/**
* Print a Comma Separated Value (CSV) header line for the
* spreadsheet dump.
*/
public static void dumpHdr() {
System.out.println(toCSV("Name") + toCSV("Family") + toCSV("parent")
+ toCSV("left mgn") + toCSV("right mgn")
+ toCSV("top mgn") + toCSV("bottom mgn") + toCSV("txt indent")
+ toCSV("line height") + toLastCSV("txt align"));
}
/**
* Dump this <code>Style</code> as a Comma Separated Value (CSV)
* line.
*/
public void dumpCSV() {
String attributes = "";
for (int index = 0; index <= 6; index++) {
if (isSet[index]) {
attributes += toCSV("" + value[index]);
}
else
attributes += toCSV(null); // unspecified
}
System.out.println(toCSV(name) + toCSV(family) + toCSV(parent)
+ attributes + toLastCSV(null));
}
/**
* Create the <code>Node</code> with the specified elements.
*
* @parentDoc Parent <code>Document</code> of the
* <code>Node</code> to create.
* @param name Name of the <code>Node</code>.
*
* @return The created <code>Node</code>.
*/
public Node createNode(org.w3c.dom.Document parentDoc, String name) {
Element node = parentDoc.createElement(name);
writeAttributes(node);
return node;
}
/**
* Return true if <code>style</code> is a subset of the
* <code>Style</code>.
*
* @param style <code>Style</code> to check.
*
* @return true if <code>style</code> is a subset, false
* otherwise.
*/
public boolean isSubset(Style style) {
if (!super.isSubset(style))
return false;
if (!this.getClass().isAssignableFrom(style.getClass()))
return false;
ParaStyle ps = (ParaStyle)style;
for (int i = 0; i < NR_PROPERTIES; i++) {
if (ps.isSet[i]) {
// if (!isSet[i]) return false;
if (i < NR_PROPERTIES - 1) {
// Compare the actual values. We allow a margin of error
// here because the conversion loses precision.
int diff;
if (value[i] > ps.value[i])
diff = value[i] - ps.value[i];
else
diff = ps.value[i] - value[i];
if (diff > 32)
return false;
} else {
if (i == TEXT_ALIGN)
if ((value[i] == 0) && (ps.value[i] == 4))
continue;
if (value[i] != ps.value[i])
return false;
}
}
}
return true;
}
/**
* Add <code>Style</code> attributes to the given
* <code>Node</code>. This may involve writing child
* <code>Node</code> objects as well.
*
* @param node The <code>Node</code> to add <code>Style</code>
* attributes.
*/
public void writeAttributes(Element node) {
for (int i = 0; i <= TEXT_INDENT; i++) {
if (isSet[i]) {
double temp = value[i] / 100.0;
String stringVal = (new Double(temp)).toString() + "mm";
node.setAttribute(attrName[i], stringVal);
}
}
if (isSet[LINE_HEIGHT]) {
String stringVal;
if ((value[LINE_HEIGHT] & LH_PCT) != 0)
stringVal = (new Integer(value[LINE_HEIGHT] & LH_VALUEMASK)).toString() + "%";
else {
double temp = (value[LINE_HEIGHT] & LH_VALUEMASK) / 100.0;
stringVal = (new Double(temp)).toString() + "mm";
}
node.setAttribute(attrName[LINE_HEIGHT], stringVal);
}
if (isSet[TEXT_ALIGN]) {
String val;
switch (value[TEXT_ALIGN]) {
case ALIGN_RIGHT: val = "end"; break;
case ALIGN_CENTER: val = "center"; break;
case ALIGN_JUST: val = "justify"; break;
case ALIGN_LEFT: val = "left"; break;
default: val = "unknown"; break;
}
node.setAttribute(attrName[TEXT_ALIGN], val);
}
}
}