OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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