| /************************************************************************ |
| * |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER |
| * |
| * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved. |
| * |
| * Use is subject to license terms. |
| * |
| * 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 |
| * |
| * 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.odftoolkit.odfdom.dom.element; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| import java.util.Map.Entry; |
| |
| 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.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.w3c.dom.Attr; |
| import org.w3c.dom.DOMException; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| /** |
| * |
| */ |
| abstract public 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. |
| */ |
| 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. |
| */ |
| 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 |
| // |
| static private 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); |
| } |
| |
| public OdfStyleFamily getFamily() { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public OdfStyleBase getParentStyle() { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| } |