OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
20 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.style.data;
15
 
16
import org.openconcerto.openoffice.Log;
25 ilm 17
import org.openconcerto.openoffice.ODEpoch;
20 ilm 18
import org.openconcerto.openoffice.ODPackage;
25 ilm 19
import org.openconcerto.openoffice.ODValueType;
20 ilm 20
import org.openconcerto.openoffice.Style;
21
import org.openconcerto.openoffice.StyleDesc;
22
import org.openconcerto.openoffice.StyleProperties;
23
import org.openconcerto.openoffice.XMLVersion;
24
import org.openconcerto.openoffice.spreadsheet.CellStyle;
25
import org.openconcerto.openoffice.text.TextStyle.StyleTextProperties;
26
import org.openconcerto.utils.NumberUtils;
27
 
28
import java.math.RoundingMode;
29
import java.text.DecimalFormat;
174 ilm 30
import java.text.DecimalFormatSymbols;
20 ilm 31
import java.util.Arrays;
32
import java.util.Collections;
73 ilm 33
import java.util.HashSet;
20 ilm 34
import java.util.List;
174 ilm 35
import java.util.Locale;
20 ilm 36
import java.util.Map.Entry;
73 ilm 37
import java.util.Set;
20 ilm 38
import java.util.SortedMap;
39
import java.util.TreeMap;
40
import java.util.regex.Matcher;
41
import java.util.regex.Pattern;
42
 
43
import org.jdom.Attribute;
44
import org.jdom.Element;
45
import org.jdom.Namespace;
46
 
47
// from section 16.27 in v1.2-cs01-part1
48
public abstract class DataStyle extends Style {
49
    private static final int DEFAULT_GROUPING_SIZE = new DecimalFormat().getGroupingSize();
174 ilm 50
    // 15 as of LibreOffice 6, was 10 earlier
51
    /**
52
     * The default number of decimal digits if neither defined in the style nor in default-style.
53
     */
54
    public static final int DEFAULT_DECIMAL_PLACES = Integer.parseInt(System.getProperty("openDocument.defaultDecimalPlaces", "15"));
20 ilm 55
    private static final Pattern QUOTE_PATRN = Pattern.compile("'", Pattern.LITERAL);
56
    private static final Pattern EXP_PATTERN = Pattern.compile("E(\\d+)$");
57
 
58
    public static int getDecimalPlaces(final CellStyle defaultStyle) {
73 ilm 59
        if (defaultStyle != null) {
60
            return defaultStyle.getTableCellProperties(null).getDecimalPlaces();
61
        } else {
62
            return DEFAULT_DECIMAL_PLACES;
63
        }
20 ilm 64
    }
65
 
66
    public static void addStringLiteral(final StringBuilder formatSB, final String s) {
67
        formatSB.append('\'');
68
        formatSB.append(QUOTE_PATRN.matcher(s).replaceAll("''"));
69
        formatSB.append('\'');
70
    }
71
 
73 ilm 72
    public static final Set<Class<? extends DataStyle>> DATA_STYLES;
65 ilm 73
    private static final DataStyleDesc<?>[] DATA_STYLES_DESCS = new DataStyleDesc<?>[] { NumberStyle.DESC, PercentStyle.DESC, TextStyle.DESC, CurrencyStyle.DESC, DateStyle.DESC, TimeStyle.DESC,
20 ilm 74
            BooleanStyle.DESC };
73 ilm 75
    static {
76
        final Set<Class<? extends DataStyle>> l = new HashSet<Class<? extends DataStyle>>(DATA_STYLES_DESCS.length);
77
        l.add(NumberStyle.class);
78
        l.add(PercentStyle.class);
79
        l.add(TextStyle.class);
80
        l.add(CurrencyStyle.class);
81
        l.add(DateStyle.class);
82
        l.add(TimeStyle.class);
83
        l.add(BooleanStyle.class);
84
        DATA_STYLES = Collections.unmodifiableSet(l);
85
        assert DATA_STYLES_DESCS.length == DATA_STYLES.size() : "Discrepancy between classes and descs";
86
    }
20 ilm 87
 
88
    public static abstract class DataStyleDesc<S extends DataStyle> extends StyleDesc<S> {
89
 
90
        protected DataStyleDesc(Class<S> clazz, XMLVersion version, String elemName, String baseName) {
91
            super(clazz, version, elemName, baseName);
92
            this.setElementNS(getVersion().getNS("number"));
93
            // from 19.469 in v1.2-cs01-part1
174 ilm 94
            this.getRefElementsMap().addAll("style:data-style-name",
20 ilm 95
                    Arrays.asList("presentation:date-time-decl", "style:style", "text:creation-date", "text:creation-time", "text:database-display", "text:date", "text:editing-duration",
96
                            "text:expression", "text:meta-field", "text:modification-date", "text:modification-time", "text:print-date", "text:print-time", "text:table-formula", "text:time",
97
                            "text:user-defined", "text:user-field-get", "text:user-field-input", "text:variable-get", "text:variable-input", "text:variable-set"));
80 ilm 98
            this.getRefElementsMap().add("style:apply-style-name", "style:map");
20 ilm 99
        }
100
    }
101
 
65 ilm 102
    static public void registerDesc() {
103
        for (final StyleDesc<?> d : DATA_STYLES_DESCS)
104
            Style.registerAllVersions(d);
105
    }
106
 
107
    static public <S extends DataStyle> DataStyleDesc<S> getDesc(final Class<S> clazz, final XMLVersion version) {
108
        return (DataStyleDesc<S>) Style.getStyleDesc(clazz, version);
109
    }
110
 
25 ilm 111
    private final ODValueType type;
20 ilm 112
    private StyleTextProperties textProps;
113
 
25 ilm 114
    protected DataStyle(final ODPackage pkg, Element elem, final ODValueType type) {
20 ilm 115
        super(pkg, elem);
116
        this.type = type;
117
    }
118
 
25 ilm 119
    public final ODValueType getDataType() {
20 ilm 120
        return this.type;
121
    }
122
 
25 ilm 123
    public final ODEpoch getEpoch() {
124
        return this.getPackage().getODDocument().getEpoch();
125
    }
126
 
127
    /**
128
     * Convert the passed object to something that {@link #format(Object, CellStyle, boolean)} can
129
     * accept.
130
     *
131
     * @param o the object to convert.
132
     * @return an object that can be formatted, <code>null</code> if <code>o</code> cannot be
133
     *         converted.
134
     * @throws NullPointerException if <code>o</code> is <code>null</code>.
135
     * @see #canFormat(Class)
136
     */
137
    public final Object convert(final Object o) throws NullPointerException {
138
        if (o == null)
139
            throw new NullPointerException();
140
 
141
        final Object res;
142
        if (this.canFormat(o.getClass()))
143
            res = o;
144
        else
145
            res = this.convertNonNull(o);
146
        assert res == null || this.canFormat(res.getClass());
147
        return res;
148
    }
149
 
150
    // o is not null and canFormat(o.getClass()) is false
151
    // return null if o cannot be converted
152
    protected abstract Object convertNonNull(Object o);
153
 
154
    /**
155
     * Whether instances of the passed class can be {@link #format(Object, CellStyle, boolean)
156
     * formatted}.
157
     *
158
     * @param toFormat the class.
159
     * @return <code>true</code> if instances of <code>toFormat</code> can be formatted.
160
     */
20 ilm 161
    public final boolean canFormat(Class<?> toFormat) {
25 ilm 162
        return this.getDataType().canFormat(toFormat);
20 ilm 163
    }
164
 
165
    public final String getTitle() {
166
        return this.getElement().getAttributeValue("title", getElement().getNamespace());
167
    }
168
 
169
    public final StyleTextProperties getTextProperties() {
170
        if (this.textProps == null)
171
            this.textProps = new StyleTextProperties(this);
172
        return this.textProps;
173
    }
174
 
175
    public abstract String format(final Object o, final CellStyle defaultStyle, boolean lenient) throws UnsupportedOperationException;
176
 
61 ilm 177
    static protected final void reportError(String msg, boolean lenient) throws UnsupportedOperationException {
20 ilm 178
        if (lenient)
179
            Log.get().warning(msg);
180
        else
181
            throw new UnsupportedOperationException(msg);
182
    }
183
 
174 ilm 184
    public final Locale getLocale() {
185
        return this.getLocale(this.getElement());
186
    }
187
 
188
    protected final Locale getLocale(final Element elem) {
189
        final Locale res = DateStyle.getElementLocale(elem);
190
        return res != null ? res : this.getPackage().getLocale();
191
    }
192
 
20 ilm 193
    protected final String formatNumberOrScientificNumber(final Element elem, final Number n, CellStyle defaultStyle) {
194
        return this.formatNumberOrScientificNumber(elem, n, 1, defaultStyle);
195
    }
196
 
197
    protected final String formatNumberOrScientificNumber(final Element elem, final Number n, final int multiplier, CellStyle defaultStyle) {
198
        final Namespace numberNS = this.getElement().getNamespace();
199
        final StringBuilder numberSB = new StringBuilder();
200
 
201
        final List<?> embeddedTexts = elem.getChildren("embedded-text", numberNS);
202
        final SortedMap<Integer, String> embeddedTextByPosition = new TreeMap<Integer, String>(Collections.reverseOrder());
203
        for (final Object o : embeddedTexts) {
204
            final Element embeddedText = (Element) o;
205
            embeddedTextByPosition.put(Integer.valueOf(embeddedText.getAttributeValue("position", numberNS)), embeddedText.getText());
206
        }
207
 
208
        final Attribute factorAttr = elem.getAttribute("display-factor", numberNS);
209
        final double factor = (factorAttr != null ? Double.valueOf(factorAttr.getValue()) : 1) / multiplier;
210
 
211
        // default value from 19.348
212
        final boolean grouping = StyleProperties.parseBoolean(elem.getAttributeValue("grouping", numberNS), false);
213
 
214
        final String minIntDigitsAttr = elem.getAttributeValue("min-integer-digits", numberNS);
215
        final int minIntDig = minIntDigitsAttr == null ? 0 : Integer.parseInt(minIntDigitsAttr);
216
        if (minIntDig == 0) {
217
            numberSB.append('#');
218
        } else {
219
            for (int i = 0; i < minIntDig; i++)
220
                numberSB.append('0');
221
        }
222
 
223
        // e.g. if it's "--", 12,3 is displayed "12,3" and 12 is displayed "12,--"
224
        final String decReplacement = elem.getAttributeValue("decimal-replacement", numberNS);
225
        final boolean decSeparatorAlwaysShown;
226
        if (decReplacement != null && !NumberUtils.hasFractionalPart(n)) {
227
            decSeparatorAlwaysShown = true;
228
            numberSB.append('.');
229
            // escape quote in replacement
230
            addStringLiteral(numberSB, decReplacement);
231
        } else {
232
            decSeparatorAlwaysShown = false;
233
            // see 19.343.2
234
            final Attribute decPlacesAttr = elem.getAttribute("decimal-places", numberNS);
235
            final int decPlaces;
73 ilm 236
            final char decChar;
237
            if (decPlacesAttr != null) {
238
                decChar = '0';
20 ilm 239
                decPlaces = Integer.parseInt(decPlacesAttr.getValue());
73 ilm 240
            } else {
241
                // default style specifies the maximum
242
                decChar = '#';
20 ilm 243
                decPlaces = getDecimalPlaces(defaultStyle);
73 ilm 244
            }
20 ilm 245
 
246
            if (decPlaces > 0) {
247
                numberSB.append('.');
248
                for (int i = 0; i < decPlaces; i++)
73 ilm 249
                    numberSB.append(decChar);
20 ilm 250
            }
251
        }
252
 
253
        final Attribute minExpAttr = elem.getAttribute("min-exponent-digits", numberNS);
254
        if (minExpAttr != null) {
255
            numberSB.append('E');
256
            for (int i = 0; i < Integer.parseInt(minExpAttr.getValue()); i++)
257
                numberSB.append('0');
258
        }
259
 
174 ilm 260
        final DecimalFormatSymbols symbols = new DecimalFormatSymbols(this.getLocale());
261
 
262
        final DecimalFormat decFormat = new DecimalFormat(numberSB.toString(), symbols);
20 ilm 263
        // Java always use HALF_EVEN
264
        decFormat.setRoundingMode(RoundingMode.HALF_UP);
265
        decFormat.setGroupingUsed(grouping);
266
        // needed since the default size is overwritten by the pattern
267
        decFormat.setGroupingSize(DEFAULT_GROUPING_SIZE);
268
        decFormat.setDecimalSeparatorAlwaysShown(decSeparatorAlwaysShown);
269
        String res = decFormat.format(NumberUtils.divide(n, factor));
270
        // java only puts the minus sign, OO also puts the plus sign
271
        if (minExpAttr != null) {
272
            final Matcher m = EXP_PATTERN.matcher(res);
273
            if (m.find())
274
                res = res.substring(0, m.start()) + "E+" + m.group(1);
275
        }
276
        if (embeddedTextByPosition.size() > 0) {
277
            final int intDigits = Math.max(minIntDig, NumberUtils.intDigits(n));
278
            // each time we insert text the decimal point moves
279
            int offset = 0;
280
            // sorted descending to avoid overwriting
281
            for (Entry<Integer, String> e : embeddedTextByPosition.entrySet()) {
282
                final String embeddedText = e.getValue();
283
                // the text will be before this index
284
                final int index = Math.max(0, offset + intDigits - e.getKey().intValue());
285
                res = res.substring(0, index) + embeddedText + res.substring(index);
286
                offset += embeddedText.length();
287
            }
288
        }
289
        return res;
290
    }
291
}