OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 83 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 83 Rev 180
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
9
 * language governing permissions and limitations under the License.
10
 * 
10
 * 
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 /*
14
 /*
15
 * Créé le 28 oct. 2004
15
 * Créé le 28 oct. 2004
16
 */
16
 */
17
package org.openconcerto.openoffice;
17
package org.openconcerto.openoffice;
18
 
18
 
19
import org.openconcerto.openoffice.ODPackage.RootElement;
19
import org.openconcerto.openoffice.ODPackage.RootElement;
-
 
20
import org.openconcerto.utils.cache.LRUMap;
20
import org.openconcerto.utils.cc.IFactory;
21
import org.openconcerto.utils.cc.IFactory;
21
import org.openconcerto.xml.JDOMUtils;
22
import org.openconcerto.xml.JDOMUtils;
22
import org.openconcerto.xml.Validator;
23
import org.openconcerto.xml.Validator;
23
import org.openconcerto.xml.XPathUtils;
24
import org.openconcerto.xml.XPathUtils;
24
 
25
 
25
import java.util.ArrayList;
26
import java.util.ArrayList;
26
import java.util.Collections;
27
import java.util.Collections;
27
import java.util.HashMap;
28
import java.util.HashMap;
28
import java.util.Iterator;
29
import java.util.Iterator;
29
import java.util.LinkedHashMap;
-
 
30
import java.util.List;
30
import java.util.List;
31
import java.util.Map;
31
import java.util.Map;
32
import java.util.Set;
32
import java.util.Set;
33
 
33
 
34
import org.jdom.Content;
34
import org.jdom.Content;
35
import org.jdom.Document;
35
import org.jdom.Document;
36
import org.jdom.Element;
36
import org.jdom.Element;
37
import org.jdom.JDOMException;
37
import org.jdom.JDOMException;
38
import org.jdom.Namespace;
38
import org.jdom.Namespace;
39
import org.jdom.xpath.XPath;
39
import org.jdom.xpath.XPath;
40
 
40
 
41
/**
41
/**
42
 * An OpenDocument XML document, like content.xml ou styles.xml.
42
 * An OpenDocument XML document, like content.xml ou styles.xml.
43
 * 
43
 * 
44
 * @author Sylvain CUAZ
44
 * @author Sylvain CUAZ
45
 */
45
 */
46
public class ODXMLDocument {
46
public class ODXMLDocument {
47
 
47
 
48
    /**
48
    /**
49
     * All top-level elements that an office document may contain. Note that only the single xml
49
     * All top-level elements that an office document may contain. Note that only the single xml
50
     * representation (office:document) contains all of them.
50
     * representation (office:document) contains all of them.
51
     */
51
     */
52
    private static final Map<XMLVersion, List<Element>> ELEMS_ORDER;
52
    private static final Map<XMLVersion, List<Element>> ELEMS_ORDER;
53
    static {
53
    static {
54
        ELEMS_ORDER = new HashMap<XMLVersion, List<Element>>(2);
54
        ELEMS_ORDER = new HashMap<XMLVersion, List<Element>>(2);
55
        ELEMS_ORDER.put(XMLVersion.getOOo(), createChildren(XMLVersion.getOOo()));
55
        ELEMS_ORDER.put(XMLVersion.getOOo(), createChildren(XMLVersion.getOOo()));
56
        ELEMS_ORDER.put(XMLVersion.getOD(), createChildren(XMLVersion.getOD()));
56
        ELEMS_ORDER.put(XMLVersion.getOD(), createChildren(XMLVersion.getOD()));
57
    }
57
    }
58
 
58
 
59
    private static final List<Element> createChildren(XMLVersion ins) {
59
    private static final List<Element> createChildren(XMLVersion ins) {
60
        final Namespace ns = ins.getOFFICE();
60
        final Namespace ns = ins.getOFFICE();
61
        final List<Element> res = new ArrayList<Element>(8);
61
        final List<Element> res = new ArrayList<Element>(8);
62
        res.add(new Element("meta", ns));
62
        res.add(new Element("meta", ns));
63
        res.add(new Element("settings", ns));
63
        res.add(new Element("settings", ns));
64
        final OOXML xml = OOXML.getLast(ins);
64
        final OOXML xml = OOXML.getLast(ins);
65
        res.add(new Element(xml.getOfficeScripts(), ns));
65
        res.add(new Element(xml.getOfficeScripts(), ns));
66
        res.add(new Element(xml.getFontDecls()[0], ns));
66
        res.add(new Element(xml.getFontDecls()[0], ns));
67
        res.add(new Element("styles", ns));
67
        res.add(new Element("styles", ns));
68
        res.add(new Element("automatic-styles", ns));
68
        res.add(new Element("automatic-styles", ns));
69
        res.add(new Element("master-styles", ns));
69
        res.add(new Element("master-styles", ns));
70
        res.add(new Element("body", ns));
70
        res.add(new Element("body", ns));
71
        return res;
71
        return res;
72
    }
72
    }
73
 
73
 
74
    static private final int ITERATIONS_WARNING_COUNT = 2000;
74
    static private final int ITERATIONS_WARNING_COUNT = 2000;
75
    // namespaces for the name attributes
75
    // namespaces for the name attributes
76
    static private final Map<String, String> namePrefixes;
76
    static private final Map<String, String> namePrefixes;
77
    static {
77
    static {
78
        namePrefixes = new HashMap<String, String>();
78
        namePrefixes = new HashMap<String, String>();
79
        namePrefixes.put("table:table", "table");
79
        namePrefixes.put("table:table", "table");
80
        namePrefixes.put("text:a", "office");
80
        namePrefixes.put("text:a", "office");
81
        namePrefixes.put("draw:text-box", "draw");
81
        namePrefixes.put("draw:text-box", "draw");
82
        namePrefixes.put("draw:image", "draw");
82
        namePrefixes.put("draw:image", "draw");
83
        namePrefixes.put("draw:frame", "draw");
83
        namePrefixes.put("draw:frame", "draw");
84
    }
84
    }
85
 
85
 
86
    /**
86
    /**
87
     * The XML elements posessing a name.
87
     * The XML elements posessing a name.
88
     * 
88
     * 
89
     * @return the qualified names of named elements.
89
     * @return the qualified names of named elements.
90
     * @see #getDescendantByName(String, String)
90
     * @see #getDescendantByName(String, String)
91
     */
91
     */
92
    public static Set<String> getNamedElements() {
92
    public static Set<String> getNamedElements() {
93
        return Collections.unmodifiableSet(namePrefixes.keySet());
93
        return Collections.unmodifiableSet(namePrefixes.keySet());
94
    }
94
    }
95
 
95
 
96
    public static final ODXMLDocument create(final Document doc) {
96
    public static final ODXMLDocument create(final Document doc) {
97
        if (RootElement.fromDocument(doc) == RootElement.SINGLE_CONTENT)
97
        if (RootElement.fromDocument(doc) == RootElement.SINGLE_CONTENT)
98
            return new ODSingleXMLDocument(doc);
98
            return new ODSingleXMLDocument(doc);
99
        else
99
        else
100
            return new ODXMLDocument(doc);
100
            return new ODXMLDocument(doc);
101
    }
101
    }
102
 
102
 
103
    private final Document content;
103
    private final Document content;
104
    private final XMLFormatVersion version;
104
    private final XMLFormatVersion version;
105
    private final ChildCreator childCreator;
105
    private final ChildCreator childCreator;
106
    private final Map<String, Integer> styleNamesLast;
106
    private final Map<String, Integer> styleNamesLast;
107
 
107
 
108
    // before making it public, assure that content is really of version "version"
108
    // before making it public, assure that content is really of version "version"
109
    // eg by checking some namespace
109
    // eg by checking some namespace
110
    protected ODXMLDocument(final Document content, final XMLFormatVersion version) {
110
    protected ODXMLDocument(final Document content, final XMLFormatVersion version) {
111
        if (content == null)
111
        if (content == null)
112
            throw new NullPointerException("null document");
112
            throw new NullPointerException("null document");
113
        this.content = content;
113
        this.content = content;
114
        this.version = version;
114
        this.version = version;
115
        this.childCreator = new ChildCreator(this.content.getRootElement(), ELEMS_ORDER.get(this.getVersion()));
115
        this.childCreator = new ChildCreator(this.content.getRootElement(), ELEMS_ORDER.get(this.getVersion()));
116
        this.styleNamesLast = new LinkedHashMap<String, Integer>(4, 0.75f, true) {
116
        this.styleNamesLast = new LRUMap<>(15, 4);
117
            @Override
-
 
118
            protected boolean removeEldestEntry(java.util.Map.Entry<String, Integer> eldest) {
-
 
119
                return this.size() > 15;
-
 
120
            }
-
 
121
        };
-
 
122
    }
117
    }
123
 
118
 
124
    public ODXMLDocument(Document content) {
119
    public ODXMLDocument(Document content) {
125
        this(content, XMLFormatVersion.get(content.getRootElement()));
120
        this(content, XMLFormatVersion.get(content.getRootElement()));
126
    }
121
    }
127
 
122
 
128
    public ODXMLDocument(ODXMLDocument doc) {
123
    public ODXMLDocument(ODXMLDocument doc) {
129
        this((Document) doc.content.clone(), doc.version);
124
        this((Document) doc.content.clone(), doc.version);
130
    }
125
    }
131
 
126
 
132
    public Document getDocument() {
127
    public Document getDocument() {
133
        return this.content;
128
        return this.content;
134
    }
129
    }
135
 
130
 
136
    public Validator getValidator() {
131
    public Validator getValidator() {
137
        return getXML().getValidator(this.getDocument());
132
        return getXML().getValidator(this.getDocument());
138
    }
133
    }
139
 
134
 
140
    public final OOXML getXML() {
135
    public final OOXML getXML() {
141
        return this.getFormatVersion().getXML();
136
        return this.getFormatVersion().getXML();
142
    }
137
    }
143
 
138
 
144
    public final XMLFormatVersion getFormatVersion() {
139
    public final XMLFormatVersion getFormatVersion() {
145
        return this.version;
140
        return this.version;
146
    }
141
    }
147
 
142
 
148
    public final XMLVersion getVersion() {
143
    public final XMLVersion getVersion() {
149
        return this.getFormatVersion().getXMLVersion();
144
        return this.getFormatVersion().getXMLVersion();
150
    }
145
    }
151
 
146
 
152
    // *** children
147
    // *** children
153
 
148
 
154
    public final Element getChild(String childName) {
149
    public final Element getChild(String childName) {
155
        return this.getChild(childName, false);
150
        return this.getChild(childName, false);
156
    }
151
    }
157
 
152
 
158
    /**
153
    /**
159
     * Return the asked child, optionally creating it.
154
     * Return the asked child, optionally creating it.
160
     * 
155
     * 
161
     * @param childName the name of the child.
156
     * @param childName the name of the child.
162
     * @param create whether it should be created in case it doesn't exist.
157
     * @param create whether it should be created in case it doesn't exist.
163
     * @return the asked child or <code>null</code> if it doesn't exist and create is
158
     * @return the asked child or <code>null</code> if it doesn't exist and create is
164
     *         <code>false</code>
159
     *         <code>false</code>
165
     */
160
     */
166
    public Element getChild(String childName, boolean create) {
161
    public Element getChild(String childName, boolean create) {
167
        return this.childCreator.getChild(this.getVersion().getOFFICE(), childName, create);
162
        return this.childCreator.getChild(this.getVersion().getOFFICE(), childName, create);
168
    }
163
    }
169
 
164
 
170
    public void setChild(Element elem) {
165
    public void setChild(Element elem) {
171
        if (!elem.getNamespace().equals(this.getVersion().getOFFICE()))
166
        if (!elem.getNamespace().equals(this.getVersion().getOFFICE()))
172
            throw new IllegalArgumentException("all children of a document belong to the office namespace.");
167
            throw new IllegalArgumentException("all children of a document belong to the office namespace.");
173
        this.childCreator.setChild(elem);
168
        this.childCreator.setChild(elem);
174
    }
169
    }
175
 
170
 
176
    // *** descendants
171
    // *** descendants
177
 
172
 
178
    protected final Element getDescendant(String path) throws JDOMException {
173
    protected final Element getDescendant(String path) throws JDOMException {
179
        return this.getDescendant(path, false);
174
        return this.getDescendant(path, false);
180
    }
175
    }
181
 
176
 
182
    protected final Element getDescendant(String path, boolean create) throws JDOMException {
177
    protected final Element getDescendant(String path, boolean create) throws JDOMException {
183
        Element res = (Element) this.getXPath(path).selectSingleNode(this.getDocument().getRootElement());
178
        Element res = (Element) this.getXPath(path).selectSingleNode(this.getDocument().getRootElement());
184
        if (res == null && create) {
179
        if (res == null && create) {
185
            final Element parent = this.getDescendant(XPathUtils.parentOf(path), create);
180
            final Element parent = this.getDescendant(XPathUtils.parentOf(path), create);
186
            final String namespace = XPathUtils.namespace(path);
181
            final String namespace = XPathUtils.namespace(path);
187
            final Namespace ns = namespace == null ? null : this.getVersion().getNS(namespace);
182
            final Namespace ns = namespace == null ? null : this.getVersion().getNS(namespace);
188
            res = new Element(XPathUtils.localName(path), ns);
183
            res = new Element(XPathUtils.localName(path), ns);
189
            parent.addContent(res);
184
            parent.addContent(res);
190
        }
185
        }
191
        return res;
186
        return res;
192
    }
187
    }
193
 
188
 
194
    public final XPath getXPath(String string) throws JDOMException {
189
    public final XPath getXPath(String string) throws JDOMException {
195
        return OOUtils.getXPath(string, this.getVersion());
190
        return OOUtils.getXPath(string, this.getVersion());
196
    }
191
    }
197
 
192
 
198
    /**
193
    /**
199
     * Search for a descendant with the passed name.
194
     * Search for a descendant with the passed name.
200
     * 
195
     * 
201
     * @param qName the XML element qualified name, eg "table:table".
196
     * @param qName the XML element qualified name, eg "table:table".
202
     * @param name the value of the name, eg "MyTable".
197
     * @param name the value of the name, eg "MyTable".
203
     * @return the first element named <code>name</code> or <code>null</code> if none is found, eg
198
     * @return the first element named <code>name</code> or <code>null</code> if none is found, eg
204
     *         &lt;table:table table:name="MyTable" &gt;
199
     *         &lt;table:table table:name="MyTable" &gt;
205
     * @throws IllegalArgumentException if <code>qName</code> is not in {@link #getNamedElements()}
200
     * @throws IllegalArgumentException if <code>qName</code> is not in {@link #getNamedElements()}
206
     */
201
     */
207
    public final Element getDescendantByName(String qName, String name) {
202
    public final Element getDescendantByName(String qName, String name) {
208
        return this.getDescendantByName(this.getDocument().getRootElement(), qName, name);
203
        return this.getDescendantByName(this.getDocument().getRootElement(), qName, name);
209
    }
204
    }
210
 
205
 
211
    public final Element getDescendantByName(Element root, String qName, String name) {
206
    public final Element getDescendantByName(Element root, String qName, String name) {
212
        if (root.getDocument() != this.getDocument())
207
        if (root.getDocument() != this.getDocument())
213
            throw new IllegalArgumentException("root is not part of this.");
208
            throw new IllegalArgumentException("root is not part of this.");
214
        if (!namePrefixes.containsKey(qName))
209
        if (!namePrefixes.containsKey(qName))
215
            throw new IllegalArgumentException(qName + " not in " + getNamedElements());
210
            throw new IllegalArgumentException(qName + " not in " + getNamedElements());
216
        final String xp = ".//" + qName + "[@" + namePrefixes.get(qName) + ":name='" + name + "']";
211
        final String xp = ".//" + qName + "[@" + namePrefixes.get(qName) + ":name='" + name + "']";
217
        try {
212
        try {
218
            return (Element) this.getXPath(xp).selectSingleNode(root);
213
            return (Element) this.getXPath(xp).selectSingleNode(root);
219
        } catch (JDOMException e) {
214
        } catch (JDOMException e) {
220
            // static xpath, should not happen
215
            // static xpath, should not happen
221
            throw new IllegalStateException("could not find " + xp, e);
216
            throw new IllegalStateException("could not find " + xp, e);
222
        }
217
        }
223
    }
218
    }
224
 
219
 
225
    // *** styles
220
    // *** styles
226
    public final Element getStyle(final StyleDesc<?> styleDesc, final String name) {
221
    public final Element getStyle(final StyleDesc<?> styleDesc, final String name) {
227
        return this.getStyle(styleDesc, name, this.getDocument());
222
        return this.getStyle(styleDesc, name, this.getDocument());
228
    }
223
    }
229
 
224
 
230
    public final Element getStyle(final StyleDesc<?> styleDesc, final String name, final Document referent) {
225
    public final Element getStyle(final StyleDesc<?> styleDesc, final String name, final Document referent) {
231
        final String family = styleDesc instanceof StyleStyleDesc<?> ? ((StyleStyleDesc<?>) styleDesc).getFamily() : null;
226
        final String family = styleDesc instanceof StyleStyleDesc<?> ? ((StyleStyleDesc<?>) styleDesc).getFamily() : null;
232
        // see section 14.1 § Style Name : "uniquely identifies a style"
227
        // see section 14.1 § Style Name : "uniquely identifies a style"
233
        // was using an XPath but we had performance issues, we first rewrote it using variables
228
        // was using an XPath but we had performance issues, we first rewrote it using variables
234
        // (and thus parsing it only once) and saw a 40% speedup, but by rewriting it in java we
229
        // (and thus parsing it only once) and saw a 40% speedup, but by rewriting it in java we
235
        // we went from 70ms/instance + 1ms/call to 0.015ms/call :-)
230
        // we went from 70ms/instance + 1ms/call to 0.015ms/call :-)
236
        // final String stylePath = "style:style[@style:family=$family and @style:name=$name]";
231
        // final String stylePath = "style:style[@style:family=$family and @style:name=$name]";
237
        // this.styleXP = this.getXPath("./office:styles/" + stylePath +
232
        // this.styleXP = this.getXPath("./office:styles/" + stylePath +
238
        // " | ./office:automatic-styles/" + stylePath +
233
        // " | ./office:automatic-styles/" + stylePath +
239
        // " | ./office:master-styles/style:master-page/" + stylePath);
234
        // " | ./office:master-styles/style:master-page/" + stylePath);
240
        final Element root = this.getDocument().getRootElement();
235
        final Element root = this.getDocument().getRootElement();
241
        final Namespace office = getVersion().getOFFICE();
236
        final Namespace office = getVersion().getOFFICE();
242
        Element res = this.findStyleChild(root.getChild("styles", office), styleDesc.getElementNS(), styleDesc.getElementName(), family, name);
237
        Element res = this.findStyleChild(root.getChild("styles", office), styleDesc.getElementNS(), styleDesc.getElementName(), family, name);
243
        if (res != null) {
238
        if (res != null) {
244
            return res;
239
            return res;
245
        }
240
        }
246
 
241
 
247
        // automatic-styles are only reachable from the same document
242
        // automatic-styles are only reachable from the same document
248
        if (referent == this.getDocument()) {
243
        if (referent == this.getDocument()) {
249
            res = this.findStyleChild(root.getChild("automatic-styles", office), styleDesc.getElementNS(), styleDesc.getElementName(), family, name);
244
            res = this.findStyleChild(root.getChild("automatic-styles", office), styleDesc.getElementNS(), styleDesc.getElementName(), family, name);
250
            if (res != null) {
245
            if (res != null) {
251
                return res;
246
                return res;
252
            }
247
            }
253
        }
248
        }
254
 
249
 
255
        final Element masterStyles = root.getChild("master-styles", office);
250
        final Element masterStyles = root.getChild("master-styles", office);
256
        if (masterStyles != null) {
251
        if (masterStyles != null) {
257
            res = this.findStyleChild(root.getChild("master-page", getVersion().getSTYLE()), styleDesc.getElementNS(), styleDesc.getElementName(), family, name);
252
            res = this.findStyleChild(root.getChild("master-page", getVersion().getSTYLE()), styleDesc.getElementNS(), styleDesc.getElementName(), family, name);
258
            if (res != null) {
253
            if (res != null) {
259
                return res;
254
                return res;
260
            }
255
            }
261
        }
256
        }
262
 
257
 
263
        return null;
258
        return null;
264
    }
259
    }
265
 
260
 
266
    public final Element getDefaultStyle(final StyleStyleDesc<?> styleDesc, final boolean create) {
261
    public final Element getDefaultStyle(final StyleStyleDesc<?> styleDesc, final boolean create) {
267
        final Element stylesElem = this.getChild("styles", create);
262
        final Element stylesElem = this.getChild("styles", create);
268
        final Element res = this.findStyleChild(stylesElem, styleDesc.getElementNS(), StyleStyleDesc.ELEMENT_DEFAULT_NAME, styleDesc.getFamily(), null);
263
        final Element res = this.findStyleChild(stylesElem, styleDesc.getElementNS(), StyleStyleDesc.ELEMENT_DEFAULT_NAME, styleDesc.getFamily(), null);
269
        if (res != null || !create) {
264
        if (res != null || !create) {
270
            return res;
265
            return res;
271
        } else {
266
        } else {
272
            final Element created = styleDesc.createDefaultElement();
267
            final Element created = styleDesc.createDefaultElement();
273
            // OK to add at the end, the relaxNG for office:styles is 'interleave'
268
            // OK to add at the end, the relaxNG for office:styles is 'interleave'
274
            stylesElem.addContent(created);
269
            stylesElem.addContent(created);
275
            return created;
270
            return created;
276
        }
271
        }
277
    }
272
    }
278
 
273
 
279
    private final Element findStyleChild(final Element styles, final Namespace elemNS, final String elemName, final String family, final String name) {
274
    private final Element findStyleChild(final Element styles, final Namespace elemNS, final String elemName, final String family, final String name) {
280
        if (styles == null)
275
        if (styles == null)
281
            return null;
276
            return null;
282
 
277
 
283
        final Namespace styleNS = getVersion().getSTYLE();
278
        final Namespace styleNS = getVersion().getSTYLE();
284
        // from JDOM : traversal through the List is best done with a Iterator
279
        // from JDOM : traversal through the List is best done with a Iterator
285
        for (final Object o : styles.getChildren(elemName, elemNS)) {
280
        for (final Object o : styles.getChildren(elemName, elemNS)) {
286
            final Element styleElem = (Element) o;
281
            final Element styleElem = (Element) o;
287
            // name first since it is more specific (and often includes family, eg "co2")
282
            // name first since it is more specific (and often includes family, eg "co2")
288
            if ((name == null || name.equals(styleElem.getAttributeValue("name", styleNS))) && (family == null || family.equals(StyleStyleDesc.getFamily(styleElem)))) {
283
            if ((name == null || name.equals(styleElem.getAttributeValue("name", styleNS))) && (family == null || family.equals(StyleStyleDesc.getFamily(styleElem)))) {
289
                return styleElem;
284
                return styleElem;
290
            }
285
            }
291
        }
286
        }
292
        return null;
287
        return null;
293
    }
288
    }
294
 
289
 
295
    /**
290
    /**
296
     * Find an unused style name in this document.
291
     * Find an unused style name in this document.
297
     * 
292
     * 
298
     * @param desc the description of the style.
293
     * @param desc the description of the style.
299
     * @param baseName the base name, e.g. "myColStyle".
294
     * @param baseName the base name, e.g. "myColStyle".
300
     * @return an unused name, e.g. "myColStyle12".
295
     * @return an unused name, e.g. "myColStyle12".
301
     * @see Style#getStyleDesc(Class, XMLVersion)
296
     * @see Style#getStyleDesc(Class, XMLVersion)
302
     */
297
     */
303
    public final String findUnusedName(final StyleDesc<?> desc, final String baseName) {
298
    public final String findUnusedName(final StyleDesc<?> desc, final String baseName) {
304
        final Integer lastI = this.styleNamesLast.get(baseName);
299
        final Integer lastI = this.styleNamesLast.get(baseName);
305
        final int offset = lastI == null ? 0 : lastI.intValue();
300
        final int offset = lastI == null ? 0 : lastI.intValue();
306
        int iterationCount = 0;
301
        int iterationCount = 0;
307
        int i = offset;
302
        int i = offset;
308
        String res = null;
303
        String res = null;
309
        while (res == null) {
304
        while (res == null) {
310
            final String name = baseName + i;
305
            final String name = baseName + i;
311
            final Element elem = this.getStyle(desc, name);
306
            final Element elem = this.getStyle(desc, name);
312
            iterationCount++;
307
            iterationCount++;
313
            // don't increment i if we succeed since we don't know if the found name will indeed be
308
            // don't increment i if we succeed since we don't know if the found name will indeed be
314
            // used
309
            // used
315
            if (elem == null) {
310
            if (elem == null) {
316
                res = name;
311
                res = name;
317
            } else {
312
            } else {
318
                i++;
313
                i++;
319
                // warn early before it takes too long
314
                // warn early before it takes too long
320
                if (iterationCount == ITERATIONS_WARNING_COUNT) {
315
                if (iterationCount == ITERATIONS_WARNING_COUNT) {
321
                    Log.get().warning("After " + iterationCount + " iterations, no unused name found for " + baseName + " (" + desc + ")");
316
                    Log.get().warning("After " + iterationCount + " iterations, no unused name found for " + baseName + " (" + desc + ")");
322
                }
317
                }
323
            }
318
            }
324
        }
319
        }
325
        this.styleNamesLast.put(baseName, i);
320
        this.styleNamesLast.put(baseName, i);
326
        if (iterationCount >= ITERATIONS_WARNING_COUNT) {
321
        if (iterationCount >= ITERATIONS_WARNING_COUNT) {
327
            // MAYBE trigger an optimize pass that merges equal styles.
322
            // MAYBE trigger an optimize pass that merges equal styles.
328
            Log.get().warning(iterationCount + " iterations were needed to find an unused name for " + baseName + " (" + desc + ")");
323
            Log.get().warning(iterationCount + " iterations were needed to find an unused name for " + baseName + " (" + desc + ")");
329
        }
324
        }
330
        return res;
325
        return res;
331
    }
326
    }
332
 
327
 
333
    // Useful if many styles are removed (to avoid "ce12345")
328
    // Useful if many styles are removed (to avoid "ce12345")
334
    final void clearStyleNameCache() {
329
    final void clearStyleNameCache() {
335
        this.styleNamesLast.clear();
330
        this.styleNamesLast.clear();
336
    }
331
    }
337
 
332
 
338
    public final void addAutoStyle(final Element styleElem) {
333
    public final void addAutoStyle(final Element styleElem) {
339
        this.getChild("automatic-styles", true).addContent(styleElem);
334
        this.getChild("automatic-styles", true).addContent(styleElem);
340
    }
335
    }
341
 
336
 
342
    public String asString() {
337
    public String asString() {
343
        return JDOMUtils.output(this.content);
338
        return JDOMUtils.output(this.content);
344
    }
339
    }
345
 
340
 
346
    protected static interface ElementTransformer {
341
    protected static interface ElementTransformer {
347
        Element transform(Element elem) throws JDOMException;
342
        Element transform(Element elem) throws JDOMException;
348
    }
343
    }
349
 
344
 
350
    protected static final ElementTransformer NOP_ElementTransformer = new ElementTransformer() {
345
    protected static final ElementTransformer NOP_ElementTransformer = new ElementTransformer() {
351
        public Element transform(Element elem) {
346
        public Element transform(Element elem) {
352
            return elem;
347
            return elem;
353
        }
348
        }
354
    };
349
    };
355
 
350
 
356
    protected void mergeAll(ODXMLDocument other, String path) throws JDOMException {
351
    protected void mergeAll(ODXMLDocument other, String path) throws JDOMException {
357
        this.mergeAll(other, path, null);
352
        this.mergeAll(other, path, null);
358
    }
353
    }
359
 
354
 
360
    /**
355
    /**
361
     * Fusionne l'élément spécifié par topElem. Applique addTransf avant l'ajout. Attention seuls
356
     * Fusionne l'élément spécifié par topElem. Applique addTransf avant l'ajout. Attention seuls
362
     * les élément (et non les commentaires, text, etc.) de <code>other</code> sont ajoutés.
357
     * les élément (et non les commentaires, text, etc.) de <code>other</code> sont ajoutés.
363
     * 
358
     * 
364
     * @param other le document à fusionner.
359
     * @param other le document à fusionner.
365
     * @param path le chemon de l'élément à fusionner, eg "./office:body".
360
     * @param path le chemon de l'élément à fusionner, eg "./office:body".
366
     * @param addTransf la transformation à appliquer avant d'ajouter ou <code>null</code>.
361
     * @param addTransf la transformation à appliquer avant d'ajouter ou <code>null</code>.
367
     * @throws JDOMException
362
     * @throws JDOMException
368
     */
363
     */
369
    protected void mergeAll(ODXMLDocument other, String path, ElementTransformer addTransf) throws JDOMException {
364
    protected void mergeAll(ODXMLDocument other, String path, ElementTransformer addTransf) throws JDOMException {
370
        this.add(path, -1, other, path, addTransf);
365
        this.add(path, -1, other, path, addTransf);
371
    }
366
    }
372
 
367
 
373
    /**
368
    /**
374
     * Add the part pointed by <code>rpath</code> of other in this document like child number
369
     * Add the part pointed by <code>rpath</code> of other in this document like child number
375
     * <code>lindex</code> of the part pointed by <code>lpath</code>.
370
     * <code>lindex</code> of the part pointed by <code>lpath</code>.
376
     * 
371
     * 
377
     * @param lpath local xpath.
372
     * @param lpath local xpath.
378
     * @param lindex local index beneath lpath, < 0 meaning the end.
373
     * @param lindex local index beneath lpath, < 0 meaning the end.
379
     * @param other the document to add.
374
     * @param other the document to add.
380
     * @param rpath the remote xpath, note: the content of that element will be added NOT the
375
     * @param rpath the remote xpath, note: the content of that element will be added NOT the
381
     *        element itself.
376
     *        element itself.
382
     * @param addTransf the children of rpath will be transformed, can be <code>null</code>.
377
     * @param addTransf the children of rpath will be transformed, can be <code>null</code>.
383
     * @throws JDOMException if an error occur.
378
     * @throws JDOMException if an error occur.
384
     */
379
     */
385
    protected void add(final String lpath, int lindex, ODXMLDocument other, String rpath, ElementTransformer addTransf) throws JDOMException {
380
    protected void add(final String lpath, int lindex, ODXMLDocument other, String rpath, ElementTransformer addTransf) throws JDOMException {
386
        this.add(new IFactory<Element>() {
381
        this.add(new IFactory<Element>() {
387
            public Element createChecked() {
382
            public Element createChecked() {
388
                try {
383
                try {
389
                    return getDescendant(lpath, true);
384
                    return getDescendant(lpath, true);
390
                } catch (JDOMException e) {
385
                } catch (JDOMException e) {
391
                    throw new IllegalStateException("error", e);
386
                    throw new IllegalStateException("error", e);
392
                }
387
                }
393
            }
388
            }
394
        }, lindex, other, rpath, addTransf);
389
        }, lindex, other, rpath, addTransf);
395
    }
390
    }
396
 
391
 
397
    /**
392
    /**
398
     * Add the part pointed by <code>rpath</code> of other in this document like child number
393
     * Add the part pointed by <code>rpath</code> of other in this document like child number
399
     * <code>lindex</code> of <code>elem</code>.
394
     * <code>lindex</code> of <code>elem</code>.
400
     * 
395
     * 
401
     * @param elem local element, if <code>null</code> add to rpath see
396
     * @param elem local element, if <code>null</code> add to rpath see
402
     *        {@link #mergeAll(ODXMLDocument, String, org.openconcerto.openoffice.ODXMLDocument.ElementTransformer)}
397
     *        {@link #mergeAll(ODXMLDocument, String, org.openconcerto.openoffice.ODXMLDocument.ElementTransformer)}
403
     *        .
398
     *        .
404
     * @param lindex local index beneath lpath, < 0 meaning the end, ignored if elem is
399
     * @param lindex local index beneath lpath, < 0 meaning the end, ignored if elem is
405
     *        <code>null</code>.
400
     *        <code>null</code>.
406
     * @param other the document to add.
401
     * @param other the document to add.
407
     * @param rpath the remote xpath, note: the content of that element will be added NOT the
402
     * @param rpath the remote xpath, note: the content of that element will be added NOT the
408
     *        element itself.
403
     *        element itself.
409
     * @param addTransf the children of rpath will be transformed, can be <code>null</code>.
404
     * @param addTransf the children of rpath will be transformed, can be <code>null</code>.
410
     * @throws JDOMException if an error occur.
405
     * @throws JDOMException if an error occur.
411
     */
406
     */
412
    protected void add(final Element elem, int lindex, ODXMLDocument other, String rpath, ElementTransformer addTransf) throws JDOMException {
407
    protected void add(final Element elem, int lindex, ODXMLDocument other, String rpath, ElementTransformer addTransf) throws JDOMException {
413
        if (elem == null) {
408
        if (elem == null) {
414
            this.mergeAll(other, rpath, addTransf);
409
            this.mergeAll(other, rpath, addTransf);
415
        } else {
410
        } else {
416
            if (!this.getDocument().getRootElement().isAncestor(elem))
411
            if (!this.getDocument().getRootElement().isAncestor(elem))
417
                throw new IllegalArgumentException(elem + " not part of " + this);
412
                throw new IllegalArgumentException(elem + " not part of " + this);
418
            this.add(new IFactory<Element>() {
413
            this.add(new IFactory<Element>() {
419
                public Element createChecked() {
414
                public Element createChecked() {
420
                    return elem;
415
                    return elem;
421
                }
416
                }
422
            }, lindex, other, rpath, addTransf);
417
            }, lindex, other, rpath, addTransf);
423
        }
418
        }
424
    }
419
    }
425
 
420
 
426
    protected final void add(IFactory<Element> elemF, int lindex, ODXMLDocument other, String rpath, ElementTransformer addTransf) throws JDOMException {
421
    protected final void add(IFactory<Element> elemF, int lindex, ODXMLDocument other, String rpath, ElementTransformer addTransf) throws JDOMException {
427
        final Element toAdd = other.getDescendant(rpath);
422
        final Element toAdd = other.getDescendant(rpath);
428
        // si on a qqchose à ajouter
423
        // si on a qqchose à ajouter
429
        if (toAdd != null) {
424
        if (toAdd != null) {
430
            @SuppressWarnings("unchecked")
425
            @SuppressWarnings("unchecked")
431
            final List<Content> cloned = toAdd.cloneContent();
426
            final List<Content> cloned = toAdd.cloneContent();
432
            final List<Content> listToAdd;
427
            final List<Content> listToAdd;
433
            if (addTransf == null) {
428
            if (addTransf == null) {
434
                listToAdd = cloned;
429
                listToAdd = cloned;
435
            } else {
430
            } else {
436
                listToAdd = new ArrayList<Content>(cloned.size());
431
                listToAdd = new ArrayList<Content>(cloned.size());
437
                final Iterator<Content> iter = cloned.iterator();
432
                final Iterator<Content> iter = cloned.iterator();
438
                while (iter.hasNext()) {
433
                while (iter.hasNext()) {
439
                    final Content c = iter.next();
434
                    final Content c = iter.next();
440
                    if (c instanceof Element) {
435
                    if (c instanceof Element) {
441
                        final Element transformedElem = addTransf.transform((Element) c);
436
                        final Element transformedElem = addTransf.transform((Element) c);
442
                        if (transformedElem != null)
437
                        if (transformedElem != null)
443
                            listToAdd.add(transformedElem);
438
                            listToAdd.add(transformedElem);
444
                    } else {
439
                    } else {
445
                        // keep non element as when addTransf is null
440
                        // keep non element as when addTransf is null
446
                        // perhaps use a Transformer<Content> to allow to remove or modify
441
                        // perhaps use a Transformer<Content> to allow to remove or modify
447
                        listToAdd.add(c);
442
                        listToAdd.add(c);
448
                    }
443
                    }
449
                }
444
                }
450
            }
445
            }
451
            // on crée si besoin le "récepteur"
446
            // on crée si besoin le "récepteur"
452
            final Element thisElem = elemF.createChecked();
447
            final Element thisElem = elemF.createChecked();
453
            if (lindex < 0)
448
            if (lindex < 0)
454
                thisElem.addContent(listToAdd);
449
                thisElem.addContent(listToAdd);
455
            else
450
            else
456
                thisElem.addContent(lindex, listToAdd);
451
                thisElem.addContent(lindex, listToAdd);
457
        }
452
        }
458
    }
453
    }
459
 
454
 
460
    protected final void addIfNotPresent(ODXMLDocument doc, String path) throws JDOMException {
455
    protected final void addIfNotPresent(ODXMLDocument doc, String path) throws JDOMException {
461
        this.addIfNotPresent(doc, path, -1);
456
        this.addIfNotPresent(doc, path, -1);
462
    }
457
    }
463
 
458
 
464
    /**
459
    /**
465
     * Adds an element from doc to this, if it's not already there.
460
     * Adds an element from doc to this, if it's not already there.
466
     * 
461
     * 
467
     * @param doc the other document.
462
     * @param doc the other document.
468
     * @param path an XPath denoting an element, and relative to the root element, eg
463
     * @param path an XPath denoting an element, and relative to the root element, eg
469
     *        ./office:settings.
464
     *        ./office:settings.
470
     * @param index the index where to add the element, -1 means the end.
465
     * @param index the index where to add the element, -1 means the end.
471
     * @throws JDOMException if a problem occurs with path.
466
     * @throws JDOMException if a problem occurs with path.
472
     */
467
     */
473
    protected final void addIfNotPresent(ODXMLDocument doc, String path, int index) throws JDOMException {
468
    protected final void addIfNotPresent(ODXMLDocument doc, String path, int index) throws JDOMException {
474
        final Element myElem = this.getDescendant(path);
469
        final Element myElem = this.getDescendant(path);
475
        if (myElem == null) {
470
        if (myElem == null) {
476
            final Element otherElem = doc.getDescendant(path);
471
            final Element otherElem = doc.getDescendant(path);
477
            if (otherElem != null) {
472
            if (otherElem != null) {
478
                final Element myParent = this.getDescendant(XPathUtils.parentOf(path));
473
                final Element myParent = this.getDescendant(XPathUtils.parentOf(path));
479
                if (index == -1)
474
                if (index == -1)
480
                    myParent.addContent((Element) otherElem.clone());
475
                    myParent.addContent((Element) otherElem.clone());
481
                else
476
                else
482
                    myParent.addContent(index, (Element) otherElem.clone());
477
                    myParent.addContent(index, (Element) otherElem.clone());
483
            }
478
            }
484
        }
479
        }
485
    }
480
    }
486
 
481
 
487
}
482
}