OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 83 | 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.text;
15
 
25 ilm 16
import org.openconcerto.openoffice.ODDocument;
83 ilm 17
import org.openconcerto.openoffice.ODNodeDesc.Children;
18
import org.openconcerto.openoffice.ODPackage;
73 ilm 19
import org.openconcerto.openoffice.OOXML;
83 ilm 20
import org.openconcerto.openoffice.Style;
21
import org.openconcerto.openoffice.Style.ResolveResult;
17 ilm 22
import org.openconcerto.openoffice.StyledNode;
73 ilm 23
import org.openconcerto.openoffice.XMLFormatVersion;
24
import org.openconcerto.openoffice.spreadsheet.Cell;
25
import org.openconcerto.utils.CollectionUtils;
26
import org.openconcerto.utils.cc.IPredicate;
27
import org.openconcerto.xml.DescendantIterator;
28
import org.openconcerto.xml.JDOMUtils;
83 ilm 29
import org.openconcerto.xml.SimpleXMLPath;
17 ilm 30
 
73 ilm 31
import java.util.ArrayList;
32
import java.util.Arrays;
33
import java.util.Iterator;
34
import java.util.List;
35
import java.util.regex.Pattern;
36
 
83 ilm 37
import org.jdom.Attribute;
73 ilm 38
import org.jdom.Content;
17 ilm 39
import org.jdom.Element;
73 ilm 40
import org.jdom.Namespace;
41
import org.jdom.Text;
17 ilm 42
 
43
/**
44
 * A text node that can be created ex nihilo. Ie without a document at first.
45
 *
46
 * @author Sylvain CUAZ
47
 *
48
 * @param <S> type of style.
49
 */
83 ilm 50
public abstract class TextNode<S extends TextStyle> extends StyledNode<S, ODDocument> {
17 ilm 51
 
73 ilm 52
    // see §6.1.2 White Space Characters of OpenDocument v1.2
53
    private static final Pattern multiSpacePattern = Pattern.compile("[\t\r\n ]+");
83 ilm 54
    public static final String LINE_SEPARATOR = "\u2028";
55
    private static final char LINE_SEPARATOR_CHAR = LINE_SEPARATOR.charAt(0);
56
    public static final String PARAGRAPH_SEPARATOR = "\u2029";
57
    /**
58
     * Used by Microsoft Word as a line separator.
59
     *
60
     * @see <a href="http://support.microsoft.com/kb/59096/en-us">Microsoft</a>
61
     * @see <a href="http://unicode.org/reports/tr13/tr13-9.html">UNICODE NEWLINE GUIDELINES</a>
62
     */
63
    public static final char VERTICAL_TAB_CHAR = '\u000B';
73 ilm 64
 
65
    static public String getChildrenCharacterContent(final Element parentElem, final XMLFormatVersion vers, final boolean ooMode) {
83 ilm 66
        return getChildrenCharacterContent(parentElem, vers, ooMode, false);
67
    }
68
 
69
    /**
70
     * Return the text value of the passed element.
71
     *
72
     * @param parentElem an element containing paragraphs.
73
     * @param vers the version of the element.
74
     * @param ooMode whether to use the OO way or the standard way.
75
     * @param useSeparator if <code>true</code> line-breaks are returned as {@value #LINE_SEPARATOR}
76
     *        and paragraphs as {@value #PARAGRAPH_SEPARATOR}, if <code>false</code> only
77
     *        <code>'\n'</code> is used.
78
     * @return the parsed text value.
79
     * @see #getCharacterContent(Element, XMLFormatVersion, boolean, boolean)
80
     */
81
    static public String getChildrenCharacterContent(final Element parentElem, final XMLFormatVersion vers, final boolean ooMode, final boolean useSeparator) {
82
        final List<String> ps = getChildrenCharacterContent(parentElem, vers, ooMode, useSeparator, null);
83
        return CollectionUtils.join(ps, useSeparator ? PARAGRAPH_SEPARATOR : "\n");
84
 
85
    }
86
 
87
    static private List<String> getChildrenCharacterContent(final Element parentElem, final XMLFormatVersion vers, final boolean ooMode, final boolean useSeparator, final Option option) {
73 ilm 88
        final List<String> ps = new ArrayList<String>();
89
        for (final Object o : parentElem.getChildren()) {
90
            final Element child = (Element) o;
91
            if ((child.getName().equals("p") || child.getName().equals("h")) && child.getNamespacePrefix().equals("text")) {
83 ilm 92
                @SuppressWarnings("unchecked")
93
                final List<Content> content = child.getContent();
94
                ps.add(getCharacterContent(content, vers, ooMode, useSeparator, option));
73 ilm 95
            }
96
        }
83 ilm 97
        return ps;
73 ilm 98
    }
99
 
100
    /**
83 ilm 101
     * Get the number of lines in the passed element.
102
     *
103
     * @param parentElem an element containing paragraphs.
104
     * @param vers the version of the element.
105
     * @param ooMode whether to use the OO way or the standard way.
106
     * @return 0 if the element contains no paragraphs, otherwise the number of paragraphs and line
107
     *         breaks.
108
     */
109
    static public int getLinesCount(final Element parentElem, final XMLFormatVersion vers, final boolean ooMode) {
110
        final List<String> ps = getChildrenCharacterContent(parentElem, vers, ooMode, false, Option.ONLY_SEP);
111
        int res = 0;
112
        for (final String p : ps) {
113
            // one line for the paragraph plus one for each line break
114
            res += 1 + p.length();
115
        }
116
        return res;
117
    }
118
 
119
    /**
73 ilm 120
     * Return the text value of the passed element. This method doesn't just return the XML text
121
     * content, it also parses XML elements (like paragraphs, tabs and line-breaks). For the
122
     * differences between the OO way (as of 3.1) and the OpenDocument way see section 5.1.1
123
     * White-space Characters of OpenDocument-v1.0-os and §6.1.2 of OpenDocument-v1.2-part1. In
124
     * essence OpenOffice never trim strings.
125
     *
126
     * @param pElem a text element, e.g. text:p or text:h.
127
     * @param vers the version of the element.
128
     * @param ooMode whether to use the OO way or the standard way.
129
     * @return the parsed text value.
130
     */
131
    static public final String getCharacterContent(final Element pElem, final XMLFormatVersion vers, final boolean ooMode) {
83 ilm 132
        return getCharacterContent(pElem, vers, ooMode, false);
133
    }
134
 
135
    /**
136
     * Return the text value of the passed element. This method doesn't just return the XML text
137
     * content, it also parses XML elements (like paragraphs, tabs and line-breaks). For the
138
     * differences between the OO way (as of 3.1) and the OpenDocument way see section 5.1.1
139
     * White-space Characters of OpenDocument-v1.0-os and §6.1.2 of OpenDocument-v1.2-part1. In
140
     * essence OpenOffice never trim strings.
141
     *
142
     * @param pElem a text element, e.g. text:p or text:h.
143
     * @param vers the version of the element.
144
     * @param ooMode whether to use the OO way or the standard way.
145
     * @param useSeparator if <code>true</code> line-breaks are returned as {@value #LINE_SEPARATOR}
146
     *        otherwise <code>'\n'</code> is used.
147
     * @return the parsed text value.
148
     */
149
    @SuppressWarnings("unchecked")
150
    static public final String getCharacterContent(final Element pElem, final XMLFormatVersion vers, final boolean ooMode, final boolean useSeparator) {
151
        return getCharacterContent(pElem.getContent(), vers, ooMode, useSeparator, null);
152
    }
153
 
154
    private static enum Option {
155
        STOP_AT_FIRST_CHAR, ONLY_SEP
156
    }
157
 
158
    static private final String getCharacterContent(final List<Content> pElem, final XMLFormatVersion vers, final boolean ooMode, final boolean useSeparator, final Option option) {
159
        if (pElem.isEmpty())
160
            return "";
180 ilm 161
        final OOXML xml = vers.getXML();
73 ilm 162
 
163
        final StringBuilder sb = new StringBuilder();
83 ilm 164
        final Namespace textNS = xml.getVersion().getTEXT();
73 ilm 165
        final Element tabElem = xml.getTab();
166
        final Element newLineElem = xml.getLineBreak();
167
        // true if the string ends with a space that wasn't expanded from an XML element (e.g.
168
        // <tab/> or <text:s/>)
169
        boolean spaceSuffix = false;
170
        final Iterator<?> iter = new DescendantIterator(pElem, new IPredicate<Content>() {
171
            @Override
172
            public boolean evaluateChecked(Content input) {
173
                if (input instanceof Element) {
174
                    // don't descend into frames, graphical shapes...
175
                    return !((Element) input).getNamespace().getPrefix().equals("draw");
176
                }
177
                return true;
178
            }
179
        });
180
        while (iter.hasNext()) {
181
            final Object o = iter.next();
83 ilm 182
            if (option == Option.ONLY_SEP) {
183
                if (o instanceof Element && JDOMUtils.equals((Element) o, newLineElem)) {
184
                    sb.append(useSeparator ? LINE_SEPARATOR_CHAR : '\n');
73 ilm 185
                }
83 ilm 186
            } else {
187
                if (o instanceof Text) {
188
                    final String text = multiSpacePattern.matcher(((Text) o).getText()).replaceAll(" ");
189
                    // trim leading
190
                    if (!ooMode && text.startsWith(" ") && (spaceSuffix || sb.length() == 0))
191
                        sb.append(text.substring(1));
192
                    else
193
                        sb.append(text);
194
                    spaceSuffix = text.endsWith(" ");
195
                } else if (o instanceof Element) {
196
                    // perhaps handle conditions (conditional-text, hiddenparagraph, hidden-text)
197
                    final Element elem = (Element) o;
198
                    if (JDOMUtils.equals(elem, tabElem)) {
199
                        sb.append('\t');
200
                    } else if (JDOMUtils.equals(elem, newLineElem)) {
201
                        sb.append(useSeparator ? LINE_SEPARATOR_CHAR : '\n');
202
                    } else if (elem.getName().equals("s") && elem.getNamespace().equals(textNS)) {
203
                        final int count = Integer.valueOf(elem.getAttributeValue("c", textNS, "1"));
204
                        final char[] toAdd = new char[count];
205
                        Arrays.fill(toAdd, ' ');
206
                        sb.append(toAdd);
207
                    }
208
                }
73 ilm 209
            }
83 ilm 210
            if (option == Option.STOP_AT_FIRST_CHAR && sb.length() > 0)
211
                return sb.toString();
73 ilm 212
        }
213
        // trim trailing
83 ilm 214
        if (option != Option.ONLY_SEP && !ooMode && spaceSuffix)
73 ilm 215
            sb.deleteCharAt(sb.length() - 1);
216
 
217
        return sb.toString();
218
    }
219
 
83 ilm 220
    // return the one and only <span> that contains the whole text, null otherwise.
221
    @SuppressWarnings("unchecked")
222
    static public final Element getWholeSpan(final Element pElem, final XMLFormatVersion vers, final boolean ooMode) {
223
        final Iterator<Element> spanIter = pElem.getContent(TextNodeDesc.get(Span.class).getFilter(vers)).iterator();
224
        if (!spanIter.hasNext())
225
            return null;
226
        final Element first = spanIter.next();
227
        if (spanIter.hasNext())
228
            return null;
17 ilm 229
 
83 ilm 230
        final int index = pElem.indexOf(first);
231
        if (getCharacterContent(pElem.getContent().subList(0, index), vers, ooMode, false, Option.STOP_AT_FIRST_CHAR).length() > 0
232
                || getCharacterContent(pElem.getContent().subList(index + 1, pElem.getContentSize()), vers, ooMode, false, Option.STOP_AT_FIRST_CHAR).length() > 0)
233
            return null;
234
        return first;
25 ilm 235
    }
236
 
83 ilm 237
    private final XMLFormatVersion version;
238
    protected ODDocument parent;
239
 
240
    // not public since, local element cannot be checked against vers
241
    protected TextNode(final Element local, final Class<S> styleClass, final XMLFormatVersion vers) {
242
        this(local, styleClass, vers, null);
243
    }
244
 
245
    protected TextNode(final Element local, final Class<S> styleClass, final ODDocument parent) {
246
        this(local, styleClass, null, parent);
247
    }
248
 
249
    private TextNode(final Element local, final Class<S> styleClass, final XMLFormatVersion vers, final ODDocument parent) {
17 ilm 250
        super(local, styleClass);
83 ilm 251
        this.version = vers == null ? parent.getFormatVersion() : vers;
252
        if (this.version == null)
253
            throw new NullPointerException("No version");
254
        this.setDocument(parent);
17 ilm 255
    }
256
 
83 ilm 257
    public final XMLFormatVersion getVersion() {
258
        return this.version;
259
    }
260
 
17 ilm 261
    @Override
83 ilm 262
    public final ODDocument getODDocument() {
17 ilm 263
        return this.parent;
264
    }
265
 
83 ilm 266
    public final void detach() {
267
        this.setDocument(null);
268
    }
269
 
270
    private final void setDocument(ODDocument doc) {
17 ilm 271
        if (doc != this.parent) {
272
            if (doc == null) {
83 ilm 273
                this.getElement().detach();
17 ilm 274
                this.parent = null;
83 ilm 275
            } else if (doc.getPackage().getXMLFile(this.getElement().getDocument()) == null) {
276
                throw new IllegalArgumentException("Not already in the passed document");
25 ilm 277
            } else {
17 ilm 278
                this.parent = doc;
279
            }
280
        }
281
    }
282
 
83 ilm 283
    public final void addToDocument(ODDocument doc, Element where, int index) {
284
        if (doc == null) {
285
            this.detach();
286
        } else {
287
            this.checkDocument(doc.getPackage(), where);
288
            if (index < 0)
289
                where.addContent(this.getElement());
290
            else
291
                where.addContent(index, this.getElement());
292
            this.setDocument(doc);
293
        }
294
    }
73 ilm 295
 
83 ilm 296
    protected final void checkDocument(final ODPackage pkg, final Element where) {
297
        if (!pkg.getFormatVersion().equals(this.getVersion()))
298
            throw new IllegalArgumentException("Version mismatch : " + this.getVersion() + " != " + pkg.getFormatVersion());
299
        if (pkg.getXMLFile(where.getDocument()) == null)
300
            throw new IllegalArgumentException("Where element not in the passed package");
301
        if (this.getStyleName() != null && getStyle(pkg, where.getDocument()) == null)
302
            throw new IllegalArgumentException("unknown style " + getStyleName() + " in " + pkg);
303
        for (final Attribute attr : SimpleXMLPath.allAttributes().selectNodes(getElement())) {
304
            if (Style.resolveReference(pkg, where.getDocument(), attr) == ResolveResult.NOT_RESOLVED)
305
                throw new IllegalArgumentException(this + " is using an undefined style : " + attr);
306
        }
307
    }
308
 
309
    public final void addTab() {
310
        this.getElement().addContent(OOXML.get(this.getVersion()).getTab());
311
    }
312
 
313
    public final void addContent(String text) {
314
        this.getElement().addContent(OOXML.get(this.getVersion()).encodeWSasList(text));
315
    }
316
 
317
    public final Span addStyledContent(String text, String styleName) {
318
        final Element elem = Span.createEmpty(getVersion());
319
        getElement().addContent(elem);
320
        final Span res = createSpan(elem);
321
        res.addContent(text);
322
        res.setStyleName(styleName);
323
        return res;
324
    }
325
 
326
    private Span createSpan(final Element elem) {
327
        final ODDocument doc = this.getODDocument();
328
        return doc == null ? TextNodeDesc.get(Span.class).wrapNode(getVersion(), elem) : TextNodeDesc.get(Span.class).wrapNode(doc, elem);
329
    }
330
 
73 ilm 331
    public final String getCharacterContent() {
332
        return this.getCharacterContent(Cell.getTextValueMode());
333
    }
334
 
335
    public final String getCharacterContent(final boolean ooMode) {
83 ilm 336
        return getCharacterContent(this.getElement(), getVersion(), ooMode);
73 ilm 337
    }
83 ilm 338
 
339
    public final Children<Span> getSpans() {
340
        final TextNodeDesc<Span> nodeDesc = TextNodeDesc.get(Span.class);
341
        // perhaps Children should get the document dynamically (since it can change)
342
        final ODDocument doc = this.getODDocument();
343
        return doc == null ? nodeDesc.getChildren(getVersion(), getElement()) : nodeDesc.getChildren(doc, getElement());
344
    }
17 ilm 345
}