OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 25 | Go to most recent revision | Details | 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;
17
import org.openconcerto.openoffice.ODPackage;
18
import org.openconcerto.openoffice.Style;
19
import org.openconcerto.openoffice.StyleDesc;
20
import org.openconcerto.openoffice.StyleProperties;
21
import org.openconcerto.openoffice.XMLVersion;
22
import org.openconcerto.openoffice.spreadsheet.CellStyle;
23
import org.openconcerto.openoffice.text.TextStyle.StyleTextProperties;
24
import org.openconcerto.utils.NumberUtils;
25
 
26
import java.math.RoundingMode;
27
import java.text.DecimalFormat;
28
import java.util.Arrays;
29
import java.util.Collections;
30
import java.util.List;
31
import java.util.Map.Entry;
32
import java.util.SortedMap;
33
import java.util.TreeMap;
34
import java.util.regex.Matcher;
35
import java.util.regex.Pattern;
36
 
37
import org.jdom.Attribute;
38
import org.jdom.Element;
39
import org.jdom.Namespace;
40
 
41
// from section 16.27 in v1.2-cs01-part1
42
public abstract class DataStyle extends Style {
43
    private static final int DEFAULT_GROUPING_SIZE = new DecimalFormat().getGroupingSize();
44
    private static final int DEFAULT_DECIMAL_PLACES = 2;
45
    private static final Pattern QUOTE_PATRN = Pattern.compile("'", Pattern.LITERAL);
46
    private static final Pattern EXP_PATTERN = Pattern.compile("E(\\d+)$");
47
 
48
    public static int getDecimalPlaces(final CellStyle defaultStyle) {
49
        final int res;
50
        if (defaultStyle != null && defaultStyle.getTableCellProperties().getDecimalPlaces() != null)
51
            res = defaultStyle.getTableCellProperties().getDecimalPlaces().intValue();
52
        else
53
            res = DEFAULT_DECIMAL_PLACES;
54
        return res;
55
    }
56
 
57
    public static void addStringLiteral(final StringBuilder formatSB, final String s) {
58
        formatSB.append('\'');
59
        formatSB.append(QUOTE_PATRN.matcher(s).replaceAll("''"));
60
        formatSB.append('\'');
61
    }
62
 
63
    public static final DataStyleDesc<?>[] DATA_STYLES_DESCS = new DataStyleDesc<?>[] { NumberStyle.DESC, PercentStyle.DESC, TextStyle.DESC, CurrencyStyle.DESC, DateStyle.DESC, TimeStyle.DESC,
64
            BooleanStyle.DESC };
65
 
66
    public static abstract class DataStyleDesc<S extends DataStyle> extends StyleDesc<S> {
67
 
68
        protected DataStyleDesc(Class<S> clazz, XMLVersion version, String elemName, String baseName) {
69
            super(clazz, version, elemName, baseName);
70
            this.setElementNS(getVersion().getNS("number"));
71
            // from 19.469 in v1.2-cs01-part1
72
            this.getRefElementsMap().putAll(
73
                    "style:data-style-name",
74
                    Arrays.asList("presentation:date-time-decl", "style:style", "text:creation-date", "text:creation-time", "text:database-display", "text:date", "text:editing-duration",
75
                            "text:expression", "text:meta-field", "text:modification-date", "text:modification-time", "text:print-date", "text:print-time", "text:table-formula", "text:time",
76
                            "text:user-defined", "text:user-field-get", "text:user-field-input", "text:variable-get", "text:variable-input", "text:variable-set"));
77
        }
78
    }
79
 
80
    // type accepted by #format()
81
    private final Class<?> type;
82
    private StyleTextProperties textProps;
83
 
84
    protected DataStyle(final ODPackage pkg, Element elem, final Class<?> type) {
85
        super(pkg, elem);
86
        this.type = type;
87
    }
88
 
89
    protected final Class<?> getDataType() {
90
        return this.type;
91
    }
92
 
93
    public final boolean canFormat(Class<?> toFormat) {
94
        return this.getDataType().isAssignableFrom(toFormat);
95
    }
96
 
97
    public final String getTitle() {
98
        return this.getElement().getAttributeValue("title", getElement().getNamespace());
99
    }
100
 
101
    public final StyleTextProperties getTextProperties() {
102
        if (this.textProps == null)
103
            this.textProps = new StyleTextProperties(this);
104
        return this.textProps;
105
    }
106
 
107
    public abstract String format(final Object o, final CellStyle defaultStyle, boolean lenient) throws UnsupportedOperationException;
108
 
109
    protected final void reportError(String msg, boolean lenient) throws UnsupportedOperationException {
110
        if (lenient)
111
            Log.get().warning(msg);
112
        else
113
            throw new UnsupportedOperationException(msg);
114
    }
115
 
116
    protected final String formatNumberOrScientificNumber(final Element elem, final Number n, CellStyle defaultStyle) {
117
        return this.formatNumberOrScientificNumber(elem, n, 1, defaultStyle);
118
    }
119
 
120
    protected final String formatNumberOrScientificNumber(final Element elem, final Number n, final int multiplier, CellStyle defaultStyle) {
121
        final Namespace numberNS = this.getElement().getNamespace();
122
        final StringBuilder numberSB = new StringBuilder();
123
 
124
        final List<?> embeddedTexts = elem.getChildren("embedded-text", numberNS);
125
        final SortedMap<Integer, String> embeddedTextByPosition = new TreeMap<Integer, String>(Collections.reverseOrder());
126
        for (final Object o : embeddedTexts) {
127
            final Element embeddedText = (Element) o;
128
            embeddedTextByPosition.put(Integer.valueOf(embeddedText.getAttributeValue("position", numberNS)), embeddedText.getText());
129
        }
130
 
131
        final Attribute factorAttr = elem.getAttribute("display-factor", numberNS);
132
        final double factor = (factorAttr != null ? Double.valueOf(factorAttr.getValue()) : 1) / multiplier;
133
 
134
        // default value from 19.348
135
        final boolean grouping = StyleProperties.parseBoolean(elem.getAttributeValue("grouping", numberNS), false);
136
 
137
        final String minIntDigitsAttr = elem.getAttributeValue("min-integer-digits", numberNS);
138
        final int minIntDig = minIntDigitsAttr == null ? 0 : Integer.parseInt(minIntDigitsAttr);
139
        if (minIntDig == 0) {
140
            numberSB.append('#');
141
        } else {
142
            for (int i = 0; i < minIntDig; i++)
143
                numberSB.append('0');
144
        }
145
 
146
        // e.g. if it's "--", 12,3 is displayed "12,3" and 12 is displayed "12,--"
147
        final String decReplacement = elem.getAttributeValue("decimal-replacement", numberNS);
148
        final boolean decSeparatorAlwaysShown;
149
        if (decReplacement != null && !NumberUtils.hasFractionalPart(n)) {
150
            decSeparatorAlwaysShown = true;
151
            numberSB.append('.');
152
            // escape quote in replacement
153
            addStringLiteral(numberSB, decReplacement);
154
        } else {
155
            decSeparatorAlwaysShown = false;
156
            // see 19.343.2
157
            final Attribute decPlacesAttr = elem.getAttribute("decimal-places", numberNS);
158
            final int decPlaces;
159
            if (decPlacesAttr != null)
160
                decPlaces = Integer.parseInt(decPlacesAttr.getValue());
161
            else
162
                decPlaces = getDecimalPlaces(defaultStyle);
163
 
164
            if (decPlaces > 0) {
165
                numberSB.append('.');
166
                for (int i = 0; i < decPlaces; i++)
167
                    numberSB.append('0');
168
            }
169
        }
170
 
171
        final Attribute minExpAttr = elem.getAttribute("min-exponent-digits", numberNS);
172
        if (minExpAttr != null) {
173
            numberSB.append('E');
174
            for (int i = 0; i < Integer.parseInt(minExpAttr.getValue()); i++)
175
                numberSB.append('0');
176
        }
177
 
178
        final DecimalFormat decFormat = new DecimalFormat(numberSB.toString());
179
        // Java always use HALF_EVEN
180
        decFormat.setRoundingMode(RoundingMode.HALF_UP);
181
        decFormat.setGroupingUsed(grouping);
182
        // needed since the default size is overwritten by the pattern
183
        decFormat.setGroupingSize(DEFAULT_GROUPING_SIZE);
184
        decFormat.setDecimalSeparatorAlwaysShown(decSeparatorAlwaysShown);
185
        String res = decFormat.format(NumberUtils.divide(n, factor));
186
        // java only puts the minus sign, OO also puts the plus sign
187
        if (minExpAttr != null) {
188
            final Matcher m = EXP_PATTERN.matcher(res);
189
            if (m.find())
190
                res = res.substring(0, m.start()) + "E+" + m.group(1);
191
        }
192
        if (embeddedTextByPosition.size() > 0) {
193
            final int intDigits = Math.max(minIntDig, NumberUtils.intDigits(n));
194
            // each time we insert text the decimal point moves
195
            int offset = 0;
196
            // sorted descending to avoid overwriting
197
            for (Entry<Integer, String> e : embeddedTextByPosition.entrySet()) {
198
                final String embeddedText = e.getValue();
199
                // the text will be before this index
200
                final int index = Math.max(0, offset + intDigits - e.getKey().intValue());
201
                res = res.substring(0, index) + embeddedText + res.substring(index);
202
                offset += embeddedText.length();
203
            }
204
        }
205
        return res;
206
    }
207
}