OdfStyleBase.java
/**
* **********************************************************************
*
* <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* <p>Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
*
* <p>Use is subject to license terms.
*
* <p>Licensed 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. You can also obtain a copy of the License at
* http://odftoolkit.org/docs/license.txt
*
* <p>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.
*
* <p>See the License for the specific language governing permissions and limitations under the
* License.
*
* <p>**********************************************************************
*/
package org.odftoolkit.odfdom.dom.element;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.element.style.StyleChartPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleDrawingPagePropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleGraphicPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleHeaderFooterPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleListLevelPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StylePageLayoutPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleParagraphPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleRubyPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleSectionPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTableCellPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTableColumnPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTablePropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTableRowPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTextPropertiesElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.OdfStylePropertySet;
import org.odftoolkit.odfdom.dom.style.props.OdfStylePropertiesSet;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.pkg.OdfAttribute;
import org.odftoolkit.odfdom.pkg.OdfContainerElementBase;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* A placeholder for multiple style incarnation, for instance <style:style> from either the
* automatic or the template styles parent, e.g. StyleStyleElement is inheriting from it
*/
public abstract class OdfStyleBase extends OdfContainerElementBase
implements OdfStylePropertySet, Comparable {
/** */
private static final long serialVersionUID = 8271282184913774000L;
private HashMap<OdfStylePropertiesSet, OdfStylePropertiesBase> mPropertySetElementMap;
private ArrayList<OdfStylableElement> mStyleUser;
static HashMap<OdfName, OdfStylePropertiesSet> mStylePropertiesElementToSetMap;
static {
mStylePropertiesElementToSetMap = new HashMap<OdfName, OdfStylePropertiesSet>();
mStylePropertiesElementToSetMap.put(
StyleChartPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.ChartProperties);
mStylePropertiesElementToSetMap.put(
StyleDrawingPagePropertiesElement.ELEMENT_NAME,
OdfStylePropertiesSet.DrawingPageProperties);
mStylePropertiesElementToSetMap.put(
StyleGraphicPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.GraphicProperties);
mStylePropertiesElementToSetMap.put(
StyleHeaderFooterPropertiesElement.ELEMENT_NAME,
OdfStylePropertiesSet.HeaderFooterProperties);
mStylePropertiesElementToSetMap.put(
StyleListLevelPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.ListLevelProperties);
mStylePropertiesElementToSetMap.put(
StylePageLayoutPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.PageLayoutProperties);
mStylePropertiesElementToSetMap.put(
StyleParagraphPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.ParagraphProperties);
mStylePropertiesElementToSetMap.put(
StyleRubyPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.RubyProperties);
mStylePropertiesElementToSetMap.put(
StyleSectionPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.SectionProperties);
mStylePropertiesElementToSetMap.put(
StyleTableCellPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.TableCellProperties);
mStylePropertiesElementToSetMap.put(
StyleTableColumnPropertiesElement.ELEMENT_NAME,
OdfStylePropertiesSet.TableColumnProperties);
mStylePropertiesElementToSetMap.put(
StyleTablePropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.TableProperties);
mStylePropertiesElementToSetMap.put(
StyleTableRowPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.TableRowProperties);
mStylePropertiesElementToSetMap.put(
StyleTextPropertiesElement.ELEMENT_NAME, OdfStylePropertiesSet.TextProperties);
}
/** Creates a new instance of OdfElement */
public OdfStyleBase(OdfFileDom ownerDocument, String namespaceURI, String qualifiedName)
throws DOMException {
super(ownerDocument, namespaceURI, qualifiedName);
}
/** Creates a new instance of OdfElement */
public OdfStyleBase(OdfFileDom ownerDocument, OdfName aName) throws DOMException {
super(ownerDocument, aName.getUri(), aName.getQName());
}
public void addStyleUser(OdfStylableElement user) {
if (mStyleUser == null) {
mStyleUser = new ArrayList<OdfStylableElement>();
}
mStyleUser.add(user);
}
/**
* get a map containing all properties of this style and their values.
*
* @return map of properties. @Deprecated Broken by design as the same OdfStlyeProperty can occur
* multiple times and would be overwritten (e.g. background color exist 3times in cells).
*/
public Map<OdfStyleProperty, String> getStyleProperties() {
TreeMap<OdfStyleProperty, String> result = new TreeMap<OdfStyleProperty, String>();
OdfStyleFamily family = getFamily();
if (family != null) {
for (OdfStyleProperty property : family.getProperties()) {
if (hasProperty(property)) {
result.put(property, getProperty(property));
}
}
}
return result;
}
/**
* get a map containing all properties of this style and their values. The map will also include
* any properties set by parent styles
*
* @return a map of all the properties. @Deprecated Broken by design as the same OdfStlyeProperty
* can occur multiple times and would be overwritten (e.g. background color exist 3times in
* cells).
*/
public Map<OdfStyleProperty, String> getStylePropertiesDeep() {
TreeMap<OdfStyleProperty, String> result = new TreeMap<OdfStyleProperty, String>();
OdfStyleBase style = this;
while (style != null) {
OdfStyleFamily family = style.getFamily();
if (family != null) {
for (OdfStyleProperty property : family.getProperties()) {
if (!result.containsKey(property) && style.hasProperty(property)) {
result.put(property, style.getProperty(property));
}
}
}
style = style.getParentStyle();
}
return result;
}
public void removeStyleUser(OdfStylableElement user) {
if (mStyleUser != null) {
mStyleUser.remove(user);
}
}
public int getStyleUserCount() {
return mStyleUser == null ? 0 : mStyleUser.size();
}
/**
* Returns an iterator for all <code>OdfStylableElement</code> elements using this style.
*
* @return an iterator for all <code>OdfStylableElement</code> elements using this style
*/
public Iterable<OdfStylableElement> getStyleUsers() {
if (mStyleUser != null) {
return mStyleUser;
}
return new ArrayList<OdfStylableElement>();
}
public String getFamilyName() {
return getFamily().getName();
}
/**
* @param set
* @return the style:*-properties element for the given set. Returns null if such element does not
* exist yet.
*/
public OdfStylePropertiesBase getPropertiesElement(OdfStylePropertiesSet set) {
if (mPropertySetElementMap != null) {
return mPropertySetElementMap.get(set);
}
return null;
}
/**
* @param set
* @return the style:*-properties element for the given set. If such element does not yet exist,
* it is created.
*/
public OdfStylePropertiesBase getOrCreatePropertiesElement(OdfStylePropertiesSet set) {
OdfStylePropertiesBase properties = null;
if (mPropertySetElementMap != null) {
properties = mPropertySetElementMap.get(set);
}
if (properties == null) {
for (Entry<OdfName, OdfStylePropertiesSet> entry :
mStylePropertiesElementToSetMap.entrySet()) {
if (entry.getValue().equals(set)) {
properties =
(OdfStylePropertiesBase)
((OdfFileDom) this.ownerDocument).createElementNS(entry.getKey());
if (getFirstChild() == null) {
appendChild(properties);
} else {
// make sure the properties elements are in the correct order
Node beforeNode = null;
if (set.equals(OdfStylePropertiesSet.GraphicProperties)) {
beforeNode =
OdfElement.findFirstChildNode(StyleParagraphPropertiesElement.class, this);
if (beforeNode == null) {
beforeNode = OdfElement.findFirstChildNode(StyleTextPropertiesElement.class, this);
}
} else if (set.equals(OdfStylePropertiesSet.ParagraphProperties)) {
beforeNode = OdfElement.findFirstChildNode(StyleTextPropertiesElement.class, this);
} else if (!set.equals(OdfStylePropertiesSet.TextProperties)) {
beforeNode = getFirstChild();
}
if (beforeNode == null) {
beforeNode = getFirstChild();
// find first non properties node
while (beforeNode != null) {
if (beforeNode.getNodeType() == Node.ELEMENT_NODE) {
if (!(beforeNode instanceof OdfStylePropertiesBase)) {
break;
}
}
beforeNode = beforeNode.getNextSibling();
}
}
insertBefore(properties, beforeNode);
}
break;
}
}
}
return properties;
}
/** @return a property value. */
public String getProperty(OdfStyleProperty prop) {
String value = null;
OdfStylePropertiesBase properties = getPropertiesElement(prop.getPropertySet());
if (properties != null) {
if (properties.hasAttributeNS(prop.getName().getUri(), prop.getName().getLocalName())) {
return properties.getOdfAttribute(prop.getName()).getValue();
}
}
OdfStyleBase parent = getParentStyle();
if (parent != null) {
return parent.getProperty(prop);
}
return value;
}
public boolean hasProperty(OdfStyleProperty prop) {
if (mPropertySetElementMap != null) {
OdfStylePropertiesBase properties = mPropertySetElementMap.get(prop.getPropertySet());
if (properties != null) {
return properties.hasAttributeNS(prop.getName().getUri(), prop.getName().getLocalName());
}
}
return false;
}
@Override
protected void onOdfNodeInserted(OdfElement node, Node refChild) {
if (node instanceof OdfStylePropertiesBase) {
OdfStylePropertiesSet set = mStylePropertiesElementToSetMap.get(node.getOdfName());
if (set != null) {
if (mPropertySetElementMap == null) {
mPropertySetElementMap = new HashMap<OdfStylePropertiesSet, OdfStylePropertiesBase>();
}
mPropertySetElementMap.put(set, (OdfStylePropertiesBase) node);
}
}
}
@Override
protected void onOdfNodeRemoved(OdfElement node) {
if (mPropertySetElementMap != null) {
if (node instanceof OdfStylePropertiesBase) {
OdfStylePropertiesSet set = mStylePropertiesElementToSetMap.get(node.getOdfName());
if (set != null) {
mPropertySetElementMap.remove(set);
}
}
}
}
public Map<OdfStyleProperty, String> getProperties(Set<OdfStyleProperty> properties) {
HashMap<OdfStyleProperty, String> map = new HashMap<OdfStyleProperty, String>();
for (OdfStyleProperty property : properties) {
map.put(property, getProperty(property));
}
return map;
}
public Set<OdfStyleProperty> getStrictProperties() {
return getFamily().getProperties();
}
public void removeProperty(OdfStyleProperty property) {
if (mPropertySetElementMap != null) {
OdfStylePropertiesBase properties = mPropertySetElementMap.get(property.getPropertySet());
if (properties != null) {
properties.removeAttributeNS(
property.getName().getUri(), property.getName().getLocalName());
}
}
}
public void setProperties(Map<OdfStyleProperty, String> properties) {
for (Map.Entry<OdfStyleProperty, String> entry : properties.entrySet()) {
setProperty(entry.getKey(), entry.getValue());
}
}
public void setProperty(OdfStyleProperty property, String value) {
OdfStylePropertiesBase properties = getOrCreatePropertiesElement(property.getPropertySet());
if (properties != null) {
OdfAttribute propertyAttr =
((OdfFileDom) this.ownerDocument).createAttributeNS(property.getName());
properties.setOdfAttribute(propertyAttr);
propertyAttr.setValue(value);
}
}
/**
* compare one style to another one. This implements a total order on style objects.
*
* @param obj - the reference object with which to compare2.
* @return 0 if this object is the same as the obj argument; -1 if this object is less than the
* obj argument; 1 if this object is greater than the obj argument
*/
public int compareTo(Object obj) {
if (this == obj) {
return 0;
}
if (!(obj instanceof OdfStyleBase)) {
if (obj == null) {
throw new ClassCastException("The object to be compared is null!");
} else {
throw new ClassCastException("The object to be compared is not a style!");
}
}
OdfStyleBase compare = (OdfStyleBase) obj;
int c = compareNodes(this, compare);
return c;
}
// Currently this function does not consider the order of child nodes, e.g.,
//
// <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
// <style:paragraph-properties>
// <style:tab-stops>
// <style:tab-stop style:position="4.344cm"/>
// </style:tab-stops>
// <style:background-image xlink:href="Pictures/1.jpg" xlink:type="simple"
// xlink:actuate="onLoad"/>
// </style:paragraph-properties>
// </style:style>
//
// and
//
// <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard">
// <style:paragraph-properties>
// <style:background-image xlink:href="Pictures/1.jpg" xlink:type="simple"
// xlink:actuate="onLoad"/>
// <style:tab-stops>
// <style:tab-stop style:position="4.344cm"/>
// </style:tab-stops>
// </style:paragraph-properties>
// </style:style>
//
// are regarded non-equal
//
private static int compareNodes(Node compare1, Node compare2) {
// Only styles can be equal, that are from the same element
// (e.g. style:style and text:list-level-style-bullet are never equal)
int c = 0;
// if the local name is unequal (e.g. style vs. list-level-style-bullet)
// the String compareTo will give me the order
if ((c = compare1.getLocalName().compareTo(compare2.getLocalName())) != 0) {
return c;
}
// if the namespaceURI is unequal (e.g. style vs. text)
// the String compareTo will give me the order
if ((c = compare1.getNamespaceURI().compareTo(compare2.getNamespaceURI())) != 0) {
return c;
}
// compare number of attributes
int attr_count1 = compare1.getAttributes() != null ? compare1.getAttributes().getLength() : 0;
int attr_count2 = compare2.getAttributes() != null ? compare2.getAttributes().getLength() : 0;
// attributes with default values do not exist in the ODFDOM XML model
if (attr_count1 != attr_count2) {
return attr_count1 < attr_count2 ? -1 : 1;
}
// sort attributes by namespace:localname, omit style name
SortedMap<String, String> attr1 = getSortedAttributes(compare1);
SortedMap<String, String> attr2 = getSortedAttributes(compare2);
// compare2 attribute names and values
Iterator<String> keySet1Iter = attr1.keySet().iterator();
Iterator<String> keySet2Iter = attr2.keySet().iterator();
while (keySet1Iter.hasNext()) {
String key1 = keySet1Iter.next();
String key2 = keySet2Iter.next();
if ((c = key1.compareTo(key2)) != 0) {
return c;
}
String attrValue1 = attr1.get(key1);
String attrValue2 = attr2.get(key1);
if ((c = attrValue1.compareTo(attrValue2)) != 0) {
return c;
}
}
// now number of child elements
ArrayList<Node> nodes1 = getNonEmptyChildNodes(compare1);
ArrayList<Node> nodes2 = getNonEmptyChildNodes(compare2);
if (nodes1.size() != nodes2.size()) {
return nodes1.size() < nodes2.size() ? -1 : 1;
}
// now compare child elements
Iterator<Node> iter1 = nodes1.iterator();
Iterator<Node> iter2 = nodes2.iterator();
while (iter1.hasNext()) {
Node child1 = iter1.next();
Node child2 = iter2.next();
if ((c = compareNodes(child1, child2)) != 0) {
return c;
}
}
return 0;
}
// helper function for compareTo.
// sorts attributes by namespace:localname
private static SortedMap<String, String> getSortedAttributes(Node node) {
SortedMap<String, String> ret = new TreeMap<String, String>();
NamedNodeMap attrs = node.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Node cur = attrs.item(i);
String namespace = cur.getNamespaceURI();
String local = cur.getLocalName();
// styles can be still the same, even if they have different names
if (local.equals("name") && namespace.equals(OdfDocumentNamespace.STYLE.getUri())) {
continue;
}
ret.put(namespace + ":" + local, ((Attr) cur).getValue());
}
return ret;
}
// helper function for compareTo.
// all except "empty" text nodes will be returned
private static ArrayList<Node> getNonEmptyChildNodes(Node node) {
ArrayList<Node> ret = new ArrayList<Node>();
NodeList childs = node.getChildNodes();
for (int i = 0; i < childs.getLength(); i++) {
Node cur = childs.item(i);
if (cur.getNodeType() == Node.TEXT_NODE) {
if (cur.getNodeValue().trim().length() == 0) {
continue; // skip whitespace text nodes
}
}
ret.add(cur);
}
return ret;
}
/**
* Indicates if some other object is equal to this one. The attribute style:name is ignored during
* compare2.
*
* @param obj - the reference object with which to compare2.
* @return true if this object is the same as the obj argument; false otherwise.
*/
@Override
public boolean equals(Object obj) {
return obj != null ? compareTo(obj) == 0 : false;
}
@Override
public int hashCode() {
return 59 * 7
+ (this.mPropertySetElementMap != null ? this.mPropertySetElementMap.hashCode() : 0);
}
/** @return the style family of the style or null if none existent */
public OdfStyleFamily getFamily() {
return null;
}
/** @return the style parent of the style or null if none existent */
public OdfStyleBase getParentStyle() {
return null;
}
}