OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 20 | Rev 65 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
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
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.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.openoffice;
15
 
16
import static java.util.Collections.singleton;
17
import org.openconcerto.openoffice.ODPackage.RootElement;
18
import org.openconcerto.openoffice.spreadsheet.CellStyle;
19
import org.openconcerto.openoffice.spreadsheet.ColumnStyle;
20
import org.openconcerto.openoffice.spreadsheet.RowStyle;
21
import org.openconcerto.openoffice.spreadsheet.TableStyle;
22
import org.openconcerto.openoffice.style.PageLayoutStyle;
20 ilm 23
import org.openconcerto.openoffice.style.data.DataStyle;
17 ilm 24
import org.openconcerto.openoffice.text.ParagraphStyle;
25
import org.openconcerto.openoffice.text.TextStyle;
26
import org.openconcerto.utils.CollectionMap;
27
import org.openconcerto.utils.Tuple2;
28
import org.openconcerto.xml.JDOMUtils;
29
 
30
import java.util.Collection;
31
import java.util.HashMap;
32
import java.util.List;
33
import java.util.Map;
34
import java.util.Map.Entry;
35
 
36
import org.jdom.Attribute;
37
import org.jdom.Document;
38
import org.jdom.Element;
39
import org.jdom.Namespace;
40
 
41
/**
42
 * A style, see section 16 of v1.2-part1. Maintains a map of family to classes.
43
 *
44
 * @author Sylvain
45
 */
46
public class Style extends ODNode {
47
 
48
    private static final Map<XMLVersion, Map<String, StyleDesc<?>>> family2Desc;
49
    private static final Map<XMLVersion, Map<String, StyleDesc<?>>> elemName2Desc;
50
    private static final Map<XMLVersion, Map<Class<? extends Style>, StyleDesc<?>>> class2Desc;
51
    private static boolean descsLoaded = false;
20 ilm 52
    // need a CollectionMap e.g. [ "style:style", "style:data-style-name" ] ->
53
    // DataStyle.DATA_STYLES_DESCS
54
    private static final Map<XMLVersion, CollectionMap<Tuple2<String, String>, StyleDesc<?>>> attribute2Desc;
17 ilm 55
    static {
56
        final int versionsCount = XMLVersion.values().length;
57
        family2Desc = new HashMap<XMLVersion, Map<String, StyleDesc<?>>>(versionsCount);
58
        elemName2Desc = new HashMap<XMLVersion, Map<String, StyleDesc<?>>>(versionsCount);
59
        class2Desc = new HashMap<XMLVersion, Map<Class<? extends Style>, StyleDesc<?>>>(versionsCount);
20 ilm 60
        attribute2Desc = new HashMap<XMLVersion, CollectionMap<Tuple2<String, String>, StyleDesc<?>>>(versionsCount);
17 ilm 61
        for (final XMLVersion v : XMLVersion.values()) {
62
            family2Desc.put(v, new HashMap<String, StyleDesc<?>>());
63
            elemName2Desc.put(v, new HashMap<String, StyleDesc<?>>());
64
            class2Desc.put(v, new HashMap<Class<? extends Style>, StyleDesc<?>>());
20 ilm 65
            attribute2Desc.put(v, new CollectionMap<Tuple2<String, String>, StyleDesc<?>>(128));
17 ilm 66
        }
67
    }
68
 
69
    // lazy initialization to avoid circular dependency (i.e. ClassLoader loads PStyle.DESC which
70
    // loads StyleStyle which needs PStyle.DESC)
71
    private static void loadDescs() {
72
        if (!descsLoaded) {
73
            registerAllVersions(CellStyle.DESC);
74
            registerAllVersions(RowStyle.DESC);
75
            registerAllVersions(ColumnStyle.DESC);
76
            registerAllVersions(TableStyle.DESC);
77
            registerAllVersions(TextStyle.DESC);
78
            registerAllVersions(ParagraphStyle.DESC);
20 ilm 79
            for (final StyleDesc<?> d : DataStyle.DATA_STYLES_DESCS)
80
                registerAllVersions(d);
17 ilm 81
            register(GraphicStyle.DESC);
82
            register(GraphicStyle.DESC_OO);
83
            register(PageLayoutStyle.DESC);
84
            register(PageLayoutStyle.DESC_OO);
85
            descsLoaded = true;
86
        }
87
    }
88
 
89
    // until now styles have remained constant through versions
90
    private static void registerAllVersions(StyleDesc<? extends Style> desc) {
91
        for (final XMLVersion v : XMLVersion.values()) {
92
            if (v == desc.getVersion())
93
                register(desc);
94
            else
95
                register(StyleDesc.copy(desc, v));
96
        }
97
    }
98
 
99
    public static void register(StyleDesc<?> desc) {
100
        if (desc instanceof StyleStyleDesc<?>) {
101
            final StyleStyleDesc<?> styleStyleDesc = (StyleStyleDesc<?>) desc;
102
            if (family2Desc.get(desc.getVersion()).put(styleStyleDesc.getFamily(), styleStyleDesc) != null)
103
                throw new IllegalStateException(styleStyleDesc.getFamily() + " duplicate family");
104
        } else {
105
            if (elemName2Desc.get(desc.getVersion()).put(desc.getElementName(), desc) != null)
106
                throw new IllegalStateException(desc.getElementName() + " duplicate element name");
107
        }
25 ilm 108
        assert desc != null : "Will need containsKey() in getStyleDesc()";
17 ilm 109
        if (class2Desc.get(desc.getVersion()).put(desc.getStyleClass(), desc) != null)
110
            throw new IllegalStateException(desc.getStyleClass() + " duplicate");
111
    }
112
 
113
    /**
114
     * Get all registered {@link StyleDesc}.
115
     *
116
     * @param version the version.
117
     * @return all known descriptions.
118
     */
119
    private static Collection<StyleDesc<?>> getDesc(final XMLVersion version) {
120
        loadDescs();
121
        return class2Desc.get(version).values();
122
    }
123
 
124
    /**
125
     * Return a mapping from element/attribute to its {@link StyleDesc}.
126
     *
127
     * <pre>
128
     * [ element qualified name, attribute qualified name ] -> StyleDesc ; e.g. :
129
     * [ "text:p", "text:style-name" ] -> {@link ParagraphStyle#DESC}
130
     * [ "text:h", "text:style-name" ] -> {@link ParagraphStyle#DESC}
131
     * [ "text:span", "text:style-name" ] -> {@link TextStyle#DESC}
132
     * </pre>
133
     *
134
     * @param version the version.
135
     * @return the mapping from attribute to description.
136
     */
20 ilm 137
    private static CollectionMap<Tuple2<String, String>, StyleDesc<?>> getAttr2Desc(final XMLVersion version) {
138
        final CollectionMap<Tuple2<String, String>, StyleDesc<?>> map = attribute2Desc.get(version);
17 ilm 139
        if (map.isEmpty()) {
140
            for (final StyleDesc<?> desc : getDesc(version)) {
141
                fillMap(map, desc, desc.getRefElementsMap());
142
                fillMap(map, desc, desc.getMultiRefElementsMap());
143
            }
144
            assert !map.isEmpty();
145
        }
146
        return map;
147
    }
148
 
20 ilm 149
    private static void fillMap(final CollectionMap<Tuple2<String, String>, StyleDesc<?>> map, final StyleDesc<?> desc, final CollectionMap<String, String> elemsByAttrs) {
17 ilm 150
        for (final Entry<String, Collection<String>> e : elemsByAttrs.entrySet()) {
151
            for (final String elementName : e.getValue()) {
152
                final Tuple2<String, String> key = Tuple2.create(elementName, e.getKey());
20 ilm 153
                map.put(key, desc);
17 ilm 154
            }
155
        }
156
    }
157
 
158
    /**
159
     * Create the most specific instance for the passed element.
160
     *
161
     * @param pkg the package where the style is defined.
162
     * @param styleElem a style:style XML element.
163
     * @return the most specific instance, e.g. a new ColumnStyle.
164
     */
165
    public static Style warp(final ODPackage pkg, final Element styleElem) {
166
        loadDescs();
167
        final String name = styleElem.getName();
168
        if (name.equals(StyleStyleDesc.ELEMENT_NAME)) {
169
            final String family = StyleStyleDesc.getFamily(styleElem);
170
            final Map<String, StyleDesc<?>> map = family2Desc.get(pkg.getVersion());
171
            if (map.containsKey(family)) {
172
                final StyleDesc<?> styleClass = map.get(family);
173
                return styleClass.create(pkg, styleElem);
174
            } else
175
                return new StyleStyle(pkg, styleElem);
176
        } else {
177
            final Map<String, StyleDesc<?>> map = elemName2Desc.get(pkg.getVersion());
178
            if (map.containsKey(name)) {
179
                final StyleDesc<?> styleClass = map.get(name);
180
                return styleClass.create(pkg, styleElem);
181
            } else
182
                return new Style(pkg, styleElem);
183
        }
184
    }
185
 
186
    public static <S extends Style> S getStyle(final ODPackage pkg, final Class<S> clazz, final String name) {
187
        final StyleDesc<S> styleDesc = getStyleDesc(clazz, pkg.getVersion());
25 ilm 188
        return styleDesc.findStyleWithName(pkg, pkg.getContent().getDocument(), name);
17 ilm 189
    }
190
 
191
    /**
192
     * Return the style element referenced by the passed attribute.
193
     *
194
     * @param pkg the package where <code>styleAttr</code> is defined.
195
     * @param styleAttr any attribute in <code>pkg</code>, e.g.
196
     *        <code>style:page-layout-name="pm1"</code> of a style:master-page.
197
     * @return the referenced element if <code>styleAttr</code> is an attribute pointing to a style
198
     *         in the passed package, <code>null</code> otherwise, e.g.
199
     *         <code><style:page-layout style:name="pm1"></code>.
200
     */
201
    public static Element getReferencedStyleElement(final ODPackage pkg, final Attribute styleAttr) {
20 ilm 202
        final Style res = getReferencedStyle(pkg, styleAttr);
203
        if (res != null)
204
            return res.getElement();
205
        else
206
            return null;
207
    }
208
 
209
    public static Style getReferencedStyle(final ODPackage pkg, final Attribute styleAttr) {
210
        if (styleAttr == null)
211
            return null;
17 ilm 212
        assert styleAttr.getDocument() == pkg.getDocument(RootElement.CONTENT.getZipEntry()) || styleAttr.getDocument() == pkg.getDocument(RootElement.STYLES.getZipEntry()) : "attribute not defined in either the content or the styles of "
213
                + pkg;
20 ilm 214
        final Collection<StyleDesc<?>> descs = getAttr2Desc(pkg.getVersion()).getNonNull(Tuple2.create(styleAttr.getParent().getQualifiedName(), styleAttr.getQualifiedName()));
215
        for (final StyleDesc<?> desc : descs) {
216
            final Element res = pkg.getStyle(styleAttr.getDocument(), desc, styleAttr.getValue());
217
            if (res != null)
218
                return desc.create(pkg, res);
219
        }
220
        return null;
17 ilm 221
    }
222
 
223
    public static <S extends Style> StyleDesc<S> getStyleDesc(Class<S> clazz, final XMLVersion version) {
224
        return getStyleDesc(clazz, version, true);
225
    }
226
 
20 ilm 227
    public static <S extends StyleStyle> StyleStyleDesc<S> getStyleStyleDesc(Class<S> clazz, final XMLVersion version) {
228
        return (StyleStyleDesc<S>) getStyleDesc(clazz, version);
229
    }
230
 
17 ilm 231
    private static <S extends Style> StyleDesc<S> getStyleDesc(Class<S> clazz, final XMLVersion version, final boolean mustExist) {
232
        loadDescs();
233
        final Map<Class<? extends Style>, StyleDesc<?>> map = class2Desc.get(version);
25 ilm 234
        @SuppressWarnings("unchecked")
235
        final StyleDesc<S> res = (StyleDesc<S>) map.get(clazz);
236
        if (res == null && mustExist)
17 ilm 237
            throw new IllegalArgumentException("unregistered " + clazz + " for version " + version);
25 ilm 238
        return res;
17 ilm 239
    }
240
 
241
    protected static <S extends Style> StyleDesc<S> getNonNullStyleDesc(final Class<S> clazz, final XMLVersion version, final Element styleElem, final String styleName) {
242
        final StyleDesc<S> registered = getStyleDesc(clazz, version, false);
243
        if (registered != null)
244
            return registered;
245
        else
246
        // generic desc, use styleName as baseName
247
        if (clazz == StyleStyle.class) {
248
            @SuppressWarnings("unchecked")
249
            final StyleDesc<S> res = (StyleDesc<S>) new StyleStyleDesc<StyleStyle>(StyleStyle.class, version, StyleStyleDesc.getFamily(styleElem), styleName) {
250
                @Override
251
                public StyleStyle create(ODPackage pkg, Element e) {
252
                    return new StyleStyle(pkg, styleElem);
253
                }
254
            };
255
            return res;
256
        } else if (clazz == Style.class) {
257
            return new StyleDesc<S>(clazz, version, styleElem.getName(), styleName) {
258
                @Override
259
                public S create(ODPackage pkg, Element e) {
260
                    return clazz.cast(new Style(pkg, styleElem));
261
                }
262
            };
263
        } else
264
            throw new IllegalStateException("Unregistered class which is neither Style not StyleStyle :" + clazz);
265
    }
266
 
267
    private final StyleDesc<?> desc;
268
    private final ODPackage pkg;
269
    private final String name;
19 ilm 270
    private final XMLFormatVersion ns;
17 ilm 271
 
272
    public Style(final ODPackage pkg, final Element styleElem) {
273
        super(styleElem);
274
        this.pkg = pkg;
275
        this.name = this.getElement().getAttributeValue("name", this.getSTYLE());
19 ilm 276
        this.ns = this.pkg.getFormatVersion();
277
        this.desc = getNonNullStyleDesc(this.getClass(), this.ns.getXMLVersion(), styleElem, getName());
20 ilm 278
        checkElemName();
17 ilm 279
        // assert that styleElem is in pkg (and thus have the same version)
280
        assert this.pkg.getXMLFile(getElement().getDocument()) != null;
19 ilm 281
        assert this.pkg.getFormatVersion().equals(XMLFormatVersion.get(getElement().getDocument()));
17 ilm 282
    }
283
 
20 ilm 284
    protected void checkElemName() {
285
        if (!this.desc.getElementName().equals(this.getElement().getName()))
286
            throw new IllegalArgumentException("expected " + this.desc.getElementName() + " but got " + this.getElement().getName() + " for " + getElement());
287
    }
288
 
289
    protected final ODPackage getPackage() {
290
        return this.pkg;
291
    }
292
 
17 ilm 293
    protected final Namespace getSTYLE() {
294
        return this.getElement().getNamespace("style");
295
    }
296
 
297
    public final XMLVersion getNS() {
19 ilm 298
        return this.ns.getXMLVersion();
17 ilm 299
    }
300
 
301
    public final String getName() {
302
        return this.name;
303
    }
304
 
305
    protected StyleDesc<?> getDesc() {
306
        return this.desc;
307
    }
308
 
309
    public Element getFormattingProperties() {
310
        return this.getFormattingProperties(this.getName());
311
    }
312
 
313
    /**
314
     * Create if necessary and return the wanted properties.
315
     *
316
     * @param family type of properties, eg "text".
317
     * @return the matching properties, eg &lt;text-properties&gt;.
318
     */
319
    public final Element getFormattingProperties(final String family) {
20 ilm 320
        return getFormattingProperties(family, true);
321
    }
322
 
323
    public final Element getFormattingProperties(final String family, final boolean create) {
19 ilm 324
        final Element elem = this.ns.getXML().createFormattingProperties(family);
325
        Element res = this.getElement().getChild(elem.getName(), elem.getNamespace());
20 ilm 326
        if (res == null && create) {
19 ilm 327
            res = elem;
17 ilm 328
            this.getElement().addContent(res);
329
        }
330
        return res;
331
    }
332
 
333
    /**
334
     * Return the elements referring to this style in the passed document.
335
     *
336
     * @param doc an XML document.
337
     * @param wantSingle whether elements that affect only themselves should be included.
338
     * @param wantMulti whether elements that affect multiple others should be included.
339
     * @return the list of elements referring to this.
340
     */
341
    private final List<Element> getReferences(final Document doc, final boolean wantSingle, boolean wantMulti) {
342
        return this.desc.getReferences(doc, getName(), wantSingle, wantMulti);
343
    }
344
 
345
    /**
346
     * Return the elements referring to this style.
347
     *
348
     * @return the list of elements referring to this.
349
     */
350
    public final List<Element> getReferences() {
351
        return this.getReferences(true, true);
352
    }
353
 
354
    private final List<Element> getReferences(final boolean wantSingle, final boolean wantMulti) {
355
        final Document myDoc = this.getElement().getDocument();
356
        final Document content = this.pkg.getContent().getDocument();
357
        // my document can always refer to us
358
        final List<Element> res = this.getReferences(myDoc, wantSingle, wantMulti);
359
        // but only common styles can be referenced from the content
360
        if (myDoc != content && !this.isAutomatic())
361
            res.addAll(this.getReferences(content, wantSingle, wantMulti));
362
        return res;
363
    }
364
 
365
    private final boolean isAutomatic() {
366
        return this.getElement().getParentElement().getQualifiedName().equals("office:automatic-styles");
367
    }
368
 
369
    public final boolean isReferencedAtMostOnce() {
370
        // i.e. no multi-references and at most one single reference
371
        return this.getReferences(false, true).size() == 0 && this.getReferences(true, false).size() <= 1;
372
    }
373
 
374
    /**
375
     * Make a copy of this style and add it to its document.
376
     *
377
     * @return the new style with an unused name.
378
     */
379
    public final Style dup() {
380
        // don't use an ODXMLDocument attribute, search for our document in an ODPackage, that way
381
        // even if our element changes document (toSingle()) we will find the proper ODXMLDocument
382
        final ODXMLDocument xmlFile = this.pkg.getXMLFile(this.getElement().getDocument());
383
        final String unusedName = xmlFile.findUnusedName(this.desc, this.desc.getBaseName());
384
        final Element clone = (Element) this.getElement().clone();
20 ilm 385
        // needed if this is a default-style
386
        clone.setName(this.desc.getElementName());
17 ilm 387
        clone.setAttribute("name", unusedName, this.getSTYLE());
388
        JDOMUtils.insertAfter(this.getElement(), singleton(clone));
389
        return this.desc.create(this.pkg, clone);
390
    }
391
 
392
    @Override
393
    public boolean equals(Object obj) {
394
        if (!(obj instanceof Style))
395
            return false;
396
        final Style o = (Style) obj;
397
        return this.getName().equals(o.getName()) && this.getElement().getName().equals(o.getElement().getName()) && this.getElement().getNamespace().equals(o.getElement().getNamespace());
398
    }
399
 
400
    @Override
401
    public int hashCode() {
402
        return this.getName().hashCode() + this.getElement().getName().hashCode() + this.getElement().getNamespace().hashCode();
403
    }
404
}