OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | 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;
67 ilm 17
import org.openconcerto.openoffice.ODEpoch;
17 ilm 18
import org.openconcerto.openoffice.ODPackage;
25 ilm 19
import org.openconcerto.openoffice.ODValueType;
20 ilm 20
import org.openconcerto.openoffice.Style;
17 ilm 21
import org.openconcerto.openoffice.StyleStyle;
22
import org.openconcerto.openoffice.StyleStyleDesc;
25 ilm 23
import org.openconcerto.openoffice.StyledNode;
17 ilm 24
import org.openconcerto.openoffice.XMLVersion;
25 ilm 25
import org.openconcerto.openoffice.style.RelationalOperator;
17 ilm 26
import org.openconcerto.openoffice.style.SideStyleProperties;
25 ilm 27
import org.openconcerto.openoffice.style.data.BooleanStyle;
20 ilm 28
import org.openconcerto.openoffice.style.data.DataStyle;
25 ilm 29
import org.openconcerto.openoffice.style.data.NumberStyle;
17 ilm 30
import org.openconcerto.openoffice.text.ParagraphStyle.StyleParagraphProperties;
31
import org.openconcerto.openoffice.text.TextStyle.StyleTextProperties;
25 ilm 32
import org.openconcerto.utils.CompareUtils;
33
import org.openconcerto.utils.Tuple3;
34
import org.openconcerto.xml.JDOMUtils;
17 ilm 35
 
36
import java.awt.Color;
25 ilm 37
import java.math.BigDecimal;
17 ilm 38
import java.util.Arrays;
25 ilm 39
import java.util.List;
40
import java.util.regex.Matcher;
41
import java.util.regex.Pattern;
17 ilm 42
 
25 ilm 43
import org.jdom.Attribute;
17 ilm 44
import org.jdom.Element;
73 ilm 45
import org.jdom.Namespace;
17 ilm 46
 
47
public class CellStyle extends StyleStyle {
48
 
25 ilm 49
    private static final Pattern numberPatrn = Pattern.compile("-?\\d+(?:\\.\\d+)?");
50
    private static final Pattern escapedQuotePatrn = Pattern.compile("\"\"", Pattern.LITERAL);
51
    private static final Pattern stringPatrn = Pattern.compile("\"(?:[^\\p{Cntrl}\"]|\\p{Space}|" + escapedQuotePatrn.pattern() + ")*\"");
52
    private static final String valuePatrn = "(" + numberPatrn.pattern() + "|" + stringPatrn.pattern() + ")";
53
    private static final Pattern cellContentPatrn = Pattern.compile("cell-content\\(\\) *(" + RelationalOperator.OR_PATTERN + ") *" + valuePatrn + "");
54
    private static final Pattern cellContentBetweenPatrn = Pattern.compile("cell-content-is(?:-not)?-between\\(" + valuePatrn + ", *" + valuePatrn + "\\)");
55
 
17 ilm 56
    // from section 18.728 in v1.2-part1
65 ilm 57
    private static final StyleStyleDesc<CellStyle> DESC = new StyleStyleDesc<CellStyle>(CellStyle.class, XMLVersion.OD, "table-cell", "ce", "table", Arrays.asList("table:body",
17 ilm 58
            "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")) {
59
 
60
        {
80 ilm 61
            this.getMultiRefElementsMap().addAll("table:default-cell-style-name", "table:table-column", "table:table-row");
17 ilm 62
        }
63
 
64
        @Override
65
        public CellStyle create(ODPackage pkg, Element e) {
66
            return new CellStyle(pkg, e);
67
        }
25 ilm 68
 
69
        @Override
70
        protected boolean supportConditions() {
71
            return true;
72
        }
73
 
74
        @Override
75
        protected Element evaluateConditions(final StyledNode<CellStyle, ?> styledNode, final List<Element> styleMaps) {
76
            final Cell<?> cell = (Cell<?>) styledNode;
67 ilm 77
            final ODEpoch epoch = cell.getODDocument().getEpoch();
25 ilm 78
            final Object cellValue = cell.getValue();
61 ilm 79
            final boolean cellIsEmpty = cell.isEmpty();
25 ilm 80
            for (final Element styleMap : styleMaps) {
81
                final String condition = styleMap.getAttributeValue("condition", getVersion().getSTYLE()).trim();
82
                Matcher matcher = cellContentPatrn.matcher(condition);
83
                if (matcher.matches()) {
61 ilm 84
                    final Object parsed = parse(matcher.group(2));
67 ilm 85
                    final Object usedCellValue = getValue(cellIsEmpty, epoch, cellValue, parsed);
86
                    if (usedCellValue != null && RelationalOperator.getInstance(matcher.group(1)).compare(usedCellValue, parsed))
25 ilm 87
                        return styleMap;
88
                } else if ((matcher = cellContentBetweenPatrn.matcher(condition)).matches()) {
89
                    final boolean wantBetween = condition.startsWith("cell-content-is-between");
90
                    assert wantBetween ^ condition.startsWith("cell-content-is-not-between");
91
                    final Object o1 = parse(matcher.group(1));
92
                    final Object o2 = parse(matcher.group(2));
61 ilm 93
                    assert o1.getClass() == o2.getClass();
67 ilm 94
                    final Object usedCellValue = getValue(cellIsEmpty, epoch, cellValue, o1);
95
                    if (usedCellValue != null) {
96
                        final boolean isBetween = CompareUtils.compare(usedCellValue, o1) >= 0 && CompareUtils.compare(usedCellValue, o2) <= 0;
97
                        if (isBetween == wantBetween)
98
                            return styleMap;
99
                    }
25 ilm 100
                } else {
101
                    // If a consumer does not recognize a condition, it shall ignore the <style:map>
102
                    // element containing the condition.
103
                    Log.get().fine("Ignoring " + JDOMUtils.output(styleMap));
104
                }
105
            }
106
            return null;
107
        }
17 ilm 108
    };
109
 
65 ilm 110
    static public void registerDesc() {
111
        Style.registerAllVersions(DESC);
112
    }
113
 
25 ilm 114
    private static final Pattern conditionPatrn = Pattern.compile("value\\(\\) *(" + RelationalOperator.OR_PATTERN + ") *(true|false|" + numberPatrn.pattern() + ")");
115
 
116
    // from style:condition :
117
    // "n is a number for non-Boolean data styles and true or false for Boolean data styles"
118
    private static final Object convertForCondition(final Object value, final DataStyle style) {
119
        final Object castedValue;
120
        if (style instanceof BooleanStyle) {
121
            castedValue = BooleanStyle.toBoolean(value);
122
        } else {
123
            castedValue = NumberStyle.toNumber(value, style.getEpoch());
124
        }
125
        return castedValue;
126
    }
127
 
17 ilm 128
    private StyleTextProperties textProps;
129
    private StyleParagraphProperties pProps;
130
 
131
    public CellStyle(final ODPackage pkg, Element tableColElem) {
132
        super(pkg, tableColElem);
133
    }
134
 
180 ilm 135
    final DataStyle getDataStyle(final Attribute name) {
25 ilm 136
        return (DataStyle) Style.getReferencedStyle(getPackage(), name);
20 ilm 137
    }
138
 
180 ilm 139
    // see MutableCell#getDataStyle()
61 ilm 140
    final DataStyle getDataStyle() {
141
        return getDataStyle(this.getElement().getAttribute("data-style-name", this.getSTYLE()));
142
    }
143
 
25 ilm 144
    // return value since it can be changed depending on the data style.
145
    // e.g. in OO if we input 12:30 in an empty cell, it will have value-type="time"
146
    // but if we had previously set a number style (like 0,00) it would have been converted to 0,52
147
    // value-type="float"
148
    final Tuple3<DataStyle, ODValueType, Object> getDataStyle(final Object cellValue, final ODValueType valueType, final boolean onlyCast) {
61 ilm 149
        DataStyle res = getDataStyle();
25 ilm 150
        ODValueType returnValueType = valueType;
151
        Object returnCellValue = cellValue;
152
        // if the type is null, then the cell is empty so don't try to convert the cell value or
153
        // evaluate conditions
154
        if (res != null && valueType != null) {
155
            if (!onlyCast) {
156
                final Object convertedForStyle = res.convert(cellValue);
157
                // if conversion is successful
158
                if (convertedForStyle != null) {
159
                    returnCellValue = convertedForStyle;
160
                    returnValueType = res.getDataType();
161
                }
162
            }
163
 
180 ilm 164
            final List<Element> styleMaps = res.getMapChildren();
25 ilm 165
            if (styleMaps.size() > 0) {
166
                final Object converted = convertForCondition(returnCellValue, res);
167
                // we can't compare() so don't try
168
                if (converted != null) {
180 ilm 169
                    for (Element child : styleMaps) {
170
                        final Element styleMap = child;
25 ilm 171
                        final Matcher matcher = conditionPatrn.matcher(styleMap.getAttributeValue("condition", getSTYLE()).trim());
172
                        if (!matcher.matches())
173
                            throw new IllegalStateException("Cannot parse " + JDOMUtils.output(styleMap));
174
                        if (RelationalOperator.getInstance(matcher.group(1)).compare(converted, parse(matcher.group(2)))) {
175
                            res = getDataStyle(styleMap.getAttribute("apply-style-name", getSTYLE()));
176
                            break;
177
                        }
178
                    }
179
                }
180
            }
181
        }
182
        // if the type is null, then the cell is empty, we cannot make up some value, otherwise
183
        // don't change it to null
184
        assert (valueType == null) == (returnValueType == null) : "don't change type to null";
185
        assert !onlyCast || (returnValueType == valueType && returnCellValue == cellValue) : "Requested to only cast, but different object";
186
        // if res is null, the document is incoherent (non existing style name)
187
        return res == null ? null : Tuple3.create(res, returnValueType, returnCellValue);
188
    }
189
 
190
    static private Object parse(String val) {
191
        if (val.equalsIgnoreCase("true"))
192
            return Boolean.TRUE;
193
        else if (val.equalsIgnoreCase("false"))
194
            return Boolean.FALSE;
195
        else if (val.charAt(0) == '"')
196
            return escapedQuotePatrn.matcher(val.substring(1, val.length() - 1)).replaceAll("\"");
197
        else
198
            return new BigDecimal(val);
199
    }
200
 
61 ilm 201
    static private Object getDefault(Class<?> clazz) {
202
        if (clazz == Boolean.class)
203
            return Boolean.FALSE;
204
        else if (clazz == String.class)
205
            return "";
206
        else if (clazz == BigDecimal.class)
207
            return BigDecimal.ZERO;
208
        else
209
            throw new IllegalStateException("Unknown default for " + clazz);
210
    }
211
 
67 ilm 212
    // convert cellValue to class of parsed, return null if not possible
213
    static private Object getValue(boolean cellIsEmpty, ODEpoch epoch, Object cellValue, Object parsed) {
214
        final Class<?> conditionClass = parsed.getClass();
215
        if (cellIsEmpty) {
216
            // LO uses the default value for the type when the cell is empty
217
            return getDefault(conditionClass);
218
        } else if (cellValue.getClass() == conditionClass) {
219
            return cellValue;
220
        } else {
221
            // LO doesn't convert between String and Number, but Boolean are Numbers
222
            if (conditionClass == String.class) {
223
                return null;
224
            } else if (conditionClass == Boolean.class) {
225
                return BooleanStyle.toBoolean(cellValue);
226
            } else if (Number.class.isAssignableFrom(conditionClass)) {
227
                return NumberStyle.toNumber(cellValue, epoch);
228
            } else {
229
                throw new IllegalStateException("Invalid class value for condition : " + conditionClass);
230
            }
231
        }
61 ilm 232
    }
233
 
73 ilm 234
    @Deprecated
17 ilm 235
    public final Color getBackgroundColor() {
236
        return getTableCellProperties().getBackgroundColor();
237
    }
238
 
73 ilm 239
    public final Color getBackgroundColor(final Cell<?> styledNode) {
240
        return getTableCellProperties(styledNode).getBackgroundColor();
241
    }
242
 
243
    @Deprecated
17 ilm 244
    public final StyleTableCellProperties getTableCellProperties() {
73 ilm 245
        return this.getTableCellProperties(null);
17 ilm 246
    }
247
 
73 ilm 248
    // MAYBE add getWriteOnlyTableCellProperties() and enforce it in
249
    // StyleProperties.getAttributeValue(). That way getTableCellProperties() can check for non null
250
    // parameter.
251
    public final StyleTableCellProperties getTableCellProperties(final Cell<?> styledNode) {
252
        // no longer cache since the result depends on the passed node (a simple ICache is slower)
253
        return new StyleTableCellProperties(this, styledNode);
254
    }
255
 
17 ilm 256
    public final StyleTextProperties getTextProperties() {
257
        if (this.textProps == null)
258
            this.textProps = new StyleTextProperties(this);
259
        return this.textProps;
260
    }
261
 
262
    public final StyleParagraphProperties getParagraphProperties() {
263
        if (this.pProps == null)
264
            this.pProps = new StyleParagraphProperties(this);
265
        return this.pProps;
266
    }
267
 
268
    /**
269
     * See section 15.11 of OpenDocument v1.1 : Table Cell Formatting Properties.
270
     *
271
     * @author Sylvain CUAZ
272
     */
273
    public static class StyleTableCellProperties extends SideStyleProperties {
274
 
73 ilm 275
        public <S extends StyleStyle> StyleTableCellProperties(S style, StyledNode<S, ?> styledNode) {
276
            super(style, DESC.getFamily(), styledNode);
17 ilm 277
        }
278
 
73 ilm 279
        protected String getAttributeValueInAncestors(final Cell<?> cell, final TableCalcNode<?, ?> calcNode, String attrName, Namespace attrNS) {
280
            final Element elem = calcNode.getElement();
281
            final String cellStyleName = elem.getAttributeValue("default-cell-style-name", elem.getNamespace("table"));
282
            if (cellStyleName == null) {
283
                return null;
284
            } else {
285
                final CellStyle style = cell.getStyleDesc().findStyleForNode(calcNode.getODDocument().getPackage(), elem.getDocument(), cell, cellStyleName);
286
                return this.getAttributeValueInAncestors(style, true, attrName, attrNS);
287
            }
288
        }
289
 
290
        @Override
291
        protected String getAttributeValueNotInAncestors(String attrName, Namespace attrNS) {
292
            String res = null;
293
            // from §16.2 of OpenDocument v1.2 (LO ignores it)
294
            if (Style.isStandardStyleResolution() && this.getEnclosingStyle() instanceof CellStyle && this.getStyledNode() instanceof Cell) {
295
                if (!(this.getStyledNode() instanceof MutableCell))
296
                    // MAYBE pass the column alongside the cell in the constructor (from
297
                    // Table.getTableCellPropertiesAt())
298
                    throw new UnsupportedOperationException("Missing column for " + getStyledNode());
299
                final Cell<?> cell = (Cell<?>) this.getStyledNode();
300
                res = this.getAttributeValueInAncestors(cell, cell.getRow(), attrName, attrNS);
301
                if (res != null)
302
                    return res;
303
                res = this.getAttributeValueInAncestors(cell, cell.getRow().getSheet().getColumn(((MutableCell<?>) cell).getX()), attrName, attrNS);
304
            }
305
            return res;
306
        }
307
 
308
        @Override
309
        protected boolean fallbackToDefaultStyle(String attrName, Namespace attrNS) {
310
            // all properties that I've test are ignored by LO
311
            return false;
312
        }
313
 
17 ilm 314
        public final int getRotationAngle() {
20 ilm 315
            final String s = this.getAttributeValue("rotation-angle", this.getElement().getNamespace("style"));
316
            return parseInt(s, 0);
17 ilm 317
        }
318
 
73 ilm 319
        public final void setRotationAngle(final Integer angle) {
320
            this.setAttributeValue(angle, "rotation-angle");
321
        }
322
 
17 ilm 323
        public final boolean isContentPrinted() {
20 ilm 324
            return parseBoolean(this.getAttributeValue("print-content", this.getElement().getNamespace("style")), true);
17 ilm 325
        }
326
 
327
        public final boolean isContentRepeated() {
20 ilm 328
            return parseBoolean(this.getAttributeValue("repeat-content", this.getElement().getNamespace("style")), false);
17 ilm 329
        }
330
 
331
        public final boolean isShrinkToFit() {
20 ilm 332
            return parseBoolean(this.getAttributeValue("shrink-to-fit", this.getElement().getNamespace("style")), false);
17 ilm 333
        }
20 ilm 334
 
73 ilm 335
        // *maximum* number of decimal places to display (number:decimal-places is the *exact*
336
        // number to display)
337
        public final int getDecimalPlaces() {
338
            // see §20.250 style:decimal-places of OpenDocument v1.2
339
            if (!getEnclosingStyle().getElement().getName().equals(StyleStyleDesc.ELEMENT_DEFAULT_NAME))
340
                throw new IllegalStateException("Not on a default style : " + this.getEnclosingStyle());
341
            return parseInt(this.getRawDecimalPlaces(), DataStyle.DEFAULT_DECIMAL_PLACES);
20 ilm 342
        }
65 ilm 343
 
73 ilm 344
        public final String getRawDecimalPlaces() {
345
            return this.getAttributeValue("decimal-places", this.getElement().getNamespace("style"));
346
        }
347
 
65 ilm 348
        public final boolean isWrapping() {
73 ilm 349
            final String val = this.getAttributeValue("wrap-option", this.getNS("fo"));
65 ilm 350
            if (val == null || val.equals("no-wrap"))
351
                return false;
352
            else if (val.equals("wrap"))
353
                return true;
354
            else
355
                throw new IllegalStateException("Unknown value : " + val);
356
        }
357
 
358
        public final void setWrapping(final boolean b) {
73 ilm 359
            this.setAttributeValue(b ? "wrap" : "no-wrap", "wrap-option", getNS("fo"));
65 ilm 360
        }
17 ilm 361
    }
362
 
363
}