OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 73 | Rev 180 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 73 Rev 83
Line 12... Line 12...
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.openoffice.text;
14
 package org.openconcerto.openoffice.text;
15
 
15
 
16
import org.openconcerto.openoffice.ODDocument;
16
import org.openconcerto.openoffice.ODDocument;
-
 
17
import org.openconcerto.openoffice.ODNodeDesc.Children;
-
 
18
import org.openconcerto.openoffice.ODPackage;
17
import org.openconcerto.openoffice.OOXML;
19
import org.openconcerto.openoffice.OOXML;
18
import org.openconcerto.openoffice.StyleStyle;
20
import org.openconcerto.openoffice.Style;
-
 
21
import org.openconcerto.openoffice.Style.ResolveResult;
19
import org.openconcerto.openoffice.StyledNode;
22
import org.openconcerto.openoffice.StyledNode;
20
import org.openconcerto.openoffice.XMLFormatVersion;
23
import org.openconcerto.openoffice.XMLFormatVersion;
21
import org.openconcerto.openoffice.spreadsheet.Cell;
24
import org.openconcerto.openoffice.spreadsheet.Cell;
22
import org.openconcerto.utils.CollectionUtils;
25
import org.openconcerto.utils.CollectionUtils;
23
import org.openconcerto.utils.cc.IPredicate;
26
import org.openconcerto.utils.cc.IPredicate;
24
import org.openconcerto.xml.DescendantIterator;
27
import org.openconcerto.xml.DescendantIterator;
25
import org.openconcerto.xml.JDOMUtils;
28
import org.openconcerto.xml.JDOMUtils;
-
 
29
import org.openconcerto.xml.SimpleXMLPath;
26
 
30
 
27
import java.util.ArrayList;
31
import java.util.ArrayList;
28
import java.util.Arrays;
32
import java.util.Arrays;
29
import java.util.Iterator;
33
import java.util.Iterator;
30
import java.util.List;
34
import java.util.List;
31
import java.util.regex.Pattern;
35
import java.util.regex.Pattern;
32
 
36
 
-
 
37
import org.jdom.Attribute;
33
import org.jdom.Content;
38
import org.jdom.Content;
34
import org.jdom.Element;
39
import org.jdom.Element;
35
import org.jdom.Namespace;
40
import org.jdom.Namespace;
36
import org.jdom.Text;
41
import org.jdom.Text;
37
 
42
 
Line 40... Line 45...
40
 * 
45
 * 
41
 * @author Sylvain CUAZ
46
 * @author Sylvain CUAZ
42
 * 
47
 * 
43
 * @param <S> type of style.
48
 * @param <S> type of style.
44
 */
49
 */
45
public abstract class TextNode<S extends StyleStyle> extends StyledNode<S, TextDocument> {
50
public abstract class TextNode<S extends TextStyle> extends StyledNode<S, ODDocument> {
46
 
51
 
47
    // see §6.1.2 White Space Characters of OpenDocument v1.2
52
    // see §6.1.2 White Space Characters of OpenDocument v1.2
48
    private static final Pattern multiSpacePattern = Pattern.compile("[\t\r\n ]+");
53
    private static final Pattern multiSpacePattern = Pattern.compile("[\t\r\n ]+");
-
 
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';
49
 
64
 
50
    static public String getChildrenCharacterContent(final Element parentElem, final XMLFormatVersion vers, final boolean ooMode) {
65
    static public String getChildrenCharacterContent(final Element parentElem, final XMLFormatVersion vers, final boolean ooMode) {
-
 
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) {
51
        final List<String> ps = new ArrayList<String>();
88
        final List<String> ps = new ArrayList<String>();
52
        for (final Object o : parentElem.getChildren()) {
89
        for (final Object o : parentElem.getChildren()) {
53
            final Element child = (Element) o;
90
            final Element child = (Element) o;
54
            if ((child.getName().equals("p") || child.getName().equals("h")) && child.getNamespacePrefix().equals("text")) {
91
            if ((child.getName().equals("p") || child.getName().equals("h")) && child.getNamespacePrefix().equals("text")) {
-
 
92
                @SuppressWarnings("unchecked")
-
 
93
                final List<Content> content = child.getContent();
55
                ps.add(getCharacterContent(child, vers, ooMode));
94
                ps.add(getCharacterContent(content, vers, ooMode, useSeparator, option));
56
            }
95
            }
57
        }
96
        }
-
 
97
        return ps;
-
 
98
    }
-
 
99
 
-
 
100
    /**
-
 
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;
58
        return CollectionUtils.join(ps, "\n");
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;
59
    }
117
    }
60
 
118
 
61
    /**
119
    /**
62
     * Return the text value of the passed element. This method doesn't just return the XML text
120
     * Return the text value of the passed element. This method doesn't just return the XML text
63
     * content, it also parses XML elements (like paragraphs, tabs and line-breaks). For the
121
     * content, it also parses XML elements (like paragraphs, tabs and line-breaks). For the
Line 69... Line 127...
69
     * @param vers the version of the element.
127
     * @param vers the version of the element.
70
     * @param ooMode whether to use the OO way or the standard way.
128
     * @param ooMode whether to use the OO way or the standard way.
71
     * @return the parsed text value.
129
     * @return the parsed text value.
72
     */
130
     */
73
    static public final String getCharacterContent(final Element pElem, final XMLFormatVersion vers, final boolean ooMode) {
131
    static public final String getCharacterContent(final Element pElem, final XMLFormatVersion vers, final boolean ooMode) {
-
 
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 "";
74
        final OOXML xml = OOXML.get(vers, false);
161
        final OOXML xml = OOXML.get(vers, false);
75
        if (!xml.getVersion().getTEXT().equals(pElem.getNamespace()))
-
 
76
            throw new IllegalArgumentException("element isn't of version " + vers);
-
 
77
 
162
 
78
        final StringBuilder sb = new StringBuilder();
163
        final StringBuilder sb = new StringBuilder();
79
        final Namespace textNS = pElem.getNamespace();
164
        final Namespace textNS = xml.getVersion().getTEXT();
80
        final Element tabElem = xml.getTab();
165
        final Element tabElem = xml.getTab();
81
        final Element newLineElem = xml.getLineBreak();
166
        final Element newLineElem = xml.getLineBreak();
82
        // true if the string ends with a space that wasn't expanded from an XML element (e.g.
167
        // true if the string ends with a space that wasn't expanded from an XML element (e.g.
83
        // <tab/> or <text:s/>)
168
        // <tab/> or <text:s/>)
84
        boolean spaceSuffix = false;
169
        boolean spaceSuffix = false;
Line 92... Line 177...
92
                return true;
177
                return true;
93
            }
178
            }
94
        });
179
        });
95
        while (iter.hasNext()) {
180
        while (iter.hasNext()) {
96
            final Object o = iter.next();
181
            final Object o = iter.next();
-
 
182
            if (option == Option.ONLY_SEP) {
-
 
183
                if (o instanceof Element && JDOMUtils.equals((Element) o, newLineElem)) {
-
 
184
                    sb.append(useSeparator ? LINE_SEPARATOR_CHAR : '\n');
-
 
185
                }
-
 
186
            } else {
97
            if (o instanceof Text) {
187
                if (o instanceof Text) {
98
                final String text = multiSpacePattern.matcher(((Text) o).getText()).replaceAll(" ");
188
                    final String text = multiSpacePattern.matcher(((Text) o).getText()).replaceAll(" ");
99
                // trim leading
189
                    // trim leading
100
                if (!ooMode && text.startsWith(" ") && (spaceSuffix || sb.length() == 0))
190
                    if (!ooMode && text.startsWith(" ") && (spaceSuffix || sb.length() == 0))
101
                    sb.append(text.substring(1));
191
                        sb.append(text.substring(1));
Line 104... Line 194...
104
                spaceSuffix = text.endsWith(" ");
194
                    spaceSuffix = text.endsWith(" ");
105
            } else if (o instanceof Element) {
195
                } else if (o instanceof Element) {
106
                // perhaps handle conditions (conditional-text, hiddenparagraph, hidden-text)
196
                    // perhaps handle conditions (conditional-text, hiddenparagraph, hidden-text)
107
                final Element elem = (Element) o;
197
                    final Element elem = (Element) o;
108
                if (JDOMUtils.equals(elem, tabElem)) {
198
                    if (JDOMUtils.equals(elem, tabElem)) {
109
                    sb.append("\t");
199
                        sb.append('\t');
110
                } else if (JDOMUtils.equals(elem, newLineElem)) {
200
                    } else if (JDOMUtils.equals(elem, newLineElem)) {
111
                    sb.append("\n");
201
                        sb.append(useSeparator ? LINE_SEPARATOR_CHAR : '\n');
112
                } else if (elem.getName().equals("s") && elem.getNamespace().equals(textNS)) {
202
                    } else if (elem.getName().equals("s") && elem.getNamespace().equals(textNS)) {
113
                    final int count = Integer.valueOf(elem.getAttributeValue("c", textNS, "1"));
203
                        final int count = Integer.valueOf(elem.getAttributeValue("c", textNS, "1"));
114
                    final char[] toAdd = new char[count];
204
                        final char[] toAdd = new char[count];
115
                    Arrays.fill(toAdd, ' ');
205
                        Arrays.fill(toAdd, ' ');
116
                    sb.append(toAdd);
206
                        sb.append(toAdd);
117
                }
207
                    }
118
            }
208
                }
119
        }
209
            }
-
 
210
            if (option == Option.STOP_AT_FIRST_CHAR && sb.length() > 0)
-
 
211
                return sb.toString();
-
 
212
        }
120
        // trim trailing
213
        // trim trailing
121
        if (!ooMode && spaceSuffix)
214
        if (option != Option.ONLY_SEP && !ooMode && spaceSuffix)
122
            sb.deleteCharAt(sb.length() - 1);
215
            sb.deleteCharAt(sb.length() - 1);
123
 
216
 
124
        return sb.toString();
217
        return sb.toString();
125
    }
218
    }
126
 
219
 
-
 
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;
-
 
229
 
-
 
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;
-
 
235
    }
-
 
236
 
-
 
237
    private final XMLFormatVersion version;
127
    protected TextDocument parent;
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
    }
128
 
244
 
129
    public TextNode(Element local, final Class<S> styleClass) {
245
    protected TextNode(final Element local, final Class<S> styleClass, final ODDocument parent) {
130
        this(local, styleClass, null);
246
        this(local, styleClass, null, parent);
131
    }
247
    }
132
 
248
 
133
    protected TextNode(Element local, final Class<S> styleClass, final TextDocument parent) {
249
    private TextNode(final Element local, final Class<S> styleClass, final XMLFormatVersion vers, final ODDocument parent) {
134
        super(local, styleClass);
250
        super(local, styleClass);
-
 
251
        this.version = vers == null ? parent.getFormatVersion() : vers;
-
 
252
        if (this.version == null)
-
 
253
            throw new NullPointerException("No version");
135
        this.parent = parent;
254
        this.setDocument(parent);
-
 
255
    }
-
 
256
 
-
 
257
    public final XMLFormatVersion getVersion() {
-
 
258
        return this.version;
136
    }
259
    }
137
 
260
 
138
    @Override
261
    @Override
139
    public final TextDocument getODDocument() {
262
    public final ODDocument getODDocument() {
140
        return this.parent;
263
        return this.parent;
141
    }
264
    }
142
 
265
 
-
 
266
    public final void detach() {
-
 
267
        this.setDocument(null);
-
 
268
    }
-
 
269
 
143
    public final void setDocument(TextDocument doc) {
270
    private final void setDocument(ODDocument doc) {
144
        if (doc != this.parent) {
271
        if (doc != this.parent) {
145
            if (doc == null) {
272
            if (doc == null) {
146
                this.parent = null;
-
 
147
                this.getElement().detach();
273
                this.getElement().detach();
-
 
274
                this.parent = null;
148
            } else if (doc.getContentDocument() != this.getElement().getDocument()) {
275
            } else if (doc.getPackage().getXMLFile(this.getElement().getDocument()) == null) {
149
                doc.add(this);
276
                throw new IllegalArgumentException("Not already in the passed document");
150
            } else {
277
            } else {
151
                this.checkDocument(doc);
-
 
152
                this.parent = doc;
278
                this.parent = doc;
153
            }
279
            }
154
        }
280
        }
155
    }
281
    }
156
 
282
 
-
 
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
    }
-
 
295
 
157
    protected abstract void checkDocument(ODDocument doc);
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
    }
158
 
330
 
159
    public final String getCharacterContent() {
331
    public final String getCharacterContent() {
160
        return this.getCharacterContent(Cell.getTextValueMode());
332
        return this.getCharacterContent(Cell.getTextValueMode());
161
    }
333
    }
162
 
334
 
163
    public final String getCharacterContent(final boolean ooMode) {
335
    public final String getCharacterContent(final boolean ooMode) {
164
        // TODO add format version field to this class (e.g. required to add a tab to a paragraph)
336
        return getCharacterContent(this.getElement(), getVersion(), ooMode);
-
 
337
    }
-
 
338
 
165
        if (getODDocument() == null)
339
    public final Children<Span> getSpans() {
-
 
340
        final TextNodeDesc<Span> nodeDesc = TextNodeDesc.get(Span.class);
166
            throw new IllegalStateException("Unknown format version");
341
        // perhaps Children should get the document dynamically (since it can change)
-
 
342
        final ODDocument doc = this.getODDocument();
167
        return getCharacterContent(this.getElement(), getODDocument().getFormatVersion(), ooMode);
343
        return doc == null ? nodeDesc.getChildren(getVersion(), getElement()) : nodeDesc.getChildren(doc, getElement());
168
    }
344
    }
169
}
345
}