OdfOfficeAutomaticStyles.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.incubator.doc.office;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
import org.odftoolkit.odfdom.changes.MapHelper;
import org.odftoolkit.odfdom.dom.DefaultElementVisitor;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.attribute.office.OfficeValueTypeAttribute;
import org.odftoolkit.odfdom.dom.attribute.office.OfficeValueTypeAttribute.Value;
import org.odftoolkit.odfdom.dom.element.OdfStylableElement;
import org.odftoolkit.odfdom.dom.element.number.DataStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberBooleanStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberCurrencyStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberDateStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberNumberStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberPercentageStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberTextStyleElement;
import org.odftoolkit.odfdom.dom.element.number.NumberTimeStyleElement;
import org.odftoolkit.odfdom.dom.element.style.StyleMapElement;
import org.odftoolkit.odfdom.dom.element.style.StylePageLayoutElement;
import org.odftoolkit.odfdom.dom.element.style.StyleStyleElement;
import org.odftoolkit.odfdom.dom.element.text.TextListStyleElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberCurrencyStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberDateStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberPercentageStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberTimeStyle;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStylePageLayout;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextListStyle;
import org.odftoolkit.odfdom.pkg.ElementVisitor;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/** Convenient functionality for the parent ODF OpenDocument element */
public abstract class OdfOfficeAutomaticStyles extends OdfStylesBase {
public static final OdfName ELEMENT_NAME =
OdfName.newName(OdfDocumentNamespace.OFFICE, "automatic-styles");
private static final long serialVersionUID = -2925910664631016175L;
// styles that are only in OdfAutomaticStyles
private HashMap<String, OdfStylePageLayout> mPageLayouts;
// styles that are common for OdfStyles and OdfAutomaticStyles
public OdfOfficeAutomaticStyles(OdfFileDom ownerDoc) {
super(ownerDoc, ELEMENT_NAME);
// mStylesBaseImpl = new OdfStylesBase();
}
public OdfName getOdfName() {
return ELEMENT_NAME;
}
/**
* Create an <code>OdfStyle</code> element with style family
*
* @param styleFamily The <code>OdfStyleFamily</code> element
* @return an <code>OdfStyle</code> element
*/
public OdfStyle newStyle(OdfStyleFamily styleFamily) {
OdfFileDom dom = (OdfFileDom) this.ownerDocument;
OdfStyle newStyle = dom.newOdfElement(OdfStyle.class);
newStyle.setStyleFamilyAttribute(styleFamily.getName());
newStyle.setStyleNameAttribute(newUniqueStyleName(styleFamily));
// <style:style> elements are the first type of elements within the automatic styles parent
OdfElement firstChild = this.getFirstChildElement();
// if there another child, add the new <style:style> ahead
if (firstChild != null) {
this.insertBefore(newStyle, firstChild);
} else {
// otherwise append the <style:style> as first child
this.appendChild(newStyle);
}
return newStyle;
}
protected <T extends OdfElement> T getStylesElement(OdfFileDom dom, Class<T> clazz)
throws Exception {
OdfElement stylesRoot = dom.getRootElement();
OdfOfficeAutomaticStyles contentBody =
OdfElement.findFirstChildNode(OdfOfficeAutomaticStyles.class, stylesRoot);
NodeList childs = contentBody.getChildNodes();
for (int i = 0; i < childs.getLength(); i++) {
Node cur = childs.item(i);
if ((cur != null) && clazz.isInstance(cur)) {
return (T) cur;
}
}
return null;
}
/**
* Create an <code>OdfTextListStyle</code> element
*
* @return an <code>OdfTextListStyle</code> element
*/
public OdfTextListStyle newListStyle() {
return newListStyle(newUniqueStyleName(OdfStyleFamily.List));
}
/**
* Create an <code>OdfStylePageLayout</code> element
*
* @return an <code>OdfStylePageLayout</code> element
*/
public OdfStylePageLayout newPageLayout() {
return newPageLayout(newUniqueStyleName(null));
}
/**
* Create an <code>OdfStylePageLayout</code> element
*
* @return an <code>OdfStylePageLayout</code> element
*/
public OdfStylePageLayout newPageLayout(String pageLayoutName) {
OdfFileDom dom = (OdfFileDom) this.ownerDocument;
OdfStylePageLayout newPageLayout = dom.newOdfElement(OdfStylePageLayout.class);
newPageLayout.setStyleNameAttribute(pageLayoutName);
// <style:page-layout always last in automatic styles
// append it to the automatic styles parent
this.appendChild(newPageLayout);
return newPageLayout;
}
/**
* Create an <code>OdfTextListStyle</code> element
*
* @param listStyleName the name of the new list style
* @return an <code>OdfTextListStyle</code> element
*/
public OdfTextListStyle newListStyle(String listStyleName) {
OdfFileDom dom = (OdfFileDom) this.ownerDocument;
OdfTextListStyle newStyle = dom.newOdfElement(OdfTextListStyle.class);
newStyle.setStyleNameAttribute(listStyleName);
// <text:list-style are always the second after the <style:style> elements
OdfElement child = this.getFirstChildElement();
if (child != null) {
// check if the first element is of <style:style>
if (child instanceof StyleStyleElement) {
// search for a following sibling not being a <style:style> element
while (child != null && child instanceof StyleStyleElement) {
child = OdfElement.getNextSiblingElement(child);
}
}
// if such a none <style:style> element exists
if (child != null) {
// check if a style by that name already exists
OdfElement removeChild = child;
while (removeChild != null) {
if (removeChild instanceof OdfTextListStyle
&& ((OdfTextListStyle) removeChild).getStyleNameAttribute().equals(listStyleName)) {
break;
}
removeChild = OdfElement.getNextSiblingElement(removeChild);
}
// add the list style before of this
this.insertBefore(newStyle, child);
if (removeChild != null) this.removeChild(removeChild);
} else {
// otherwise add the list style after the <style:style>
this.appendChild(newStyle);
}
} else {
// add the list style to this element, the empty automatic styles parent
this.appendChild(newStyle);
}
return newStyle;
}
/**
* Returns the <code>OdfStylePageLayout</code> element with the given name.
*
* @param name is the name of the page layout
* @return the page layout or null if there is no such page layout
*/
public OdfStylePageLayout getPageLayout(String name) {
if (mPageLayouts != null) {
return mPageLayouts.get(name);
} else {
return null;
}
}
/**
* Returns the <code>OdfStylePageLayout</code> element with the given name.
*
* @param name is the name of the page layout
* @return the page layout
*/
public OdfStylePageLayout getOrCreatePageLayout(String name) {
OdfStylePageLayout pageLayout = getPageLayout(name);
if (pageLayout == null) {
OdfFileDom dom = (OdfFileDom) this.ownerDocument;
pageLayout = dom.newOdfElement(OdfStylePageLayout.class);
pageLayout.setStyleNameAttribute(name);
this.appendChild(pageLayout);
}
return pageLayout;
}
@Override
public void onOdfNodeInserted(OdfElement node, Node refNode) {
if (node instanceof OdfStylePageLayout) {
OdfStylePageLayout pageLayout = (OdfStylePageLayout) node;
if (mPageLayouts == null) {
mPageLayouts = new HashMap<String, OdfStylePageLayout>();
}
mPageLayouts.put(pageLayout.getStyleNameAttribute(), pageLayout);
} else {
super.onOdfNodeInserted(node, refNode);
}
}
@Override
public void onOdfNodeRemoved(OdfElement node) {
if (node instanceof OdfStylePageLayout) {
if (mPageLayouts != null) {
OdfStylePageLayout pageLayout = (OdfStylePageLayout) node;
mPageLayouts.remove(pageLayout.getStyleNameAttribute());
}
} else {
super.onOdfNodeRemoved(node);
}
}
/**
* This methods removes all automatic styles that are currently not used by any styleable element.
* Additionally all duplicate automatic styles will be removed.
*/
public void optimize() {
Iterator<OdfStyle> iter = getAllStyles().iterator();
SortedSet<OdfStyle> stylesSet = new TreeSet<OdfStyle>();
while (iter.hasNext()) {
OdfStyle cur = iter.next();
// skip styles which are not in use:
if (cur.getStyleUserCount() < 1) {
continue;
}
SortedSet<OdfStyle> tail = stylesSet.tailSet(cur);
OdfStyle found = tail.size() > 0 ? tail.first() : null;
if (found != null && found.equals(cur)) {
// cur already in set. Replace all usages of cur by found:
Iterator<OdfStylableElement> styleUsersIter = cur.getStyleUsers().iterator();
ArrayList<OdfStylableElement> styleUsers = new ArrayList<OdfStylableElement>();
while (styleUsersIter.hasNext()) {
styleUsers.add(styleUsersIter.next());
}
styleUsersIter = styleUsers.iterator();
while (styleUsersIter.hasNext()) {
OdfStylableElement elem = styleUsersIter.next();
OdfStyle autoStyle = elem.getAutomaticStyle();
if (autoStyle != null) {
elem.setStyleName(found.getStyleNameAttribute());
}
}
} else {
stylesSet.add(cur);
}
}
OdfStyle style = OdfElement.findFirstChildNode(OdfStyle.class, this);
while (style != null) {
OdfStyle nextStyle = OdfElement.findNextChildNode(OdfStyle.class, style);
if (style.getStyleUserCount() < 1) {
this.removeChild(style);
}
style = nextStyle;
}
}
/**
* This method makes the style unique
*
* @param referenceStyle The reference <code>OdfStyle</code> element to create a new automatic
* style
* @return an <code>OdfStyle</code> element
*/
public OdfStyle makeStyleUnique(OdfStyle referenceStyle) {
OdfStyle newStyle = null;
if (referenceStyle.getOwnerDocument() != this.getOwnerDocument()) {
// import style from a different dom
newStyle = (OdfStyle) this.getOwnerDocument().importNode(referenceStyle, true);
} else {
// just clone
newStyle = (OdfStyle) referenceStyle.cloneNode(true);
}
newStyle.setStyleNameAttribute(newUniqueStyleName(newStyle.getFamily()));
appendChild(newStyle);
return newStyle;
}
private String newUniqueStyleName(OdfStyleFamily styleFamily) {
String unique_name;
if (styleFamily != null && styleFamily.equals(OdfStyleFamily.List)) {
do {
unique_name = String.format("l%06x", (int) (Math.random() * 0xffffff));
} while (getListStyle(unique_name) != null);
} else {
do {
unique_name = String.format("a%06x", (int) (Math.random() * 0xffffff));
} while (getStyle(unique_name, styleFamily) != null);
}
return unique_name;
}
private DataStyleElement createDataStyleElement(
Value type, String numberFormatCode, String newDataStyleName) {
Value t = type;
if (t == OfficeValueTypeAttribute.Value.VOID) {
t = MapHelper.detectFormatType(numberFormatCode);
}
if (t == OfficeValueTypeAttribute.Value.VOID) {
return null;
}
OdfFileDom fileDom = (OdfFileDom) getOwnerDocument();
DataStyleElement newStyle = null;
switch (t) {
case DATE:
newStyle = new OdfNumberDateStyle(fileDom, numberFormatCode, newDataStyleName);
break;
case BOOLEAN:
newStyle = new NumberBooleanStyleElement(fileDom, newDataStyleName);
break;
case CURRENCY:
newStyle = new OdfNumberCurrencyStyle(fileDom, numberFormatCode, newDataStyleName);
break;
case FLOAT:
newStyle = new OdfNumberStyle(fileDom, numberFormatCode, newDataStyleName);
break;
case PERCENTAGE:
newStyle = new OdfNumberPercentageStyle(fileDom, numberFormatCode, newDataStyleName);
break;
case STRING:
newStyle = new NumberTextStyleElement(fileDom, numberFormatCode, newDataStyleName);
break;
case TIME:
newStyle = new OdfNumberTimeStyle(fileDom, numberFormatCode, newDataStyleName);
break;
case VOID:
// can never happen, already blocked above
break;
}
return newStyle;
}
public DataStyleElement createDataStyle(
Value type, String numberFormatCode, String newDataStyleName) {
/* conditional formats:
- default conditions:
tow parts: "value()>=0";"value()<0"
three parts: "value()>0";"value()<0";value()==0
The last one is in general
*/
ArrayList<String> partArray = new ArrayList<String>();
// TODO: skip quoted parts - check fails if semicolon is in quotes
while (numberFormatCode.contains(";")) {
int partStart = numberFormatCode.lastIndexOf(";", numberFormatCode.length());
String part = numberFormatCode.substring(partStart + 1);
partArray.add(0, part);
numberFormatCode = numberFormatCode.substring(0, partStart);
}
partArray.add(0, numberFormatCode);
// this is the anchor style in case multiple parts are required
DataStyleElement newStyle =
createDataStyleElement(type, partArray.get(partArray.size() - 1), newDataStyleName);
if (partArray.size() > 1) {
OdfFileDom fileDom = (OdfFileDom) getOwnerDocument();
for (int partIndex = 0; partIndex < partArray.size() - 1; ++partIndex) {
String partStyleName = newDataStyleName + "P" + partIndex;
String part = partArray.get(partIndex);
DataStyleElement partStyle = createDataStyleElement(Value.VOID, part, partStyleName);
// generate an appropriate condition and add this style as sub style and make part style a
// volatile style
String condition = "value()";
// TODO: extract condition if available (e.g. '[>5]')
condition += partArray.size() == 1 ? ">=0" : partIndex == 0 ? ">0" : "<0";
StyleMapElement styleMap = fileDom.newOdfElement(StyleMapElement.class);
styleMap.setStyleApplyStyleNameAttribute(partStyleName);
styleMap.setStyleConditionAttribute(condition);
newStyle.appendChild(styleMap);
partStyle.setAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "style:volatile", "true");
appendChild(partStyle);
}
}
return (DataStyleElement) appendChild(newStyle);
}
/**
* Create child element {@odf.element number:boolean-style}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element number:boolean-style}
*/
public NumberBooleanStyleElement newNumberBooleanStyleElement(String styleNameValue) {
NumberBooleanStyleElement numberBooleanStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(NumberBooleanStyleElement.class);
numberBooleanStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(numberBooleanStyle);
return numberBooleanStyle;
}
/**
* Create child element {@odf.element number:currency-style}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element number:currency-style}
*/
public NumberCurrencyStyleElement newNumberCurrencyStyleElement(String styleNameValue) {
NumberCurrencyStyleElement numberCurrencyStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(NumberCurrencyStyleElement.class);
numberCurrencyStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(numberCurrencyStyle);
return numberCurrencyStyle;
}
/**
* Create child element {@odf.element number:date-style}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element number:date-style}
*/
public NumberDateStyleElement newNumberDateStyleElement(String styleNameValue) {
NumberDateStyleElement numberDateStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(NumberDateStyleElement.class);
numberDateStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(numberDateStyle);
return numberDateStyle;
}
/**
* Create child element {@odf.element number:number-style}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element number:number-style}
*/
public NumberNumberStyleElement newNumberNumberStyleElement(String styleNameValue) {
NumberNumberStyleElement numberNumberStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(NumberNumberStyleElement.class);
numberNumberStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(numberNumberStyle);
return numberNumberStyle;
}
/**
* Create child element {@odf.element number:percentage-style}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element number:percentage-style}
*/
public NumberPercentageStyleElement newNumberPercentageStyleElement(String styleNameValue) {
NumberPercentageStyleElement numberPercentageStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(NumberPercentageStyleElement.class);
numberPercentageStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(numberPercentageStyle);
return numberPercentageStyle;
}
/**
* Create child element {@odf.element number:text-style}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element number:text-style}
*/
public NumberTextStyleElement newNumberTextStyleElement(String styleNameValue) {
NumberTextStyleElement numberTextStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(NumberTextStyleElement.class);
numberTextStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(numberTextStyle);
return numberTextStyle;
}
/**
* Create child element {@odf.element number:time-style}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element number:time-style}
*/
public NumberTimeStyleElement newNumberTimeStyleElement(String styleNameValue) {
NumberTimeStyleElement numberTimeStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(NumberTimeStyleElement.class);
numberTimeStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(numberTimeStyle);
return numberTimeStyle;
}
/**
* Create child element {@odf.element style:page-layout}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element style:page-layout}
*/
public StylePageLayoutElement newStylePageLayoutElement(String styleNameValue) {
StylePageLayoutElement stylePageLayout =
((OdfFileDom) this.ownerDocument).newOdfElement(StylePageLayoutElement.class);
stylePageLayout.setStyleNameAttribute(styleNameValue);
this.appendChild(stylePageLayout);
return stylePageLayout;
}
/**
* Create child element {@odf.element style:style}.
*
* @param styleFamilyValue the <code>String</code> value of <code>StyleFamilyAttribute</code>, see
* {@odf.attribute style:family} at specification
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element style:style}
*/
public StyleStyleElement newStyleStyleElement(String styleFamilyValue, String styleNameValue) {
StyleStyleElement styleStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(StyleStyleElement.class);
styleStyle.setStyleFamilyAttribute(styleFamilyValue);
styleStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(styleStyle);
return styleStyle;
}
/**
* Create child element {@odf.element text:list-style}.
*
* @param styleNameValue the <code>String</code> value of <code>StyleNameAttribute</code>, see
* {@odf.attribute style:name} at specification
* @return the element {@odf.element text:list-style}
*/
public TextListStyleElement newTextListStyleElement(String styleNameValue) {
TextListStyleElement textListStyle =
((OdfFileDom) this.ownerDocument).newOdfElement(TextListStyleElement.class);
textListStyle.setStyleNameAttribute(styleNameValue);
this.appendChild(textListStyle);
return textListStyle;
}
@Override
public void accept(ElementVisitor visitor) {
if (visitor instanceof DefaultElementVisitor) {
DefaultElementVisitor defaultVisitor = (DefaultElementVisitor) visitor;
defaultVisitor.visit(this);
} else {
visitor.visit(this);
}
}
}