OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
83 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
 
16
import org.openconcerto.openoffice.ODDocument;
17
import org.openconcerto.openoffice.OOXML;
18
import org.openconcerto.openoffice.XMLFormatVersion;
19
import org.openconcerto.openoffice.text.Heading;
20
import org.openconcerto.openoffice.text.Paragraph;
21
import org.openconcerto.openoffice.text.Span;
22
import org.openconcerto.openoffice.text.TextNode;
23
import org.openconcerto.openoffice.text.TextNodeDesc;
24
import org.openconcerto.xml.JDOMUtils;
25
 
26
import java.util.ArrayList;
27
import java.util.Iterator;
28
import java.util.LinkedList;
29
import java.util.List;
30
import java.util.regex.Matcher;
31
import java.util.regex.Pattern;
32
 
33
import org.jdom.Content;
34
import org.jdom.Element;
35
import org.jdom.Text;
36
import org.jdom.filter.ElementFilter;
37
import org.jdom.filter.Filter;
38
 
39
// this represent a list of lines separated by Sep, e.g. :
40
// foo Sep.ANY bar Sep.LINE baz Sep.PARAGRAPH
41
public final class Lines {
42
 
43
    // remove all text:span (keeping their content) from elem
44
    static private final void flattenSpans(final Element elem, final XMLFormatVersion vers) {
45
        final ElementFilter filter = TextNodeDesc.get(Span.class).getFilter(vers);
46
        int i = 0;
47
        int size = elem.getContentSize();
48
        while (i < size) {
49
            final Content c = elem.getContent(i);
50
            if (filter.matches(c)) {
51
                elem.addContent(i, ((Element) c).removeContent());
52
                c.detach();
53
                size = elem.getContentSize();
54
            } else {
55
                i++;
56
            }
57
        }
58
    }
59
 
60
    // newline or line separator or paragraph separator
182 ilm 61
    static private final Pattern SEP_PATTERN = Pattern.compile("(\r?\n)|\f|" + TextNode.VERTICAL_TAB_CHAR + "|\\p{Zl}|\\p{Zp}");
83 ilm 62
 
63
    static private enum Sep {
64
        // allow to re-use exiting lines (be it a line-break or a new paragraph)
65
        ANY,
66
        // explicit line-break
67
        LINE,
68
        // explicit new paragraph
69
        PARAGRAPH
70
    }
71
 
72
    static private final Sep getSep(final String s, final boolean onlyP) {
73
        final int codePoint = s.codePointAt(0);
74
        final Sep res;
75
        if (codePoint == '\n' || codePoint == '\r') {
76
            res = Sep.ANY;
77
        } else if (codePoint == TextNode.VERTICAL_TAB_CHAR) {
78
            res = Sep.LINE;
182 ilm 79
        } else if (codePoint == '\f') {
80
            res = Sep.PARAGRAPH;
83 ilm 81
        } else {
82
            final int cat = Character.getType(codePoint);
83
            if (cat == Character.PARAGRAPH_SEPARATOR) {
84
                res = Sep.PARAGRAPH;
85
            } else if (cat == Character.LINE_SEPARATOR) {
86
                res = Sep.LINE;
87
            } else {
88
                throw new IllegalArgumentException("Unknown codePoint " + codePoint);
89
            }
90
        }
91
        return onlyP ? Sep.PARAGRAPH : res;
92
    }
93
 
94
    private final ODDocument doc;
95
    private final OOXML xml;
96
    private final LinkedList<String> lines;
97
    private final LinkedList<Sep> separators;
98
 
99
    public Lines(final ODDocument doc, final String text) {
100
        super();
101
        this.doc = doc;
180 ilm 102
        this.xml = doc.getFormatVersion().getXML();
83 ilm 103
        this.lines = new LinkedList<String>();
104
        this.separators = new LinkedList<Sep>();
105
        this.parse(text, isCalc());
106
    }
107
 
108
    private final boolean isCalc() {
109
        return this.doc instanceof SpreadSheet;
110
    }
111
 
112
    // building
113
 
114
    private void parse(final String value, final boolean onlyP) {
115
        final Matcher matcher = SEP_PATTERN.matcher(value);
116
        int i = 0;
117
        while (matcher.find()) {
118
            this.add(value.substring(i, matcher.start()), getSep(matcher.group(), onlyP));
119
            i = matcher.end();
120
        }
121
        this.addLast(value.substring(i));
122
    }
123
 
124
    private void add(final String s, final Sep sep) {
125
        if (sep == null)
126
            throw new NullPointerException("Null separator");
127
        addLine(s);
128
        this.separators.add(sep);
129
    }
130
 
131
    private final void addLine(final String s) {
132
        if (s == null)
133
            throw new NullPointerException("Null string");
134
        this.lines.add(s);
135
    }
136
 
137
    private void addLast(final String s) {
138
        this.addLine(s);
139
        checkLineFirst(true, "Size mismatch");
140
    }
141
 
142
    // checking
143
 
144
    private void checkLineFirst(final boolean b, final String msg) {
145
        if (!checkLineFirst(b, false))
146
            throw new IllegalArgumentException(msg);
147
    }
148
 
149
    public final boolean checkLineFirst(final boolean b) {
150
        return checkLineFirst(b, true);
151
    }
152
 
153
    public final boolean checkLineFirst(final boolean b, final boolean allowEmpty) {
154
        final int linesSize = this.lines.size();
155
        final int sepSize = this.separators.size();
156
        if (linesSize == 0 && sepSize == 0)
157
            return allowEmpty;
158
 
159
        final boolean lineFirst;
160
        if (linesSize == sepSize + 1)
161
            lineFirst = true;
162
        else if (linesSize == sepSize)
163
            lineFirst = false;
164
        else
165
            throw new IllegalArgumentException("Size problem");
166
        return lineFirst == b;
167
    }
168
 
169
    // consuming
170
 
171
    public Sep peekSep() {
172
        return this.separators.peekFirst();
173
    }
174
 
175
    public String peekLine() {
176
        return this.lines.peekFirst();
177
    }
178
 
179
    public boolean allConsumed() {
180
        return peekLine() == null;
181
    }
182
 
183
    public List<Content> consume() {
184
        return this.consume(false);
185
    }
186
 
187
    public List<Content> consume(final boolean noSep) {
188
        if (!noSep) {
189
            this.separators.removeFirst();
190
        } else {
191
            checkLineFirst(true, "Already separator first");
192
        }
193
        final String res = this.lines.removeFirst();
194
        return this.xml.encodeWSasList(res);
195
    }
196
 
197
    public void consumeSep() {
198
        checkLineFirst(false, "Not separator first");
199
        this.separators.removeFirst();
200
    }
201
 
202
    // TODO add Integer startLine and Integer endLine parameters to only replace a subset of the
203
    // current text (null could mean after the end and negative could use TextNode.getLinesCount()).
204
    // This would allow to easily change the content of a text document.
205
    public final void setText(final Element elem, final boolean textMode) {
206
        if (this.doc.getPackage().getXMLFile(elem.getDocument()) == null)
207
            throw new IllegalArgumentException("Element not in document");
208
        final boolean isCalc = this.isCalc();
209
        final XMLFormatVersion vers = this.xml.getFormatVersion();
210
        if (!this.checkLineFirst(true))
211
            throw new IllegalStateException("Lines invalid");
212
 
213
        final Element tabElem = this.xml.getTab();
214
        final Element newLineElem = this.xml.getLineBreak();
215
        final Element spacesElem = this.xml.createSpaces(1);
216
 
217
        final Filter noNLTextFilter = new Filter() {
218
            @Override
219
            public boolean matches(Object obj) {
220
                if (obj instanceof Element) {
221
                    final Element elem = (Element) obj;
222
                    return JDOMUtils.equals(elem, tabElem) || JDOMUtils.equals(elem, spacesElem);
223
                } else {
224
                    return obj instanceof Text;
225
                }
226
            }
227
        };
228
        final Filter nlFilter = new ElementFilter(newLineElem.getName(), newLineElem.getNamespace());
229
        // reuse text:p to keep style
230
        final Filter pFilter = TextNodeDesc.get(Paragraph.class).getFilter(vers).or(TextNodeDesc.get(Heading.class).getFilter(vers));
231
        @SuppressWarnings("unchecked")
232
        final Iterator<Element> pChildren = new ArrayList<Element>(elem.getContent(pFilter)).iterator();
233
        while (pChildren.hasNext() && !this.allConsumed()) {
234
            final Element pElem = pChildren.next();
235
            // to keep it simple remove all text:span except if there's one for the whole text :
236
            // allow cells in spreadsheet to keep their character style.
237
            final Element wholeSpan = TextNode.getWholeSpan(pElem, vers, textMode);
238
            final Element wholeText = wholeSpan == null ? pElem : wholeSpan;
239
            flattenSpans(wholeText, vers);
240
 
241
            int j = 0;
242
            int size = wholeText.getContentSize();
243
            while (j < size) {
244
                final Content c = wholeText.getContent(j);
245
                if (noNLTextFilter.matches(c)) {
246
                    // remove current text
247
                    c.detach();
248
                    size--;
249
                } else if (nlFilter.matches(c)) {
250
                    // re-use line-break if allowed
251
                    if (this.peekSep() == Sep.LINE || this.peekSep() == Sep.ANY) {
252
                        // add before line-break
253
                        wholeText.addContent(j, this.consume());
254
                        size++;
255
                        // jump after line-break
256
                        j += 2;
257
                    } else {
258
                        // if not allowed (or we're at the last line) remove line-break
259
                        c.detach();
260
                        size--;
261
                    }
262
                } else {
263
                    // content that doesn't encode text
264
                    j++;
265
                }
266
            }
267
            // since we only consumed in the above loop when there was a next separator
268
            assert !this.allConsumed();
269
            wholeText.addContent(this.consume(true));
270
            // *** ATTN sep first
271
            assert this.checkLineFirst(false);
272
 
273
            // create requested new lines
274
            while (this.peekSep() == Sep.LINE) {
275
                wholeText.addContent((Content) newLineElem.clone());
276
                wholeText.addContent(this.consume());
277
            }
278
 
279
            // avoid creating paragraphs
280
            if (!isCalc && !this.allConsumed() && !pChildren.hasNext()) {
281
                while (this.peekSep() == Sep.LINE || this.peekSep() == Sep.ANY) {
282
                    wholeText.addContent((Content) newLineElem.clone());
283
                    wholeText.addContent(this.consume());
284
                }
285
            }
286
            assert this.peekSep() != Sep.LINE;
287
            if (!this.allConsumed()) {
288
                this.consumeSep();
289
            }
290
            // *** ATTN string first
291
            assert this.checkLineFirst(true);
292
        }
293
        assert this.checkLineFirst(true);
294
        // remove extra paragraphs
295
        while (pChildren.hasNext()) {
296
            pChildren.next().detach();
297
        }
298
        // create needed paragraphs
299
        Element pElem = null;
300
        while (!this.allConsumed()) {
301
            final boolean firstLoop = pElem == null;
302
            // except for the first loop (still string first) there's always a separator if
303
            // there's a line
304
            assert firstLoop || this.peekSep() != null;
305
            if (firstLoop || this.peekSep() == Sep.PARAGRAPH) {
306
                pElem = Paragraph.createEmpty(vers);
307
                // switch to sep first in the first loop
308
                pElem.setContent(this.consume(firstLoop));
309
                elem.addContent(pElem);
310
            } else {
311
                pElem.addContent((Content) newLineElem.clone());
312
                pElem.addContent(this.consume());
313
            }
314
        }
315
    }
316
}