OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 21 | Rev 73 | 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
 /*
15
 * Cell created on 10 décembre 2005
16
 */
17
package org.openconcerto.openoffice.spreadsheet;
18
 
19
import org.openconcerto.openoffice.ODDocument;
20
import org.openconcerto.openoffice.ODValueType;
20 ilm 21
import org.openconcerto.openoffice.OOXML;
25 ilm 22
import org.openconcerto.openoffice.StyleDesc;
19 ilm 23
import org.openconcerto.openoffice.XMLFormatVersion;
17 ilm 24
import org.openconcerto.openoffice.XMLVersion;
25
import org.openconcerto.utils.CollectionUtils;
20 ilm 26
import org.openconcerto.xml.JDOMUtils;
17 ilm 27
 
28
import java.util.ArrayList;
29
import java.util.Arrays;
30
import java.util.Iterator;
31
import java.util.List;
32
import java.util.regex.Pattern;
33
 
34
import org.jdom.Element;
35
import org.jdom.Namespace;
36
import org.jdom.Text;
37
 
38
/**
39
 * A cell in a calc document. If you want to change a cell value you must obtain a MutableCell.
40
 *
41
 * @author Sylvain
42
 * @param <D> type of document
43
 */
44
public class Cell<D extends ODDocument> extends TableCalcNode<CellStyle, D> {
45
 
46
    // see 5.1.1
47
    private static final Pattern multiSpacePattern = Pattern.compile("[\t\r\n ]+");
48
    private static boolean OO_MODE = true;
49
 
19 ilm 50
    // from §5.12 of OpenDocument-v1.2-cs01-part2
51
    // Error ::= '#' [A-Z0-9]+ ([!?] | ('/' ([A-Z] | ([0-9] [!?]))))
21 ilm 52
    // we added an optional space before the marks to support OpenOffice/LibreOffice (at least until
53
    // 3.4)
19 ilm 54
    private static final Pattern ErrorPattern = Pattern.compile("#[A-Z0-9]+( ?[!?]|(/([A-Z]|([0-9] ?[!?]))))");
55
 
17 ilm 56
    /**
57
     * Set whether {@link #getTextValue()} parses strings using the standard way or using the
58
     * OpenOffice.org way.
59
     *
60
     * @param ooMode <code>true</code> if strings should be parsed the OO way.
61
     * @see #getTextValue(boolean)
62
     */
63
    public static void setTextValueMode(boolean ooMode) {
64
        OO_MODE = ooMode;
65
    }
66
 
67
    public static boolean getTextValueMode() {
68
        return OO_MODE;
69
    }
70
 
71
    static final Element createEmpty(XMLVersion ns) {
72
        return createEmpty(ns, 1);
73
    }
74
 
75
    static final Element createEmpty(XMLVersion ns, int count) {
76
        final Element e = new Element("table-cell", ns.getTABLE());
77
        if (count > 1)
78
            e.setAttribute("number-columns-repeated", count + "", ns.getTABLE());
79
        return e;
80
    }
81
 
82
    private final Row<D> row;
83
 
25 ilm 84
    Cell(Row<D> parent, Element elem, StyleDesc<CellStyle> styleDesc) {
85
        super(parent.getODDocument(), elem, styleDesc);
17 ilm 86
        this.row = parent;
87
    }
88
 
89
    protected final Row<D> getRow() {
90
        return this.row;
91
    }
92
 
93
    protected final XMLVersion getNS() {
94
        return this.getODDocument().getVersion();
95
    }
96
 
97
    protected final Namespace getValueNS() {
98
        final XMLVersion ns = this.getNS();
99
        return ns == XMLVersion.OD ? ns.getOFFICE() : ns.getTABLE();
100
    }
101
 
102
    protected final String getType() {
103
        return this.getElement().getAttributeValue("value-type", getValueNS());
104
    }
105
 
106
    public final ODValueType getValueType() {
107
        final String type = this.getType();
108
        return type == null ? null : ODValueType.get(type);
109
    }
110
 
111
    // cannot resolve our style since a single instance of Cell is used for all
112
    // repeated and thus if we need to check table-column table:default-cell-style-name
113
    // we wouldn't know which column to check.
114
    @Override
115
    protected String getStyleName() {
116
        throw new UnsupportedOperationException("cannot resolve our style, use MutableCell");
117
    }
118
 
119
    String getStyleAttr() {
120
        return this.getElement().getAttributeValue("style-name", getNS().getTABLE());
121
    }
122
 
123
    private final String getValue(String attrName) {
124
        return this.getElement().getAttributeValue(attrName, getValueNS());
125
    }
126
 
127
    public Object getValue() {
128
        final ODValueType vt = this.getValueType();
129
        if (vt == null || vt == ODValueType.STRING) {
130
            // ATTN oo generates string value-types w/o any @string-value
131
            final String attr = vt == null ? null : this.getValue(vt.getValueAttribute());
132
            if (attr != null)
133
                return attr;
134
            else {
135
                return getTextValue();
136
            }
137
        } else {
138
            return vt.parse(this.getValue(vt.getValueAttribute()));
139
        }
140
    }
141
 
142
    /**
143
     * Calls {@link #getTextValue(boolean)} using {@link #getTextValueMode()}.
144
     *
145
     * @return a string for the content of this cell.
146
     */
147
    public String getTextValue() {
148
        return this.getTextValue(getTextValueMode());
149
    }
150
 
151
    /**
152
     * Return the text value of this cell. This is often the formatted string of a value, e.g.
153
     * "11 novembre 2009" for a date. This method doesn't just return the text content it also
154
     * parses XML elements (like paragraphs, tabs and line-breaks). For the differences between the
155
     * OO way (as of 3.1) and the OpenDocument way see section 5.1.1 White-space Characters of
156
     * OpenDocument-v1.0-os and OpenDocument-v1.2-part1. In essence OpenOffice never trim strings.
157
     *
158
     * @param ooMode whether to use the OO way or the standard way.
159
     * @return a string for the content of this cell.
160
     */
161
    public String getTextValue(final boolean ooMode) {
162
        final List<String> ps = new ArrayList<String>();
163
        for (final Object o : this.getElement().getChildren()) {
164
            final Element child = (Element) o;
165
            if ((child.getName().equals("p") || child.getName().equals("h")) && child.getNamespacePrefix().equals("text")) {
166
                ps.add(getStringValue(child, ooMode));
167
            }
168
        }
169
        return CollectionUtils.join(ps, "\n");
170
    }
171
 
172
    private String getStringValue(final Element pElem, final boolean ooMode) {
173
        final StringBuilder sb = new StringBuilder();
174
        final Namespace textNS = pElem.getNamespace();
20 ilm 175
        final OOXML xml = OOXML.get(getODDocument().getFormatVersion());
176
        final Element tabElem = xml.getTab();
177
        final Element newLineElem = xml.getLineBreak();
17 ilm 178
        // true if the string ends with a space that wasn't expanded from an XML element (e.g.
179
        // <tab/> or <text:s/>)
180
        boolean spaceSuffix = false;
20 ilm 181
        final Iterator<?> iter = pElem.getDescendants();
17 ilm 182
        while (iter.hasNext()) {
183
            final Object o = iter.next();
184
            if (o instanceof Text) {
185
                final String text = multiSpacePattern.matcher(((Text) o).getText()).replaceAll(" ");
186
                // trim leading
187
                if (!ooMode && text.startsWith(" ") && (spaceSuffix || sb.length() == 0))
188
                    sb.append(text.substring(1));
189
                else
190
                    sb.append(text);
191
                spaceSuffix = text.endsWith(" ");
192
            } else if (o instanceof Element) {
193
                final Element elem = (Element) o;
20 ilm 194
                if (JDOMUtils.equals(elem, tabElem)) {
17 ilm 195
                    sb.append("\t");
20 ilm 196
                } else if (JDOMUtils.equals(elem, newLineElem)) {
17 ilm 197
                    sb.append("\n");
198
                } else if (elem.getName().equals("s") && elem.getNamespace().equals(textNS)) {
199
                    final int count = Integer.valueOf(elem.getAttributeValue("c", textNS, "1"));
200
                    final char[] toAdd = new char[count];
201
                    Arrays.fill(toAdd, ' ');
202
                    sb.append(toAdd);
203
                }
204
            }
205
        }
206
        // trim trailing
207
        if (!ooMode && spaceSuffix)
208
            sb.deleteCharAt(sb.length() - 1);
209
 
210
        return sb.toString();
211
    }
212
 
19 ilm 213
    public final String getFormula() {
214
        return this.getElement().getAttributeValue("formula", getTABLE());
215
    }
216
 
217
    /**
218
     * Tries to find out if this cell computation resulted in an error. This method cannot be robust
219
     * since there's no error attribute in OpenDocument, we must match the value of the cell against
220
     * a pattern. E.g. whether a cell has '=A0' for formula or '= "#N" & "/A"', this method will
221
     * return a non-null error.
222
     *
223
     * @return the error or <code>null</code>.
224
     */
225
    public String getError() {
226
        // to differentiate between the result of a computation and the user having typed '#N/A'
227
        // (this is because per §4.6 of OpenDocument-v1.2-cs01-part2 : if an error value is the
228
        // result of a cell computation it shall be stored as if it was a string.)
229
        if (getFormula() == null)
230
            return null;
231
        final String textValue = getTextValue();
232
        // OpenDocument 1.1 didn't specify errors
233
        return (XMLFormatVersion.get(XMLVersion.OD, "1.1").equals(getODDocument().getFormatVersion()) && textValue.equals("#NA")) || ErrorPattern.matcher(textValue).matches() ? textValue : null;
234
    }
235
 
17 ilm 236
    public boolean isValid() {
21 ilm 237
        return !this.isCovered();
17 ilm 238
    }
239
 
21 ilm 240
    protected final boolean isCovered() {
241
        return this.getElement().getName().equals("covered-table-cell");
242
    }
243
 
17 ilm 244
    public final boolean isEmpty() {
245
        return this.getValueType() == null && this.getElement().getContentSize() == 0;
246
    }
247
 
248
    public final int getColumnsSpanned() {
249
        // from 8.1.3 Table Cell
250
        return Integer.parseInt(this.getElement().getAttributeValue("number-columns-spanned", getNS().getTABLE(), "1"));
251
    }
252
 
253
    public final int getRowsSpanned() {
254
        // from 8.1.3 Table Cell
255
        return Integer.parseInt(this.getElement().getAttributeValue("number-rows-spanned", getNS().getTABLE(), "1"));
256
    }
257
 
258
    protected final boolean coversOtherCells() {
259
        return getColumnsSpanned() > 1 || getRowsSpanned() > 1;
260
    }
261
}