/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008 jOpenDocument, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU
* General Public License Version 3 only ("GPL").
* You may not use this file except in compliance with the License.
* You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
* See the License for the specific language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*
*/
package org.jopendocument.dom;
import org.jopendocument.dom.text.TextNode;
import org.jopendocument.util.CollectionMap;
import org.jopendocument.util.CopyUtils;
import org.jopendocument.util.ExceptionUtils;
import org.jopendocument.util.ProductInfo;
import org.jopendocument.util.cc.ITransformer;
import org.jopendocument.util.JDOMUtils;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.xpath.XPath;
/**
* An XML document containing all of an office document, see section 2.1 of OpenDocument 1.1.
*
* @author Sylvain CUAZ 24 nov. 2004
*/
public class ODSingleXMLDocument extends ODXMLDocument implements Cloneable, ODDocument {
final static Set<String> DONT_PREFIX;
static {
DONT_PREFIX = new HashSet<String>();
// don't touch to user fields and variables
// we want them to be the same across the document
DONT_PREFIX.add("user-field-decl");
DONT_PREFIX.add("user-field-get");
DONT_PREFIX.add("variable-get");
DONT_PREFIX.add("variable-decl");
DONT_PREFIX.add("variable-set");
}
// Voir le TODO du ctor
// public static OOSingleXMLDocument createEmpty() {
// }
/**
* Create a document from a collection of subdocuments.
*
* @param content the content.
* @param style the styles, can be <code>null</code>.
* @return the merged document.
*/
public static ODSingleXMLDocument createFromDocument(Document content, Document style) {
return createFromDocument(content, style, null);
}
public static ODSingleXMLDocument createFromDocument(Document content, Document style, Document settings) {
return createFromDocument(content, style, settings, null, new ODPackage());
}
static ODSingleXMLDocument createFromDocument(Document content, Document style, Document settings, Document meta, ODPackage files) {
final Element root = content.getRootElement();
// see section 2.1.1 first meta, then settings, then the rest
prependToRoot(settings, root);
prependToRoot(meta, root);
final ODSingleXMLDocument single = new ODSingleXMLDocument(content, files);
if (single.getChild("body") == null)
throw new IllegalArgumentException("no body in " + single);
// signal that the xml is a complete document (was document-content)
root.setName("document");
root.setAttribute("mimetype", files.getMimeType(), single.getVersion().getOFFICE());
if (style != null) {
// section 2.1 : Styles used in the document content and automatic styles used in the
// styles themselves.
// more precisely in section 2.1.1 : office:document-styles contains style, master
// style, auto style, font decls ; the last two being also in content.xml but are *not*
// related : eg P1 of styles.xml is *not* the P1 of content.xml
try {
single.mergeAllStyles(new ODXMLDocument(style), true);
} catch (JDOMException e) {
throw new IllegalArgumentException("style is not valid", e);
}
}
return single;
}
private static void prependToRoot(Document settings, final Element root) {
if (settings != null) {
final Element officeSettings = (Element) settings.getRootElement().getChildren().get(0);
root.addContent(0, (Element) officeSettings.clone());
}
}
/**
* Create a document from a file.
*
* @param f an OpenDocument package file.
* @return the merged file.
* @throws JDOMException if the file is not a valid OpenDocument file.
* @throws IOException if the file can't be read.
* @see #createFromDocument(Document, Document)
*/
public static ODSingleXMLDocument createFromFile(File f) throws JDOMException, IOException {
// this loads all linked files
return new ODPackage(f).toSingle();
}
/**
* fix bug when a SingleXMLDoc is used to create a document (for example with P2 and 1_P2), and
* then create another instance s2 with the previous document and add a second file (also with
* P2 and 1_P2) => s2 will contain P2, 1_P2, 1_P2, 1_1_P2.
*/
private static final String COUNT = "SingleXMLDocument_count";
/** Le nombre de fichiers concat */
private int numero;
/** Les styles présent dans ce document */
private final Set<String> stylesNames;
/** Les styles de liste présent dans ce document */
private final Set<String> listStylesNames;
/** Les fichiers référencés par ce document */
private final ODPackage pkg;
private final ODMeta meta;
// the element between each page
private Element pageBreak;
public ODSingleXMLDocument(Document content) {
this(content, new ODPackage());
}
/**
* A new single document. NOTE: this document will put himself in <code>pkg</code>, replacing
* any previous content.
*
* @param content the XML.
* @param pkg the package this document belongs to.
*/
private ODSingleXMLDocument(Document content, final ODPackage pkg) {
super(content);
// inited in getPageBreak()
this.pageBreak = null;
this.pkg = pkg;
this.pkg.rmFile("styles.xml");
this.pkg.rmFile("settings.xml");
this.pkg.putFile("content.xml", this, "text/xml");
// set the generator
// creates if necessary meta at the right position
this.getChild("meta", true);
final Properties props = ProductInfo.getInstance().getProps();
final String generator;
if (props == null)
generator = this.getClass().getName();
else
generator = props.getProperty("NAME") + "/" + props.getProperty("VERSION");
this.meta = ODMeta.create(this);
this.meta.setGenerator(generator);
final ODUserDefinedMeta userMeta = this.meta.getUserMeta(COUNT);
if (userMeta != null) {
final Object countValue = userMeta.getValue();
if (countValue instanceof Number) {
this.numero = ((Number) countValue).intValue();
} else {
this.numero = new BigDecimal(countValue.toString()).intValue();
}
} else {
// if not hasCount(), it's not us that created content
// so there should not be any 1_
this.setNumero(0);
}
this.stylesNames = new HashSet<String>(64);
this.listStylesNames = new HashSet<String>(16);
// little trick to find the common styles names (not to be prefixed so they remain
// consistent across the added documents)
final Element styles = this.getChild("styles");
if (styles != null) {
// create a second document with our styles to collect names
final Element root = this.getDocument().getRootElement();
final Document clonedDoc = new Document(new Element(root.getName(), root.getNamespace()));
clonedDoc.getRootElement().addContent(styles.detach());
try {
this.mergeStyles(new ODXMLDocument(clonedDoc));
} catch (JDOMException e) {
throw new IllegalArgumentException("can't find common styles names.");
}
// reattach our styles
styles.detach();
this.setChild(styles);
}
}
ODSingleXMLDocument(ODSingleXMLDocument doc, ODPackage p) {
super(doc);
this.stylesNames = new HashSet<String>(doc.stylesNames);
this.listStylesNames = new HashSet<String>(doc.listStylesNames);
this.pkg = p;
this.meta = ODMeta.create(this);
this.setNumero(doc.numero);
}
@Override
public ODSingleXMLDocument clone() {
final ODPackage copy = new ODPackage(this.pkg);
return (ODSingleXMLDocument) copy.getContent();
}
private void setNumero(int numero) {
this.numero = numero;
this.meta.getUserMeta(COUNT, true).setValue(this.numero);
}
/**
* The number of files concatenated with {@link #add(ODSingleXMLDocument)}.
*
* @return number of files concatenated.
*/
public final int getNumero() {
return this.numero;
}
public ODPackage getPackage() {
return this.pkg;
}
/**
* Append a paragraph or a heading.
*
* @param p paragraph to add.
*/
public synchronized void add(TextNode p) {
this.add(p, null, -1);
}
public synchronized void add(TextNode p, Element where, int index) {
// add it first to avoid infinite loop, since setDocument() can call this method
final Element addToElem = where == null ? this.getBody() : where;
if (index < 0)
addToElem.addContent(p.getElement());
else
addToElem.addContent(index, p.getElement());
try {
p.setDocument(this);
} catch (RuntimeException e) {
// the paragraph can throw an exception to notify that is not compatible with us (eg
// missing styles), in that case remove it
p.getElement().detach();
throw e;
}
}
/**
* Append a document.
*
* @param doc the document to add.
*/
public synchronized void add(ODSingleXMLDocument doc) {
// ajoute un saut de page entre chaque document
this.add(doc, true);
}
/**
* Append a document.
*
* @param doc the document to add, <code>null</code> means no-op.
* @param pageBreak whether a page break should be inserted before <code>doc</code>.
*/
public synchronized void add(ODSingleXMLDocument doc, boolean pageBreak) {
if (doc != null && pageBreak)
// only add a page break, if a page was really added
this.getBody().addContent(this.getPageBreak());
this.add(null, 0, doc);
}
public synchronized void replace(Element elem, ODSingleXMLDocument doc) {
final Element parent = elem.getParentElement();
this.add(parent, parent.indexOf(elem), doc);
elem.detach();
}
public synchronized void add(Element where, int index, ODSingleXMLDocument doc) {
if (doc == null)
return;
if (!this.getVersion().equals(doc.getVersion()))
throw new IllegalArgumentException("version mismatch");
this.setNumero(this.numero + 1);
try {
this.mergeEmbedded(doc);
this.mergeSettings(doc);
this.mergeAllStyles(doc, false);
this.mergeBody(where, index, doc);
} catch (JDOMException exn) {
throw new IllegalArgumentException("XML error", exn);
}
}
/**
* Merge the four elements of style.
*
* @param doc the xml document to merge.
* @param sameDoc whether <code>doc</code> is the same OpenDocument than this, eg
* <code>true</code> when merging content.xml and styles.xml.
* @throws JDOMException if an error occurs.
*/
private void mergeAllStyles(ODXMLDocument doc, boolean sameDoc) throws JDOMException {
// no reference
this.mergeFontDecls(doc);
// section 14.1
// § Parent Style only refer to other common styles
// § Next Style cannot refer to an autostyle (only available in common styles)
// § List Style can refer to an autostyle
// § Master Page Name cannot (auto master pages does not exist)
// § Data Style Name (for cells) can
// but since the UI for common styles doesn't allow to customize List Style
// and there is no common styles for tables : office:styles doesn't reference any automatic
// styles
this.mergeStyles(doc);
// on the contrary autostyles do refer to other autostyles :
// choosing "activate bullets" will create an automatic paragraph style:style
// referencing an automatic text:list-style.
this.mergeAutoStyles(doc, !sameDoc);
// section 14.4
// § Page Layout can refer to an autostyle
// § Next Style Name refer to another masterPage
this.mergeMasterStyles(doc, !sameDoc);
}
private void mergeEmbedded(ODSingleXMLDocument doc) {
// since we are adding another document our existing thumbnail is obsolete
this.pkg.rmFile("Thumbnails/thumbnail.png");
// copy the files
final ODPackage opkg = CopyUtils.copy(doc.pkg);
for (final String name : opkg.getEntries()) {
final ODPackageEntry e = opkg.getEntry(name);
if (!ODPackage.isStandardFile(e.getName())) {
this.pkg.putFile(this.prefix(e.getName()), e.getData(), e.getType());
}
}
}
private void mergeSettings(ODSingleXMLDocument doc) throws JDOMException {
this.addIfNotPresent(doc, "./office:settings", 0);
}
/**
* Fusionne les office:font-decls/style:font-decl. On ne préfixe jamais, on ajoute seulement si
* l'attribut style:name est différent.
*
* @param doc le document à fusionner avec celui-ci.
* @throws JDOMException
*/
private void mergeFontDecls(ODXMLDocument doc) throws JDOMException {
if (this.getVersion() == XMLVersion.OOo)
this.mergeUnique(doc, "font-decls", "style:font-decl");
else
this.mergeUnique(doc, "font-face-decls", "style:font-face");
}
// merge everything under office:styles
private void mergeStyles(ODXMLDocument doc) throws JDOMException {
// les default-style (notamment tab-stop-distance)
this.mergeUnique(doc, "styles", "style:default-style", "style:family", NOP_ElementTransformer);
// les styles
this.stylesNames.addAll(this.mergeUnique(doc, "styles", "style:style"));
// on ajoute outline-style si non présent
this.addStylesIfNotPresent(doc, "outline-style");
// les list-style
this.listStylesNames.addAll(this.mergeUnique(doc, "styles", "text:list-style"));
// les *notes-configuration
this.addStylesIfNotPresent(doc, "footnotes-configuration");
this.addStylesIfNotPresent(doc, "endnotes-configuration");
}
/**
* Fusionne les office:automatic-styles, on préfixe tout.
*
* @param doc le document à fusionner avec celui-ci.
* @param ref whether to prefix hrefs.
* @throws JDOMException
*/
private void mergeAutoStyles(ODXMLDocument doc, boolean ref) throws JDOMException {
final List<Element> addedStyles = this.prefixAndAddAutoStyles(doc);
for (final Element addedStyle : addedStyles) {
this.prefix(addedStyle, ref);
}
}
/**
* Fusionne les office:master-styles. On ne préfixe jamais, on ajoute seulement si l'attribut
* style:name est différent.
*
* @param doc le document à fusionner avec celui-ci.
* @param ref whether to prefix hrefs.
* @throws JDOMException if an error occurs.
*/
private void mergeMasterStyles(ODXMLDocument doc, boolean ref) throws JDOMException {
// est référencé dans les styles avec "style:master-page-name"
this.mergeUnique(doc, "master-styles", "style:master-page", ref ? this.prefixTransf : this.prefixTransfNoRef);
}
/**
* Fusionne les corps.
*
* @param doc le document à fusionner avec celui-ci.
* @throws JDOMException
*/
private void mergeBody(Element where, int index, ODSingleXMLDocument doc) throws JDOMException {
this.add(where, index, doc, this.getBodyPath(), new ElementTransformer() {
public Element transform(Element elem) throws JDOMException {
// ATTN n'ajoute pas sequence-decls
if (elem.getName().equals("sequence-decls"))
return null;
if (elem.getName().equals("user-field-decls")) {
// user fields are global to a document, they do not vary across it.
// hence they are initialized at declaration
// we should assure that there's no 2 declaration with the same name
detachDuplicate(elem);
}
if (elem.getName().equals("variable-decls")) {
// variables are not initialized at declaration
// we should still assure that there's no 2 declaration with the same name
detachDuplicate(elem);
}
// par défaut
return ODSingleXMLDocument.this.prefixTransf.transform(elem);
}
});
}
/**
* Detach the children of elem whose names already exist in the body.
*
* @param elem the elem to be trimmed.
* @throws JDOMException if an error occurs.
*/
protected final void detachDuplicate(Element elem) throws JDOMException {
final String singularName = elem.getName().substring(0, elem.getName().length() - 1);
final List thisNames = getXPath("./text:" + singularName + "s/text:" + singularName + "/@text:name").selectNodes(getChild("body"));
CollectionUtils.transform(thisNames, new Transformer() {
public Object transform(Object obj) {
return ((Attribute) obj).getValue();
}
});
final Iterator iter = elem.getChildren().iterator();
while (iter.hasNext()) {
final Element decl = (Element) iter.next();
if (thisNames.contains(decl.getAttributeValue("name", getVersion().getTEXT()))) {
// on retire les déjà existant
iter.remove();
}
}
}
// *** Utils
// returns the xpath to the body
private final String getBodyPath() {
return this.getVersion() == XMLVersion.OOo ? "./office:body" : "./office:body/office:text";
}
public final Element getBody() {
try {
return this.getDescendant(this.getBodyPath());
} catch (JDOMException e) {
throw new IllegalStateException("no body", e);
}
}
/**
* Verify that styles referenced by this document are indeed defined. NOTE this method is not
* perfect : not all problems are detected.
*
* @return <code>null</code> if no problem has been found, else a String describing it.
*/
public final String checkStyles() {
try {
final CollectionMap<String, String> stylesNames = this.getStylesNames();
// text:style-name : text:p, text:span
// table:style-name : table:table, table:row, table:column, table:cell
// draw:style-name : draw:text-box
// style:data-style-name : <style:style style:family="table-cell">
// TODO check by family
final Set<String> names = new HashSet<String>(stylesNames.values());
final Iterator attrs = this.getXPath(".//@text:style-name | .//@table:style-name | .//@draw:style-name | .//@style:data-style-name | .//@style:list-style-name").selectNodes(
this.getDocument()).iterator();
while (attrs.hasNext()) {
final Attribute attr = (Attribute) attrs.next();
if (!names.contains(attr.getValue()))
return "unknown style referenced by " + attr.getName() + " in " + JDOMUtils.output(attr.getParent());
}
// TODO check other references like page-*-name (§3 of #prefix())
} catch (IllegalStateException e) {
return ExceptionUtils.getStackTrace(e);
} catch (JDOMException e) {
return ExceptionUtils.getStackTrace(e);
}
return null;
}
private final CollectionMap<String, String> getStylesNames() throws IllegalStateException {
// section 14.1 § Style Name : style:family + style:name is unique
final CollectionMap<String, String> res = new CollectionMap<String, String>(HashSet.class);
final List<Element> nodes = new ArrayList<Element>();
nodes.add(this.getChild("styles"));
nodes.add(this.getChild("automatic-styles"));
try {
{
final Iterator iter = this.getXPath("./style:style/@style:name").selectNodes(nodes).iterator();
while (iter.hasNext()) {
final Attribute attr = (Attribute) iter.next();
final String styleName = attr.getValue();
final String family = attr.getParent().getAttributeValue("family", attr.getNamespace());
if (res.getNonNull(family).contains(styleName))
throw new IllegalStateException("duplicate style in " + family + " : " + styleName);
res.put(family, styleName);
}
}
{
final List<String> dataStyles = Arrays.asList("number-style", "currency-style", "percentage-style", "date-style", "time-style", "boolean-style", "text-style");
final String xpDataStyles = org.jopendocument.util.CollectionUtils.join(dataStyles, " | ", new ITransformer<String, String>() {
public String transformChecked(String input) {
return "./number:" + input;
}
});
final Iterator listIter = this.getXPath("./text:list-style | " + xpDataStyles).selectNodes(nodes).iterator();
while (listIter.hasNext()) {
final Element elem = (Element) listIter.next();
res.put(elem.getQualifiedName(), elem.getAttributeValue("name", getVersion().getSTYLE()));
}
}
} catch (JDOMException e) {
throw new IllegalStateException(e);
}
return res;
}
/**
* Préfixe les attributs en ayant besoin.
*
* @param elem l'élément à préfixer.
* @param references whether to prefix hrefs.
* @throws JDOMException if an error occurs.
*/
void prefix(Element elem, boolean references) throws JDOMException {
Iterator attrs = this.getXPath(".//@text:style-name | .//@table:style-name | .//@draw:style-name | .//@style:data-style-name").selectNodes(elem).iterator();
while (attrs.hasNext()) {
Attribute attr = (Attribute) attrs.next();
// text:list/@text:style-name references text:list-style
if (!this.listStylesNames.contains(attr.getValue()) && !this.stylesNames.contains(attr.getValue())) {
attr.setValue(this.prefix(attr.getValue()));
}
}
attrs = this.getXPath(".//@style:list-style-name").selectNodes(elem).iterator();
while (attrs.hasNext()) {
Attribute attr = (Attribute) attrs.next();
if (!this.listStylesNames.contains(attr.getValue())) {
attr.setValue(this.prefix(attr.getValue()));
}
}
attrs = this.getXPath(".//@style:page-master-name | .//@style:page-layout-name | .//@text:name | .//@form:name | .//@form:property-name").selectNodes(elem).iterator();
while (attrs.hasNext()) {
final Attribute attr = (Attribute) attrs.next();
final String parentName = attr.getParent().getName();
if (!DONT_PREFIX.contains(parentName))
attr.setValue(this.prefix(attr.getValue()));
}
// prefix references
if (references) {
attrs = this.getXPath(".//@xlink:href[../@xlink:show='embed']").selectNodes(elem).iterator();
while (attrs.hasNext()) {
final Attribute attr = (Attribute) attrs.next();
final String prefixedPath = this.prefixPath(attr.getValue());
if (prefixedPath != null)
attr.setValue(prefixedPath);
}
}
}
/**
* Prefix a path.
*
* @param href a path inside the pkg, eg "./Object 1/content.xml".
* @return the prefixed path or <code>null</code> if href is external, eg "./3_Object
* 1/content.xml".
*/
private String prefixPath(final String href) {
if (this.getVersion().equals(XMLVersion.OOo)) {
// in OOo 1.x inPKG is denoted by a #
final boolean sharp = href.startsWith("#");
if (sharp)
// eg #Pictures/100000000000006C000000ABCC02339E.png
return "#" + this.prefix(href.substring(1));
else
// eg ../../../../Program%20Files/OpenOffice.org1.1.5/share/gallery/apples.gif
return null;
} else {
URI uri;
try {
uri = new URI(href);
} catch (URISyntaxException e) {
// OO doesn't escape characters for files
uri = null;
}
// section 17.5
final boolean inPKGFile = uri == null || uri.getScheme() == null && uri.getAuthority() == null && uri.getPath().charAt(0) != '/';
if (inPKGFile) {
final String dotSlash = "./";
if (href.startsWith(dotSlash))
return dotSlash + this.prefix(href.substring(dotSlash.length()));
else
return this.prefix(href);
} else
return null;
}
}
private String prefix(String value) {
return "_" + this.numero + value;
}
private final ElementTransformer prefixTransf = new ElementTransformer() {
public Element transform(Element elem) throws JDOMException {
ODSingleXMLDocument.this.prefix(elem, true);
return elem;
}
};
private final ElementTransformer prefixTransfNoRef = new ElementTransformer() {
public Element transform(Element elem) throws JDOMException {
ODSingleXMLDocument.this.prefix(elem, false);
return elem;
}
};
/**
* Ajoute dans ce document seulement les éléments de doc correspondant au XPath spécifié et dont
* la valeur de l'attribut style:name n'existe pas déjà.
*
* @param doc le document à fusionner avec celui-ci.
* @param topElem eg "office:font-decls".
* @param elemToMerge les éléments à fusionner (par rapport à topElem), eg "style:font-decl".
* @return les noms des éléments ajoutés.
* @throws JDOMException
* @see #mergeUnique(ODSingleXMLDocument, String, String, ElementTransformer)
*/
private List<String> mergeUnique(ODXMLDocument doc, String topElem, String elemToMerge) throws JDOMException {
return this.mergeUnique(doc, topElem, elemToMerge, NOP_ElementTransformer);
}
/**
* Ajoute dans ce document seulement les éléments de doc correspondant au XPath spécifié et dont
* la valeur de l'attribut style:name n'existe pas déjà. En conséquence n'ajoute que les
* éléments possédant un attribut style:name.
*
* @param doc le document à fusionner avec celui-ci.
* @param topElem eg "office:font-decls".
* @param elemToMerge les éléments à fusionner (par rapport à topElem), eg "style:font-decl".
* @param addTransf la transformation à appliquer avant d'ajouter.
* @return les noms des éléments ajoutés.
* @throws JDOMException
*/
private List<String> mergeUnique(ODXMLDocument doc, String topElem, String elemToMerge, ElementTransformer addTransf) throws JDOMException {
return this.mergeUnique(doc, topElem, elemToMerge, "style:name", addTransf);
}
private List<String> mergeUnique(ODXMLDocument doc, String topElem, String elemToMerge, String attrFQName, ElementTransformer addTransf) throws JDOMException {
List<String> added = new ArrayList<String>();
Element thisParent = this.getChild(topElem, true);
XPath xp = this.getXPath("./" + elemToMerge + "/@" + attrFQName);
// les styles de ce document
List thisElemNames = xp.selectNodes(thisParent);
// on transforme la liste d'attributs en liste de String
CollectionUtils.transform(thisElemNames, new Transformer() {
public Object transform(Object obj) {
return ((Attribute) obj).getValue();
}
});
// pour chaque style de l'autre document
Iterator otherElemNames = xp.selectNodes(doc.getChild(topElem)).iterator();
while (otherElemNames.hasNext()) {
Attribute attr = (Attribute) otherElemNames.next();
// on l'ajoute si non déjà dedans
if (!thisElemNames.contains(attr.getValue())) {
thisParent.addContent(addTransf.transform((Element) attr.getParent().clone()));
added.add(attr.getValue());
}
}
return added;
}
/**
* Ajoute l'élément elemName de doc, s'il n'est pas dans ce document.
*
* @param doc le document à fusionner avec celui-ci.
* @param elemName l'élément à ajouter, eg "outline-style".
* @throws JDOMException if elemName is not valid.
*/
private void addStylesIfNotPresent(ODXMLDocument doc, String elemName) throws JDOMException {
this.addIfNotPresent(doc, "./office:styles/text:" + elemName);
}
/**
* Prefixe les fils de auto-styles possédant un attribut "name" avant de les ajouter.
*
* @param doc le document à fusionner avec celui-ci.
* @return les élément ayant été ajoutés.
* @throws JDOMException
*/
private List<Element> prefixAndAddAutoStyles(ODXMLDocument doc) throws JDOMException {
final List<Element> result = new ArrayList<Element>(128);
final List otherNames = this.getXPath("./*/@style:name").selectNodes(doc.getChild("automatic-styles"));
Iterator iter = otherNames.iterator();
while (iter.hasNext()) {
Attribute attr = (Attribute) iter.next();
Element parent = (Element) attr.getParent().clone();
parent.setAttribute("name", this.prefix(attr.getValue()), this.getVersion().getSTYLE());
this.getChild("automatic-styles").addContent(parent);
result.add(parent);
}
return result;
}
/**
* Saves this OO document to a file.
*
* @param f the file where this document will be saved, without extension, eg "dir/myfile".
* @return the actual file where it has been saved (with extension), eg "dir/myfile.odt".
* @throws IOException if an error occurs.
*/
public File saveAs(File f) throws IOException {
return this.pkg.saveAs(f);
}
private Element getPageBreak() {
if (this.pageBreak == null) {
final String styleName = "PageBreak";
try {
final XPath xp = this.getXPath("./style:style[@style:name='" + styleName + "']");
final Element styles = this.getChild("styles", true);
if (xp.selectSingleNode(styles) == null) {
final Element pageBreakStyle = new Element("style", this.getVersion().getSTYLE());
pageBreakStyle.setAttribute("name", styleName, this.getVersion().getSTYLE());
pageBreakStyle.setAttribute("family", "paragraph", this.getVersion().getSTYLE());
pageBreakStyle.setContent(getPProps().setAttribute("break-after", "page", this.getVersion().getNS("fo")));
// <element name="office:styles"> <interleave>...
// so just append the new style
styles.addContent(pageBreakStyle);
}
} catch (JDOMException e) {
// static path, shouldn't happen
throw new IllegalStateException("pb while searching for " + styleName, e);
}
this.pageBreak = new Element("p", this.getVersion().getTEXT()).setAttribute("style-name", styleName, this.getVersion().getTEXT());
}
return (Element) this.pageBreak.clone();
}
private final Element getPProps() {
return new Element(this.getVersion().equals(XMLVersion.OD) ? "paragraph-properties" : "properties", this.getVersion().getSTYLE());
}
}