OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 20 | Rev 61 | 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.spreadsheet;
15
 
25 ilm 16
import org.openconcerto.openoffice.Log;
17 ilm 17
import org.openconcerto.openoffice.ODPackage;
25 ilm 18
import org.openconcerto.openoffice.ODValueType;
20 ilm 19
import org.openconcerto.openoffice.Style;
17 ilm 20
import org.openconcerto.openoffice.StyleStyle;
21
import org.openconcerto.openoffice.StyleStyleDesc;
25 ilm 22
import org.openconcerto.openoffice.StyledNode;
17 ilm 23
import org.openconcerto.openoffice.XMLVersion;
25 ilm 24
import org.openconcerto.openoffice.style.RelationalOperator;
17 ilm 25
import org.openconcerto.openoffice.style.SideStyleProperties;
25 ilm 26
import org.openconcerto.openoffice.style.data.BooleanStyle;
20 ilm 27
import org.openconcerto.openoffice.style.data.DataStyle;
25 ilm 28
import org.openconcerto.openoffice.style.data.NumberStyle;
17 ilm 29
import org.openconcerto.openoffice.text.ParagraphStyle.StyleParagraphProperties;
30
import org.openconcerto.openoffice.text.TextStyle.StyleTextProperties;
25 ilm 31
import org.openconcerto.utils.CompareUtils;
32
import org.openconcerto.utils.Tuple3;
33
import org.openconcerto.xml.JDOMUtils;
17 ilm 34
 
35
import java.awt.Color;
25 ilm 36
import java.math.BigDecimal;
17 ilm 37
import java.util.Arrays;
25 ilm 38
import java.util.List;
39
import java.util.regex.Matcher;
40
import java.util.regex.Pattern;
17 ilm 41
 
25 ilm 42
import org.jdom.Attribute;
17 ilm 43
import org.jdom.Element;
44
 
45
public class CellStyle extends StyleStyle {
46
 
25 ilm 47
    private static final Pattern numberPatrn = Pattern.compile("-?\\d+(?:\\.\\d+)?");
48
    private static final Pattern escapedQuotePatrn = Pattern.compile("\"\"", Pattern.LITERAL);
49
    private static final Pattern stringPatrn = Pattern.compile("\"(?:[^\\p{Cntrl}\"]|\\p{Space}|" + escapedQuotePatrn.pattern() + ")*\"");
50
    private static final String valuePatrn = "(" + numberPatrn.pattern() + "|" + stringPatrn.pattern() + ")";
51
    private static final Pattern cellContentPatrn = Pattern.compile("cell-content\\(\\) *(" + RelationalOperator.OR_PATTERN + ") *" + valuePatrn + "");
52
    private static final Pattern cellContentBetweenPatrn = Pattern.compile("cell-content-is(?:-not)?-between\\(" + valuePatrn + ", *" + valuePatrn + "\\)");
53
 
17 ilm 54
    // from section 18.728 in v1.2-part1
55
    public static final StyleStyleDesc<CellStyle> DESC = new StyleStyleDesc<CellStyle>(CellStyle.class, XMLVersion.OD, "table-cell", "ce", "table", Arrays.asList("table:body",
56
            "table:covered-table-cell", "table:even-rows", "table:first-column", "table:first-row", "table:last-column", "table:last-row", "table:odd-columns", "table:odd-rows", "table:table-cell")) {
57
 
58
        {
59
            this.getMultiRefElementsMap().putAll("table:default-cell-style-name", "table:table-column", "table:table-row");
60
        }
61
 
62
        @Override
63
        public CellStyle create(ODPackage pkg, Element e) {
64
            return new CellStyle(pkg, e);
65
        }
25 ilm 66
 
67
        @Override
68
        protected boolean supportConditions() {
69
            return true;
70
        }
71
 
72
        @Override
73
        protected Element evaluateConditions(final StyledNode<CellStyle, ?> styledNode, final List<Element> styleMaps) {
74
            final Cell<?> cell = (Cell<?>) styledNode;
75
            final Object cellValue = cell.getValue();
76
            for (final Element styleMap : styleMaps) {
77
                final String condition = styleMap.getAttributeValue("condition", getVersion().getSTYLE()).trim();
78
                Matcher matcher = cellContentPatrn.matcher(condition);
79
                if (matcher.matches()) {
80
                    if (RelationalOperator.getInstance(matcher.group(1)).compare(cellValue, parse(matcher.group(2))))
81
                        return styleMap;
82
                } else if ((matcher = cellContentBetweenPatrn.matcher(condition)).matches()) {
83
                    final boolean wantBetween = condition.startsWith("cell-content-is-between");
84
                    assert wantBetween ^ condition.startsWith("cell-content-is-not-between");
85
                    final Object o1 = parse(matcher.group(1));
86
                    final Object o2 = parse(matcher.group(2));
87
                    final boolean isBetween = CompareUtils.compare(cellValue, o1) >= 0 && CompareUtils.compare(cellValue, o2) <= 0;
88
                    if (isBetween == wantBetween)
89
                        return styleMap;
90
                } else {
91
                    // If a consumer does not recognize a condition, it shall ignore the <style:map>
92
                    // element containing the condition.
93
                    Log.get().fine("Ignoring " + JDOMUtils.output(styleMap));
94
                }
95
            }
96
            return null;
97
        }
17 ilm 98
    };
99
 
25 ilm 100
    private static final Pattern conditionPatrn = Pattern.compile("value\\(\\) *(" + RelationalOperator.OR_PATTERN + ") *(true|false|" + numberPatrn.pattern() + ")");
101
 
102
    // from style:condition :
103
    // "n is a number for non-Boolean data styles and true or false for Boolean data styles"
104
    private static final Object convertForCondition(final Object value, final DataStyle style) {
105
        final Object castedValue;
106
        if (style instanceof BooleanStyle) {
107
            castedValue = BooleanStyle.toBoolean(value);
108
        } else {
109
            castedValue = NumberStyle.toNumber(value, style.getEpoch());
110
        }
111
        return castedValue;
112
    }
113
 
17 ilm 114
    private StyleTableCellProperties cellProps;
115
    private StyleTextProperties textProps;
116
    private StyleParagraphProperties pProps;
117
 
118
    public CellStyle(final ODPackage pkg, Element tableColElem) {
119
        super(pkg, tableColElem);
120
    }
121
 
25 ilm 122
    private final DataStyle getDataStyle(final Attribute name) {
123
        return (DataStyle) Style.getReferencedStyle(getPackage(), name);
20 ilm 124
    }
125
 
25 ilm 126
    // return value since it can be changed depending on the data style.
127
    // e.g. in OO if we input 12:30 in an empty cell, it will have value-type="time"
128
    // but if we had previously set a number style (like 0,00) it would have been converted to 0,52
129
    // value-type="float"
130
    final Tuple3<DataStyle, ODValueType, Object> getDataStyle(final Object cellValue, final ODValueType valueType, final boolean onlyCast) {
131
        DataStyle res = getDataStyle(this.getElement().getAttribute("data-style-name", this.getSTYLE()));
132
        ODValueType returnValueType = valueType;
133
        Object returnCellValue = cellValue;
134
        // if the type is null, then the cell is empty so don't try to convert the cell value or
135
        // evaluate conditions
136
        if (res != null && valueType != null) {
137
            if (!onlyCast) {
138
                final Object convertedForStyle = res.convert(cellValue);
139
                // if conversion is successful
140
                if (convertedForStyle != null) {
141
                    returnCellValue = convertedForStyle;
142
                    returnValueType = res.getDataType();
143
                }
144
            }
145
 
146
            final List<?> styleMaps = res.getElement().getChildren("map", getSTYLE());
147
            if (styleMaps.size() > 0) {
148
                final Object converted = convertForCondition(returnCellValue, res);
149
                // we can't compare() so don't try
150
                if (converted != null) {
151
                    for (Object child : styleMaps) {
152
                        final Element styleMap = (Element) child;
153
                        final Matcher matcher = conditionPatrn.matcher(styleMap.getAttributeValue("condition", getSTYLE()).trim());
154
                        if (!matcher.matches())
155
                            throw new IllegalStateException("Cannot parse " + JDOMUtils.output(styleMap));
156
                        if (RelationalOperator.getInstance(matcher.group(1)).compare(converted, parse(matcher.group(2)))) {
157
                            res = getDataStyle(styleMap.getAttribute("apply-style-name", getSTYLE()));
158
                            break;
159
                        }
160
                    }
161
                }
162
            }
163
        }
164
        // if the type is null, then the cell is empty, we cannot make up some value, otherwise
165
        // don't change it to null
166
        assert (valueType == null) == (returnValueType == null) : "don't change type to null";
167
        assert !onlyCast || (returnValueType == valueType && returnCellValue == cellValue) : "Requested to only cast, but different object";
168
        // if res is null, the document is incoherent (non existing style name)
169
        return res == null ? null : Tuple3.create(res, returnValueType, returnCellValue);
170
    }
171
 
172
    static private Object parse(String val) {
173
        if (val.equalsIgnoreCase("true"))
174
            return Boolean.TRUE;
175
        else if (val.equalsIgnoreCase("false"))
176
            return Boolean.FALSE;
177
        else if (val.charAt(0) == '"')
178
            return escapedQuotePatrn.matcher(val.substring(1, val.length() - 1)).replaceAll("\"");
179
        else
180
            return new BigDecimal(val);
181
    }
182
 
17 ilm 183
    public final Color getBackgroundColor() {
184
        return getTableCellProperties().getBackgroundColor();
185
    }
186
 
187
    public final StyleTableCellProperties getTableCellProperties() {
188
        if (this.cellProps == null)
189
            this.cellProps = new StyleTableCellProperties(this);
190
        return this.cellProps;
191
    }
192
 
193
    public final StyleTextProperties getTextProperties() {
194
        if (this.textProps == null)
195
            this.textProps = new StyleTextProperties(this);
196
        return this.textProps;
197
    }
198
 
199
    public final StyleParagraphProperties getParagraphProperties() {
200
        if (this.pProps == null)
201
            this.pProps = new StyleParagraphProperties(this);
202
        return this.pProps;
203
    }
204
 
205
    /**
206
     * See section 15.11 of OpenDocument v1.1 : Table Cell Formatting Properties.
207
     *
208
     * @author Sylvain CUAZ
209
     */
210
    public static class StyleTableCellProperties extends SideStyleProperties {
211
 
212
        public StyleTableCellProperties(StyleStyle style) {
213
            super(style, DESC.getFamily());
214
        }
215
 
216
        public final int getRotationAngle() {
20 ilm 217
            final String s = this.getAttributeValue("rotation-angle", this.getElement().getNamespace("style"));
218
            return parseInt(s, 0);
17 ilm 219
        }
220
 
221
        public final boolean isContentPrinted() {
20 ilm 222
            return parseBoolean(this.getAttributeValue("print-content", this.getElement().getNamespace("style")), true);
17 ilm 223
        }
224
 
225
        public final boolean isContentRepeated() {
20 ilm 226
            return parseBoolean(this.getAttributeValue("repeat-content", this.getElement().getNamespace("style")), false);
17 ilm 227
        }
228
 
229
        public final boolean isShrinkToFit() {
20 ilm 230
            return parseBoolean(this.getAttributeValue("shrink-to-fit", this.getElement().getNamespace("style")), false);
17 ilm 231
        }
20 ilm 232
 
233
        public final Integer getDecimalPlaces() {
234
            return parseInteger(this.getAttributeValue("decimal-places", this.getElement().getNamespace("style")));
235
        }
17 ilm 236
    }
237
 
238
}