OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 93 | Rev 182 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 93 Rev 180
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
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
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
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.
9
 * language governing permissions and limitations under the License.
10
 * 
10
 * 
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 /*
14
 /*
15
 * Créé le 28 oct. 2004
15
 * Créé le 28 oct. 2004
16
 */
16
 */
17
package org.openconcerto.openoffice;
17
package org.openconcerto.openoffice;
18
 
18
 
19
import org.openconcerto.openoffice.text.Span;
19
import org.openconcerto.openoffice.text.Span;
20
import org.openconcerto.openoffice.text.TextNode;
20
import org.openconcerto.openoffice.text.TextNode;
21
import org.openconcerto.utils.CollectionUtils;
21
import org.openconcerto.utils.CollectionUtils;
22
import org.openconcerto.utils.cc.IPredicate;
22
import org.openconcerto.utils.cc.IPredicate;
23
import org.openconcerto.xml.JDOMUtils;
23
import org.openconcerto.xml.JDOMUtils;
24
import org.openconcerto.xml.Validator;
24
import org.openconcerto.xml.Validator;
25
 
25
 
26
import java.util.ArrayList;
26
import java.util.ArrayList;
27
import java.util.Collections;
27
import java.util.Collections;
28
import java.util.Deque;
28
import java.util.Deque;
29
import java.util.HashMap;
29
import java.util.HashMap;
30
import java.util.Iterator;
30
import java.util.Iterator;
31
import java.util.LinkedList;
31
import java.util.LinkedList;
32
import java.util.List;
32
import java.util.List;
33
import java.util.Map;
33
import java.util.Map;
34
import java.util.Set;
34
import java.util.Set;
35
import java.util.SortedMap;
35
import java.util.SortedMap;
36
import java.util.TreeMap;
36
import java.util.TreeMap;
37
import java.util.regex.Matcher;
37
import java.util.regex.Matcher;
38
import java.util.regex.Pattern;
38
import java.util.regex.Pattern;
39
 
39
 
40
import javax.xml.XMLConstants;
40
import javax.xml.XMLConstants;
41
import javax.xml.validation.Schema;
41
import javax.xml.validation.Schema;
42
import javax.xml.validation.SchemaFactory;
42
import javax.xml.validation.SchemaFactory;
43
 
43
 
44
import net.jcip.annotations.GuardedBy;
-
 
45
import net.jcip.annotations.Immutable;
-
 
46
import net.jcip.annotations.ThreadSafe;
-
 
47
 
-
 
48
import org.jdom.Content;
44
import org.jdom.Content;
49
import org.jdom.DocType;
45
import org.jdom.DocType;
50
import org.jdom.Document;
46
import org.jdom.Document;
51
import org.jdom.Element;
47
import org.jdom.Element;
52
import org.jdom.JDOMException;
48
import org.jdom.JDOMException;
53
import org.jdom.Namespace;
49
import org.jdom.Namespace;
54
import org.jdom.Parent;
50
import org.jdom.Parent;
55
import org.jdom.Text;
51
import org.jdom.Text;
56
import org.jdom.xpath.XPath;
52
import org.jdom.xpath.XPath;
57
import org.xml.sax.SAXException;
53
import org.xml.sax.SAXException;
58
 
54
 
-
 
55
import net.jcip.annotations.GuardedBy;
-
 
56
import net.jcip.annotations.Immutable;
-
 
57
import net.jcip.annotations.ThreadSafe;
-
 
58
 
59
/**
59
/**
60
 * Various bits of OpenDocument XML.
60
 * Various bits of OpenDocument XML.
61
 * 
61
 * 
62
 * @author Sylvain CUAZ
62
 * @author Sylvain CUAZ
63
 * @see #get(XMLFormatVersion)
63
 * @see #get(XMLFormatVersion)
64
 */
64
 */
65
public abstract class OOXML implements Comparable<OOXML> {
65
public abstract class OOXML implements Comparable<OOXML> {
66
 
66
 
67
    /**
67
    /**
68
     * If this system property is set to <code>true</code> then {@link #get(XMLFormatVersion)} will
68
     * If this system property is set to <code>true</code> then {@link #get(XMLFormatVersion)} will
69
     * never return <code>null</code>, allowing to support unknown versions.
69
     * never return <code>null</code>, allowing to support unknown versions.
70
     */
70
     */
71
    public static final String LAST_FOR_UNKNOWN_PROP = OOXML.class.getPackage().getName() + ".lastOOXMLForUnknownVersion";
71
    public static final String LAST_FOR_UNKNOWN_PROP = OOXML.class.getPackage().getName() + ".lastOOXMLForUnknownVersion";
-
 
72
    private static final boolean LAST_FOR_UNKNOWN = Boolean.getBoolean(LAST_FOR_UNKNOWN_PROP);
72
    private static final XML_OO instanceOO = new XML_OO();
73
    private static final XML_OO instanceOO = new XML_OO();
73
    @GuardedBy("OOXML")
74
    @GuardedBy("OOXML")
74
    private static final SortedMap<String, XML_OD> instancesODByDate = new TreeMap<String, XML_OD>();
75
    private static final SortedMap<String, XML_OD> instancesODByDate = new TreeMap<String, XML_OD>();
75
    @GuardedBy("OOXML")
76
    @GuardedBy("OOXML")
76
    private static final Map<String, XML_OD> instancesODByVersion = new HashMap<String, XML_OD>();
77
    private static final Map<String, XML_OD> instancesODByVersion = new HashMap<String, XML_OD>();
77
    private static final List<OOXML> values;
78
    private static final List<OOXML> values;
78
    @GuardedBy("OOXML")
79
    @GuardedBy("OOXML")
79
    private static OOXML defaultInstance;
80
    private static OOXML defaultInstance;
80
    private static final Pattern WHITE_SPACE_TO_ENCODE = Pattern.compile("\n|" + TextNode.VERTICAL_TAB_CHAR + "|\t| {2,}");
81
    private static final Pattern WHITE_SPACE_TO_ENCODE = Pattern.compile("\n|" + TextNode.VERTICAL_TAB_CHAR + "|\t| {2,}");
81
 
82
 
82
    static {
83
    static {
83
        register(new XML_OD_1_0());
84
        register(new XML_OD_1_0());
84
        register(new XML_OD_1_1());
85
        register(new XML_OD_1_1());
85
        register(new XML_OD_1_2());
86
        register(new XML_OD_1_2());
-
 
87
        register(new XML_OD_1_3());
86
 
88
 
87
        final List<OOXML> tmp = new ArrayList<OOXML>(instancesODByDate.size() + 1);
89
        final List<OOXML> tmp = new ArrayList<OOXML>(instancesODByDate.size() + 1);
88
        tmp.add(instanceOO);
90
        tmp.add(instanceOO);
89
        tmp.addAll(instancesODByDate.values());
91
        tmp.addAll(instancesODByDate.values());
90
        values = Collections.unmodifiableList(tmp);
92
        values = Collections.unmodifiableList(tmp);
91
 
93
 
92
        setDefault(getLast());
94
        setDefault(getLast());
93
    }
95
    }
94
 
96
 
95
    private static synchronized void register(XML_OD xml) {
97
    private static synchronized void register(XML_OD xml) {
96
        assert xml.getVersion() == XMLVersion.OD;
98
        assert xml.getVersion() == XMLVersion.OD;
97
        instancesODByDate.put(xml.getDateString(), xml);
99
        instancesODByDate.put(xml.getDateString(), xml);
98
        instancesODByVersion.put(xml.getFormatVersion().getOfficeVersion(), xml);
100
        instancesODByVersion.put(xml.getFormatVersion().getOfficeVersion(), xml);
99
    }
101
    }
100
 
102
 
101
    /**
103
    /**
102
     * Returns the instance that match the requested version.
104
     * Returns the instance that match the requested version.
103
     * 
105
     * 
104
     * @param version the version.
106
     * @param version the version.
105
     * @return the corresponding instance, <code>null</code> for unsupported versions.
107
     * @return the corresponding instance, <code>null</code> for unsupported versions.
106
     * @see #LAST_FOR_UNKNOWN_PROP
108
     * @see #LAST_FOR_UNKNOWN_PROP
107
     */
109
     */
108
    public static OOXML get(XMLFormatVersion version) {
110
    public static OOXML get(XMLFormatVersion version) {
109
        return get(version, Boolean.getBoolean(LAST_FOR_UNKNOWN_PROP));
111
        return get(version, LAST_FOR_UNKNOWN);
110
    }
112
    }
111
 
113
 
112
    public static synchronized OOXML get(XMLFormatVersion version, final boolean lastForUnknown) {
114
    public static synchronized OOXML get(XMLFormatVersion version, final boolean lastForUnknown) {
113
        if (version.getXMLVersion() == XMLVersion.OOo) {
115
        if (version.getXMLVersion() == XMLVersion.OOo) {
114
            return instanceOO;
116
            return instanceOO;
115
        } else {
117
        } else {
116
            final XML_OD res = instancesODByVersion.get(version.getOfficeVersion());
118
            final XML_OD res = instancesODByVersion.get(version.getOfficeVersion());
117
            if (res == null && lastForUnknown)
119
            if (res == null && lastForUnknown)
118
                return getLast(version.getXMLVersion());
120
                return getLast(version.getXMLVersion());
119
            else
121
            else
120
                return res;
122
                return res;
121
        }
123
        }
122
    }
124
    }
123
 
125
 
124
    public static OOXML get(Element root) {
126
    public static OOXML get(Element root) {
125
        return XMLFormatVersion.get(root).getXML();
127
        return XMLFormatVersion.get(root).getXML();
126
    }
128
    }
127
 
129
 
128
    /**
130
    /**
129
     * Return all known instances in the order they were published.
131
     * Return all known instances in the order they were published.
130
     * 
132
     * 
131
     * @return all known instances ordered.
133
     * @return all known instances ordered.
132
     * @see #compareTo(OOXML)
134
     * @see #compareTo(OOXML)
133
     */
135
     */
134
    static public final List<OOXML> values() {
136
    static public final List<OOXML> values() {
135
        return values;
137
        return values;
136
    }
138
    }
137
 
139
 
138
    static public final OOXML getLast() {
140
    static public final OOXML getLast() {
139
        return CollectionUtils.getLast(values);
141
        return CollectionUtils.getLast(values);
140
    }
142
    }
141
 
143
 
142
    static public synchronized final OOXML getLast(XMLVersion version) {
144
    static public synchronized final OOXML getLast(XMLVersion version) {
143
        if (version == XMLVersion.OOo)
145
        if (version == XMLVersion.OOo)
144
            return instanceOO;
146
            return instanceOO;
145
        else
147
        else
146
            return instancesODByDate.get(instancesODByDate.lastKey());
148
            return instancesODByDate.get(instancesODByDate.lastKey());
147
    }
149
    }
148
 
150
 
149
    public static synchronized void setDefault(OOXML ns) {
151
    public static synchronized void setDefault(OOXML ns) {
150
        defaultInstance = ns;
152
        defaultInstance = ns;
151
    }
153
    }
152
 
154
 
153
    public static synchronized OOXML getDefault() {
155
    public static synchronized OOXML getDefault() {
154
        return defaultInstance;
156
        return defaultInstance;
155
    }
157
    }
156
 
158
 
157
    // from OpenDocument-v1.2-schema.rng : a coordinate is a length
159
    // from OpenDocument-v1.2-schema.rng : a coordinate is a length
158
    static private final Length parseCoordinate(final Element elem, final String attrName, final Namespace ns) {
160
    static private final Length parseCoordinate(final Element elem, final String attrName, final Namespace ns) {
159
        return parseLength(elem, attrName, ns);
161
        return parseLength(elem, attrName, ns);
160
    }
162
    }
161
 
163
 
162
    static private final Length parseLength(final Element elem, final String attrName, final Namespace ns) {
164
    static private final Length parseLength(final Element elem, final String attrName, final Namespace ns) {
163
        final String attr = elem.getAttributeValue(attrName, ns);
165
        final String attr = elem.getAttributeValue(attrName, ns);
164
        final Length res = LengthUnit.parseLength(attr);
166
        final Length res = LengthUnit.parseLength(attr);
165
        assert res != null;
167
        assert res != null;
166
        return res.isNone() ? null : res;
168
        return res.isNone() ? null : res;
167
    }
169
    }
168
 
170
 
169
    // *** instances
171
    // *** instances
170
 
172
 
171
    private final XMLFormatVersion version;
173
    private final XMLFormatVersion version;
172
    private final String dateString;
174
    private final String dateString;
173
 
175
 
174
    private OOXML(XMLFormatVersion version, final String dateString) {
176
    private OOXML(XMLFormatVersion version, final String dateString) {
175
        this.version = version;
177
        this.version = version;
176
        this.dateString = dateString;
178
        this.dateString = dateString;
177
    }
179
    }
178
 
180
 
179
    /**
181
    /**
180
     * The date the specification was published.
182
     * The date the specification was published.
181
     * 
183
     * 
182
     * @return the date in "yyyyMMdd" format.
184
     * @return the date in "yyyyMMdd" format.
183
     */
185
     */
184
    public final String getDateString() {
186
    public final String getDateString() {
185
        return this.dateString;
187
        return this.dateString;
186
    }
188
    }
187
 
189
 
188
    /**
190
    /**
189
     * Compare the date the specification was published.
191
     * Compare the date the specification was published.
190
     * 
192
     * 
191
     * @param o the object to be compared.
193
     * @param o the object to be compared.
192
     * @see #getDateString()
194
     * @see #getDateString()
193
     */
195
     */
194
    @Override
196
    @Override
195
    public int compareTo(OOXML o) {
197
    public int compareTo(OOXML o) {
196
        return this.dateString.compareTo(o.dateString);
198
        return this.dateString.compareTo(o.dateString);
197
    }
199
    }
198
 
200
 
199
    public final XMLVersion getVersion() {
201
    public final XMLVersion getVersion() {
200
        return this.getFormatVersion().getXMLVersion();
202
        return this.getFormatVersion().getXMLVersion();
201
    }
203
    }
202
 
204
 
203
    public final XMLFormatVersion getFormatVersion() {
205
    public final XMLFormatVersion getFormatVersion() {
204
        return this.version;
206
        return this.version;
205
    }
207
    }
206
 
208
 
207
    public abstract boolean canValidate();
209
    public abstract boolean canValidate();
208
 
210
 
209
    public Validator getValidator(final Document doc) {
211
    public Validator getValidator(final Document doc) {
210
        // true since by default LibreOffice generates foreign content
212
        // true since by default LibreOffice generates foreign content
211
        return this.getValidator(doc, true);
213
        return this.getValidator(doc, true);
212
    }
214
    }
213
 
215
 
214
    /**
216
    /**
215
     * Verify that the passed document is a valid OpenOffice.org 1 or ODF document.
217
     * Verify that the passed document is a valid OpenOffice.org 1 or ODF document.
216
     * 
218
     * 
217
     * @param doc the XML to test.
219
     * @param doc the XML to test.
218
     * @param ignoreForeign <code>true</code> to ignore unknown mark up, e.g. "extended document" in
220
     * @param ignoreForeign <code>true</code> to ignore unknown mark up, e.g. "extended document" in
219
     *        OpenDocument v1.2 §2.2.2 and in OpenDocument v1.1 §1.5.
221
     *        OpenDocument v1.2 §2.2.2 and in OpenDocument v1.1 §1.5.
220
     * @return a validator on <code>doc</code>.
222
     * @return a validator on <code>doc</code>.
221
     */
223
     */
222
    public abstract Validator getValidator(final Document doc, final boolean ignoreForeign);
224
    public abstract Validator getValidator(final Document doc, final boolean ignoreForeign);
223
 
225
 
224
    public abstract Document createManifestDoc();
226
    public abstract Document createManifestDoc();
225
 
227
 
226
    /**
228
    /**
227
     * Return the names of font face declarations.
229
     * Return the names of font face declarations.
228
     * 
230
     * 
229
     * @return at index 0 the name of the container element, at 1 the qualified name of its
231
     * @return at index 0 the name of the container element, at 1 the qualified name of its
230
     *         children.
232
     *         children.
231
     */
233
     */
232
    public abstract String[] getFontDecls();
234
    public abstract String[] getFontDecls();
233
 
235
 
234
    /**
236
    /**
235
     * Return the top-level script element in the content.
237
     * Return the top-level script element in the content.
236
     * 
238
     * 
237
     * @return the top-level script element name.
239
     * @return the top-level script element name.
238
     */
240
     */
239
    public abstract String getOfficeScripts();
241
    public abstract String getOfficeScripts();
240
 
242
 
241
    /**
243
    /**
242
     * The name of the elements where scripts are defined.
244
     * The name of the elements where scripts are defined.
243
     * 
245
     * 
244
     * @return the name of the children of {@link #getOfficeScripts()} defining scripts.
246
     * @return the name of the children of {@link #getOfficeScripts()} defining scripts.
245
     */
247
     */
246
    public abstract String getOfficeScript();
248
    public abstract String getOfficeScript();
247
 
249
 
248
    /**
250
    /**
249
     * The name of the element where event listeners are defined.
251
     * The name of the element where event listeners are defined.
250
     * 
252
     * 
251
     * @return the name of the child of {@link #getOfficeScripts()} defining event listeners.
253
     * @return the name of the child of {@link #getOfficeScripts()} defining event listeners.
252
     */
254
     */
253
    public abstract String getOfficeEventListeners();
255
    public abstract String getOfficeEventListeners();
254
 
256
 
255
    public abstract String getEventListener();
257
    public abstract String getEventListener();
256
 
258
 
257
    public final Element getLineBreak() {
259
    public final Element getLineBreak() {
258
        return new Element("line-break", getVersion().getTEXT());
260
        return new Element("line-break", getVersion().getTEXT());
259
    }
261
    }
260
 
262
 
261
    public abstract Element getTab();
263
    public abstract Element getTab();
262
 
264
 
263
    public abstract String getFrameQName();
265
    public abstract String getFrameQName();
264
 
266
 
265
    public abstract Element createFormattingProperties(final String family);
267
    public abstract Element createFormattingProperties(final String family);
266
 
268
 
267
    protected final Element encodeRT_L(final Element root, final String s, final Map<String, String> styles) {
269
    protected final Element encodeRT_L(final Element root, final String s, final Map<String, String> styles) {
268
        final List<String> quotedCodes = new ArrayList<String>(styles.size());
270
        final List<String> quotedCodes = new ArrayList<String>(styles.size());
269
        for (final String code : styles.keySet()) {
271
        for (final String code : styles.keySet()) {
270
            if (code.length() == 0 || code.indexOf('/') >= 0 || code.indexOf('[') >= 0 || code.indexOf(']') >= 0)
272
            if (code.length() == 0 || code.indexOf('/') >= 0 || code.indexOf('[') >= 0 || code.indexOf(']') >= 0)
271
                throw new IllegalArgumentException("Invalid code : " + code);
273
                throw new IllegalArgumentException("Invalid code : " + code);
272
            quotedCodes.add(Pattern.quote(code));
274
            quotedCodes.add(Pattern.quote(code));
273
        }
275
        }
274
        final Pattern p = Pattern.compile("\\[/?(" + CollectionUtils.join(quotedCodes, "|") + ")\\]");
276
        final Pattern p = Pattern.compile("\\[/?(" + CollectionUtils.join(quotedCodes, "|") + ")\\]");
275
        final Matcher m = p.matcher(s);
277
        final Matcher m = p.matcher(s);
276
 
278
 
277
        final Deque<Element> elements = new LinkedList<Element>();
279
        final Deque<Element> elements = new LinkedList<Element>();
278
        final Deque<String> codes = new LinkedList<String>();
280
        final Deque<String> codes = new LinkedList<String>();
279
        elements.addFirst(root);
281
        elements.addFirst(root);
280
        codes.addFirst(null);
282
        codes.addFirst(null);
281
        final Namespace testNS = getVersion().getTEXT();
283
        final Namespace testNS = getVersion().getTEXT();
282
        int last = 0;
284
        int last = 0;
283
        while (m.find()) {
285
        while (m.find()) {
284
            assert elements.size() == codes.size();
286
            assert elements.size() == codes.size();
285
            final Element current = elements.getFirst();
287
            final Element current = elements.getFirst();
286
            // null if root
288
            // null if root
287
            final String currentCode = codes.getFirst();
289
            final String currentCode = codes.getFirst();
288
            current.addContent(new Text(s.substring(last, m.start())));
290
            current.addContent(new Text(s.substring(last, m.start())));
289
            assert m.group().charAt(0) == '[';
291
            assert m.group().charAt(0) == '[';
290
            final boolean closing = m.group().charAt(1) == '/';
292
            final boolean closing = m.group().charAt(1) == '/';
291
            final String code = m.group(1);
293
            final String code = m.group(1);
292
            if (closing) {
294
            if (closing) {
293
                if (!code.equals(currentCode))
295
                if (!code.equals(currentCode))
294
                    throw new IllegalArgumentException("Mismatch current " + currentCode + " but closing " + code + " at " + m.start() + "\n" + s);
296
                    throw new IllegalArgumentException("Mismatch current " + currentCode + " but closing " + code + " at " + m.start() + "\n" + s);
295
                elements.removeFirst();
297
                elements.removeFirst();
296
                codes.removeFirst();
298
                codes.removeFirst();
297
            } else {
299
            } else {
298
                final Element newElem = new Element("span", testNS).setAttribute("style-name", styles.get(code), testNS);
300
                final Element newElem = new Element("span", testNS).setAttribute("style-name", styles.get(code), testNS);
299
                current.addContent(newElem);
301
                current.addContent(newElem);
300
                elements.addFirst(newElem);
302
                elements.addFirst(newElem);
301
                codes.addFirst(code);
303
                codes.addFirst(code);
302
            }
304
            }
303
            last = m.end();
305
            last = m.end();
304
        }
306
        }
305
        if (elements.size() != 1)
307
        if (elements.size() != 1)
306
            throw new IllegalArgumentException("Some tags weren't closed : " + elements + "\n" + s);
308
            throw new IllegalArgumentException("Some tags weren't closed : " + elements + "\n" + s);
307
        assert elements.peekFirst() == root;
309
        assert elements.peekFirst() == root;
308
        root.addContent(new Text(s.substring(last)));
310
        root.addContent(new Text(s.substring(last)));
309
        return root;
311
        return root;
310
    }
312
    }
311
 
313
 
312
    /**
314
    /**
313
     * Convert rich text (with [] tags) into XML.
315
     * Convert rich text (with [] tags) into XML.
314
     * 
316
     * 
315
     * @param content the string to convert, eg "texte [b]gras[/b]".
317
     * @param content the string to convert, eg "texte [b]gras[/b]".
316
     * @param styles the mapping from tagname (eg "b") to the name of the character style (eg
318
     * @param styles the mapping from tagname (eg "b") to the name of the character style (eg
317
     *        "Gras").
319
     *        "Gras").
318
     * @return the corresponding element.
320
     * @return the corresponding element.
319
     */
321
     */
320
    public final Element encodeRT(String content, Map<String, String> styles) {
322
    public final Element encodeRT(String content, Map<String, String> styles) {
321
        return encodeRT_L(Span.createEmpty(getFormatVersion()), content, styles);
323
        return encodeRT_L(Span.createEmpty(getFormatVersion()), content, styles);
322
    }
324
    }
323
 
325
 
324
    // create the necessary <text:s c="n"/>
326
    // create the necessary <text:s c="n"/>
325
    public final Element createSpaces(int count) {
327
    public final Element createSpaces(int count) {
326
        return new Element("s", getVersion().getTEXT()).setAttribute("c", count + "", getVersion().getTEXT());
328
        return new Element("s", getVersion().getTEXT()).setAttribute("c", count + "", getVersion().getTEXT());
327
    }
329
    }
328
 
330
 
329
    /**
331
    /**
330
     * Encode a String to OO XML. Handles substition of whitespaces to their OO equivalent.
332
     * Encode a String to OO XML. Handles substition of whitespaces to their OO equivalent.
331
     * 
333
     * 
332
     * @param s a plain ole String, eg "term\tdefinition".
334
     * @param s a plain ole String, eg "term\tdefinition".
333
     * @return an Element suitable to be inserted in an OO XML document, eg
335
     * @return an Element suitable to be inserted in an OO XML document, eg
334
     * 
336
     * 
335
     *         <pre>
337
     *         <pre>
336
     *     &lt;text:span&gt;term&lt;text:tab-stop/&gt;definition&lt;/text:span&gt;
338
     *     &lt;text:span&gt;term&lt;text:tab-stop/&gt;definition&lt;/text:span&gt;
337
     * </pre>
339
     *         </pre>
338
     * 
340
     * 
339
     *         .
341
     *         .
340
     */
342
     */
341
    public final Element encodeWS(final String s) {
343
    public final Element encodeWS(final String s) {
342
        return Span.createEmpty(getFormatVersion()).setContent(encodeWSasList(s));
344
        return Span.createEmpty(getFormatVersion()).setContent(encodeWSasList(s));
343
    }
345
    }
344
 
346
 
345
    public final List<Content> encodeWSasList(final String s) {
347
    public final List<Content> encodeWSasList(final String s) {
346
        final List<Content> res = new ArrayList<Content>();
348
        final List<Content> res = new ArrayList<Content>();
347
        final Matcher m = WHITE_SPACE_TO_ENCODE.matcher(s);
349
        final Matcher m = WHITE_SPACE_TO_ENCODE.matcher(s);
348
        int last = 0;
350
        int last = 0;
349
        while (m.find()) {
351
        while (m.find()) {
350
            res.add(new Text(s.substring(last, m.start())));
352
            res.add(new Text(s.substring(last, m.start())));
351
            switch (m.group().charAt(0)) {
353
            switch (m.group().charAt(0)) {
352
            case '\n':
354
            case '\n':
353
                // Vertical Tab, see TextNode#VERTICAL_TAB_CHAR
355
                // Vertical Tab, see TextNode#VERTICAL_TAB_CHAR
354
            case '\u000B':
356
            case '\u000B':
355
                res.add(getLineBreak());
357
                res.add(getLineBreak());
356
                break;
358
                break;
357
            case '\t':
359
            case '\t':
358
                res.add(getTab());
360
                res.add(getTab());
359
                break;
361
                break;
360
            case ' ':
362
            case ' ':
361
                res.add(createSpaces(m.group().length()));
363
                res.add(createSpaces(m.group().length()));
362
                break;
364
                break;
363
 
365
 
364
            default:
366
            default:
365
                throw new IllegalStateException("unknown item: " + m.group());
367
                throw new IllegalStateException("unknown item: " + m.group());
366
            }
368
            }
367
            last = m.end();
369
            last = m.end();
368
        }
370
        }
369
        res.add(new Text(s.substring(last)));
371
        res.add(new Text(s.substring(last)));
370
        return res;
372
        return res;
371
    }
373
    }
372
 
374
 
373
    @SuppressWarnings("unchecked")
375
    @SuppressWarnings("unchecked")
374
    public final void encodeWS(final Text t) {
376
    public final void encodeWS(final Text t) {
375
        final Parent parent = t.getParent();
377
        final Parent parent = t.getParent();
376
        final int ind = parent.indexOf(t);
378
        final int ind = parent.indexOf(t);
377
        t.detach();
379
        t.detach();
378
        parent.getContent().addAll(ind, encodeWSasList(t.getText()));
380
        parent.getContent().addAll(ind, encodeWSasList(t.getText()));
379
    }
381
    }
380
 
382
 
381
    @SuppressWarnings("unchecked")
383
    @SuppressWarnings("unchecked")
382
    public final Element encodeWS(final Element elem) {
384
    public final Element encodeWS(final Element elem) {
383
        final XPath path;
385
        final XPath path;
384
        try {
386
        try {
385
            path = OOUtils.getXPath(".//text()", getVersion());
387
            path = OOUtils.getXPath(".//text()", getVersion());
386
        } catch (JDOMException e) {
388
        } catch (JDOMException e) {
387
            // static path, hence always valid
389
            // static path, hence always valid
388
            throw new IllegalStateException("cannot create XPath", e);
390
            throw new IllegalStateException("cannot create XPath", e);
389
        }
391
        }
390
        try {
392
        try {
391
            final Iterator iter = new ArrayList(path.selectNodes(elem)).iterator();
393
            final Iterator iter = new ArrayList(path.selectNodes(elem)).iterator();
392
            while (iter.hasNext()) {
394
            while (iter.hasNext()) {
393
                final Text t = (Text) iter.next();
395
                final Text t = (Text) iter.next();
394
                encodeWS(t);
396
                encodeWS(t);
395
            }
397
            }
396
        } catch (JDOMException e) {
398
        } catch (JDOMException e) {
397
            throw new IllegalArgumentException("cannot find text nodes of " + elem, e);
399
            throw new IllegalArgumentException("cannot find text nodes of " + elem, e);
398
        }
400
        }
399
        return elem;
401
        return elem;
400
    }
402
    }
401
 
403
 
402
    /**
404
    /**
403
     * Return the coordinates of the top-left and bottom-right of the passed shape.
405
     * Return the coordinates of the top-left and bottom-right of the passed shape.
404
     * 
406
     * 
405
     * @param elem an XML element.
407
     * @param elem an XML element.
406
     * @return an array of 4 lengths, <code>null</code> if <code>elem</code> is not a shape, items
408
     * @return an array of 4 lengths, <code>null</code> if <code>elem</code> is not a shape, items
407
     *         themselves are never <code>null</code>.
409
     *         themselves are never <code>null</code>.
408
     */
410
     */
409
    public final Length[] getCoordinates(Element elem) {
411
    public final Length[] getCoordinates(Element elem) {
410
        return this.getCoordinates(elem, true, true);
412
        return this.getCoordinates(elem, true, true);
411
    }
413
    }
412
 
414
 
413
    /**
415
    /**
414
     * Return the coordinates of the top-left and bottom-right of the passed shape.
416
     * Return the coordinates of the top-left and bottom-right of the passed shape.
415
     * 
417
     * 
416
     * @param elem an XML element.
418
     * @param elem an XML element.
417
     * @param horizontal <code>true</code> if the x coordinates should be computed,
419
     * @param horizontal <code>true</code> if the x coordinates should be computed,
418
     *        <code>false</code> meaning items 0 and 2 of the result are <code>null</code>.
420
     *        <code>false</code> meaning items 0 and 2 of the result are <code>null</code>.
419
     * @param vertical <code>true</code> if the y coordinates should be computed, <code>false</code>
421
     * @param vertical <code>true</code> if the y coordinates should be computed, <code>false</code>
420
     *        meaning items 1 and 3 of the result are <code>null</code>.
422
     *        meaning items 1 and 3 of the result are <code>null</code>.
421
     * @return an array of 4 lengths, <code>null</code> if <code>elem</code> is not a shape, items
423
     * @return an array of 4 lengths, <code>null</code> if <code>elem</code> is not a shape, items
422
     *         themselves are only <code>null</code> if requested with <code>horizontal</code> or
424
     *         themselves are only <code>null</code> if requested with <code>horizontal</code> or
423
     *         <code>vertical</code>.
425
     *         <code>vertical</code>.
424
     */
426
     */
425
    public final Length[] getCoordinates(Element elem, final boolean horizontal, final boolean vertical) {
427
    public final Length[] getCoordinates(Element elem, final boolean horizontal, final boolean vertical) {
426
        return getCoordinates(elem, getVersion().getNS("svg"), horizontal, vertical);
428
        return getCoordinates(elem, getVersion().getNS("svg"), horizontal, vertical);
427
    }
429
    }
428
 
430
 
429
    static private final Length[] getCoordinates(Element elem, final Namespace svgNS, final boolean horizontal, final boolean vertical) {
431
    static private final Length[] getCoordinates(Element elem, final Namespace svgNS, final boolean horizontal, final boolean vertical) {
430
        if (elem.getName().equals("g") && elem.getNamespacePrefix().equals("draw")) {
432
        if (elem.getName().equals("g") && elem.getNamespacePrefix().equals("draw")) {
431
            // put below if to allow null to be returned by getLocalCoordinates() if elem isn't a
433
            // put below if to allow null to be returned by getLocalCoordinates() if elem isn't a
432
            // shape
434
            // shape
433
            if (!horizontal && !vertical)
435
            if (!horizontal && !vertical)
434
                return new Length[] { null, null, null, null };
436
                return new Length[] { null, null, null, null };
435
 
437
 
436
            // an OpenDocument group (of shapes) doesn't have any coordinates nor any width and
438
            // an OpenDocument group (of shapes) doesn't have any coordinates nor any width and
437
            // height so iterate through its components to find its coordinates
439
            // height so iterate through its components to find its coordinates
438
            Length minX = null, minY = null;
440
            Length minX = null, minY = null;
439
            Length maxX = null, maxY = null;
441
            Length maxX = null, maxY = null;
440
            for (final Object c : elem.getChildren()) {
442
            for (final Object c : elem.getChildren()) {
441
                final Element child = (Element) c;
443
                final Element child = (Element) c;
442
                final Length[] childCoord = getCoordinates(child, svgNS, horizontal, vertical);
444
                final Length[] childCoord = getCoordinates(child, svgNS, horizontal, vertical);
443
                // e.g. <office:event-listeners>, <svg:desc>, <svg:title>
445
                // e.g. <office:event-listeners>, <svg:desc>, <svg:title>
444
                if (childCoord != null) {
446
                if (childCoord != null) {
445
                    {
447
                    {
446
                        final Length x = childCoord[0];
448
                        final Length x = childCoord[0];
447
                        final Length x2 = childCoord[2];
449
                        final Length x2 = childCoord[2];
448
                        if (x != null) {
450
                        if (x != null) {
449
                            assert x2 != null;
451
                            assert x2 != null;
450
                            if (minX == null || x.compareTo(minX) < 0)
452
                            if (minX == null || x.compareTo(minX) < 0)
451
                                minX = x;
453
                                minX = x;
452
                            if (maxX == null || x2.compareTo(maxX) > 0)
454
                            if (maxX == null || x2.compareTo(maxX) > 0)
453
                                maxX = x2;
455
                                maxX = x2;
454
                        }
456
                        }
455
                    }
457
                    }
456
                    {
458
                    {
457
                        final Length y = childCoord[1];
459
                        final Length y = childCoord[1];
458
                        final Length y2 = childCoord[3];
460
                        final Length y2 = childCoord[3];
459
                        if (y != null) {
461
                        if (y != null) {
460
                            assert y2 != null;
462
                            assert y2 != null;
461
                            if (minY == null || y.compareTo(minY) < 0)
463
                            if (minY == null || y.compareTo(minY) < 0)
462
                                minY = y;
464
                                minY = y;
463
                            if (maxY == null || y2.compareTo(maxY) > 0)
465
                            if (maxY == null || y2.compareTo(maxY) > 0)
464
                                maxY = y2;
466
                                maxY = y2;
465
                        }
467
                        }
466
                    }
468
                    }
467
                }
469
                }
468
            }
470
            }
469
            // works because we check above if both horizontal and vertical are false
471
            // works because we check above if both horizontal and vertical are false
470
            if (minX == null && minY == null)
472
            if (minX == null && minY == null)
471
                throw new IllegalArgumentException("Empty group : " + JDOMUtils.output(elem));
473
                throw new IllegalArgumentException("Empty group : " + JDOMUtils.output(elem));
472
            return new Length[] { minX, minY, maxX, maxY };
474
            return new Length[] { minX, minY, maxX, maxY };
473
        } else {
475
        } else {
474
            return getLocalCoordinates(elem, svgNS, horizontal, vertical);
476
            return getLocalCoordinates(elem, svgNS, horizontal, vertical);
475
        }
477
        }
476
    }
478
    }
477
 
479
 
478
    // return null if elem isn't a shape (no x/y or no width/height)
480
    // return null if elem isn't a shape (no x/y or no width/height)
479
    // BigDecimal null if and only if horizontal/vertical is false
481
    // BigDecimal null if and only if horizontal/vertical is false
480
    static private final Length[] getLocalCoordinates(Element elem, final Namespace svgNS, final boolean horizontal, final boolean vertical) {
482
    static private final Length[] getLocalCoordinates(Element elem, final Namespace svgNS, final boolean horizontal, final boolean vertical) {
481
        final Length x = parseCoordinate(elem, "x", svgNS);
483
        final Length x = parseCoordinate(elem, "x", svgNS);
482
        final Length x1 = parseCoordinate(elem, "x1", svgNS);
484
        final Length x1 = parseCoordinate(elem, "x1", svgNS);
483
        if (x == null && x1 == null)
485
        if (x == null && x1 == null)
484
            return null;
486
            return null;
485
 
487
 
486
        final Length y = parseCoordinate(elem, "y", svgNS);
488
        final Length y = parseCoordinate(elem, "y", svgNS);
487
        final Length y1 = parseCoordinate(elem, "y1", svgNS);
489
        final Length y1 = parseCoordinate(elem, "y1", svgNS);
488
        if (y == null && y1 == null)
490
        if (y == null && y1 == null)
489
            throw new IllegalArgumentException("Have x but missing y in " + JDOMUtils.output(elem));
491
            throw new IllegalArgumentException("Have x but missing y in " + JDOMUtils.output(elem));
490
 
492
 
491
        final Length startX;
493
        final Length startX;
492
        final Length endX;
494
        final Length endX;
493
        if (horizontal) {
495
        if (horizontal) {
494
            if (x == null) {
496
            if (x == null) {
495
                startX = x1;
497
                startX = x1;
496
                endX = parseCoordinate(elem, "x2", svgNS);
498
                endX = parseCoordinate(elem, "x2", svgNS);
497
            } else {
499
            } else {
498
                startX = x;
500
                startX = x;
499
                final Length width = parseLength(elem, "width", svgNS);
501
                final Length width = parseLength(elem, "width", svgNS);
500
                endX = width == null ? null : startX.add(width);
502
                endX = width == null ? null : startX.add(width);
501
            }
503
            }
502
            // return null if there's no second coordinate (it's a point)
504
            // return null if there's no second coordinate (it's a point)
503
            if (endX == null)
505
            if (endX == null)
504
                return null;
506
                return null;
505
        } else {
507
        } else {
506
            startX = null;
508
            startX = null;
507
            endX = null;
509
            endX = null;
508
        }
510
        }
509
 
511
 
510
        final Length startY;
512
        final Length startY;
511
        final Length endY;
513
        final Length endY;
512
        if (vertical) {
514
        if (vertical) {
513
            if (y == null) {
515
            if (y == null) {
514
                startY = y1;
516
                startY = y1;
515
                endY = parseCoordinate(elem, "y2", svgNS);
517
                endY = parseCoordinate(elem, "y2", svgNS);
516
            } else {
518
            } else {
517
                startY = y;
519
                startY = y;
518
                final Length height = parseLength(elem, "height", svgNS);
520
                final Length height = parseLength(elem, "height", svgNS);
519
                endY = height == null ? null : startY.add(height);
521
                endY = height == null ? null : startY.add(height);
520
            }
522
            }
521
            // return null if there's no second coordinate (it's a point)
523
            // return null if there's no second coordinate (it's a point)
522
            if (endY == null)
524
            if (endY == null)
523
                return null;
525
                return null;
524
        } else {
526
        } else {
525
            startY = null;
527
            startY = null;
526
            endY = null;
528
            endY = null;
527
        }
529
        }
528
 
530
 
529
        return new Length[] { startX, startY, endX, endY };
531
        return new Length[] { startX, startY, endX, endY };
530
    }
532
    }
531
 
533
 
532
    @Immutable
534
    @Immutable
533
    private static final class XML_OO extends OOXML {
535
    private static final class XML_OO extends OOXML {
534
        private static final Set<Namespace> NS = XMLVersion.OOo.getNamespaceSet();
536
        private static final Set<Namespace> NS = XMLVersion.OOo.getNamespaceSet();
535
 
537
 
536
        private static final IPredicate<Object> UNKNOWN_PRED = new IPredicate<Object>() {
538
        private static final IPredicate<Object> UNKNOWN_PRED = new IPredicate<Object>() {
537
            @Override
539
            @Override
538
            public boolean evaluateChecked(Object input) {
540
            public boolean evaluateChecked(Object input) {
539
                final Namespace ns = JDOMUtils.getNamespace(input);
541
                final Namespace ns = JDOMUtils.getNamespace(input);
540
                return ns != null && !NS.contains(ns);
542
                return ns != null && !NS.contains(ns);
541
            }
543
            }
542
        };
544
        };
543
        private static final IPredicate<Object> MANIFEST_UNKNOWN_PRED = new IPredicate<Object>() {
545
        private static final IPredicate<Object> MANIFEST_UNKNOWN_PRED = new IPredicate<Object>() {
544
            @Override
546
            @Override
545
            public boolean evaluateChecked(Object input) {
547
            public boolean evaluateChecked(Object input) {
546
                final Namespace ns = JDOMUtils.getNamespace(input);
548
                final Namespace ns = JDOMUtils.getNamespace(input);
547
                return ns != null && !ns.equals(XMLVersion.OOo.getManifest());
549
                return ns != null && !ns.equals(XMLVersion.OOo.getManifest());
548
            }
550
            }
549
        };
551
        };
550
 
552
 
551
        private static final DocType createManifestDocType() {
553
        private static final DocType createManifestDocType() {
552
            return new DocType("manifest:manifest", "-//OpenOffice.org//DTD Manifest 1.0//EN", "Manifest.dtd");
554
            return new DocType("manifest:manifest", "-//OpenOffice.org//DTD Manifest 1.0//EN", "Manifest.dtd");
553
        }
555
        }
554
 
556
 
555
        public XML_OO() {
557
        public XML_OO() {
556
            super(XMLFormatVersion.getOOo(), "20020501");
558
            super(XMLFormatVersion.getOOo(), "20020501");
557
        }
559
        }
558
 
560
 
559
        @Override
561
        @Override
560
        public boolean canValidate() {
562
        public boolean canValidate() {
561
            return true;
563
            return true;
562
        }
564
        }
563
 
565
 
564
        @Override
566
        @Override
565
        public Validator getValidator(final Document doc, final boolean ignoreForeign) {
567
        public Validator getValidator(final Document doc, final boolean ignoreForeign) {
566
            // DTDs are stubborn, xmlns have to be exactly where they want
568
            // DTDs are stubborn, xmlns have to be exactly where they want
567
            // in this case the root element
569
            // in this case the root element
568
            final boolean isManifest = doc.getRootElement().getQualifiedName().equals("manifest:manifest");
570
            final boolean isManifest = doc.getRootElement().getQualifiedName().equals("manifest:manifest");
569
            if (!isManifest) {
571
            if (!isManifest) {
570
                for (final Namespace n : getVersion().getALL())
572
                for (final Namespace n : getVersion().getALL())
571
                    doc.getRootElement().addNamespaceDeclaration(n);
573
                    doc.getRootElement().addNamespaceDeclaration(n);
572
            }
574
            }
573
            return new Validator.DTDValidator(doc, !ignoreForeign ? null : (isManifest ? MANIFEST_UNKNOWN_PRED : UNKNOWN_PRED), OOUtils.getBuilderLoadDTD());
575
            return new Validator.DTDValidator(doc, !ignoreForeign ? null : (isManifest ? MANIFEST_UNKNOWN_PRED : UNKNOWN_PRED), OOUtils.getBuilderLoadDTD());
574
        }
576
        }
575
 
577
 
576
        @Override
578
        @Override
577
        public Document createManifestDoc() {
579
        public Document createManifestDoc() {
578
            return new Document(new Element("manifest", this.getVersion().getManifest()), createManifestDocType());
580
            return new Document(new Element("manifest", this.getVersion().getManifest()), createManifestDocType());
579
        }
581
        }
580
 
582
 
581
        @Override
583
        @Override
582
        public String getOfficeScripts() {
584
        public String getOfficeScripts() {
583
            return "script";
585
            return "script";
584
        }
586
        }
585
 
587
 
586
        @Override
588
        @Override
587
        public String getOfficeScript() {
589
        public String getOfficeScript() {
588
            return "script-data";
590
            return "script-data";
589
        }
591
        }
590
 
592
 
591
        @Override
593
        @Override
592
        public String getOfficeEventListeners() {
594
        public String getOfficeEventListeners() {
593
            return "events";
595
            return "events";
594
        }
596
        }
595
 
597
 
596
        @Override
598
        @Override
597
        public String getEventListener() {
599
        public String getEventListener() {
598
            return "event";
600
            return "event";
599
        }
601
        }
600
 
602
 
601
        @Override
603
        @Override
602
        public String[] getFontDecls() {
604
        public String[] getFontDecls() {
603
            return new String[] { "font-decls", "style:font-decl" };
605
            return new String[] { "font-decls", "style:font-decl" };
604
        }
606
        }
605
 
607
 
606
        @Override
608
        @Override
607
        public final Element getTab() {
609
        public final Element getTab() {
608
            return new Element("tab-stop", getVersion().getTEXT());
610
            return new Element("tab-stop", getVersion().getTEXT());
609
        }
611
        }
610
 
612
 
611
        @Override
613
        @Override
612
        public String getFrameQName() {
614
        public String getFrameQName() {
613
            return "draw:text-box";
615
            return "draw:text-box";
614
        }
616
        }
615
 
617
 
616
        @Override
618
        @Override
617
        public Element createFormattingProperties(String family) {
619
        public Element createFormattingProperties(String family) {
618
            return new Element("properties", this.getVersion().getSTYLE());
620
            return new Element("properties", this.getVersion().getSTYLE());
619
        }
621
        }
620
    }
622
    }
621
 
623
 
622
    @ThreadSafe
624
    @ThreadSafe
623
    private static class XML_OD extends OOXML {
625
    private static class XML_OD extends OOXML {
624
 
626
 
625
        private static final IPredicate<Object> UNKNOWN_PRED = new IPredicate<Object>() {
627
        private static final IPredicate<Object> UNKNOWN_PRED = new IPredicate<Object>() {
626
            @Override
628
            @Override
627
            public boolean evaluateChecked(Object input) {
629
            public boolean evaluateChecked(Object input) {
628
                final Namespace ns = JDOMUtils.getNamespace(input);
630
                final Namespace ns = JDOMUtils.getNamespace(input);
629
                // leave non-elements alone
631
                // leave non-elements alone
630
                if (ns == null)
632
                if (ns == null)
631
                    return false;
633
                    return false;
632
                // XMLVersion doesn't include all namespaces of the standard, so only exclude known
634
                // XMLVersion doesn't include all namespaces of the standard, so only exclude known
633
                // extended namespaces.
635
                // extended namespaces.
634
                return ns.equals(Namespace.NO_NAMESPACE) || ns.getURI().startsWith("urn:org:documentfoundation:names:experimental") || ns.getURI().startsWith("urn:openoffice:names:experimental");
636
                return ns.equals(Namespace.NO_NAMESPACE) || ns.getURI().startsWith("urn:org:documentfoundation:names:experimental") || ns.getURI().startsWith("urn:openoffice:names:experimental");
635
            }
637
            }
636
        };
638
        };
637
 
639
 
638
        private final String schemaFile, manifestSchemaFile;
640
        private final String schemaFile, manifestSchemaFile;
639
        @GuardedBy("this")
641
        @GuardedBy("this")
640
        private Schema schema, manifestSchema;
642
        private Schema schema, manifestSchema;
641
 
643
 
642
        public XML_OD(final String dateString, final String versionString, final String schemaFile, final String manifestSchemaFile) {
644
        public XML_OD(final String dateString, final String versionString, final String schemaFile, final String manifestSchemaFile) {
643
            super(XMLFormatVersion.get(XMLVersion.OD, versionString), dateString);
645
            super(XMLFormatVersion.get(XMLVersion.OD, versionString), dateString);
644
            this.schemaFile = schemaFile;
646
            this.schemaFile = schemaFile;
645
            this.manifestSchemaFile = manifestSchemaFile;
647
            this.manifestSchemaFile = manifestSchemaFile;
646
            this.schema = this.manifestSchema = null;
648
            this.schema = this.manifestSchema = null;
647
        }
649
        }
648
 
650
 
649
        @Override
651
        @Override
650
        public boolean canValidate() {
652
        public boolean canValidate() {
651
            return this.schemaFile != null && this.manifestSchemaFile != null;
653
            return this.schemaFile != null && this.manifestSchemaFile != null;
652
        }
654
        }
653
 
655
 
654
        private Schema createSchema(final String name) throws SAXException {
656
        private Schema createSchema(final String name) throws SAXException {
655
            return SchemaFactory.newInstance(XMLConstants.RELAXNG_NS_URI).newSchema(getClass().getResource("oofficeDTDs/" + name));
657
            return SchemaFactory.newInstance(XMLConstants.RELAXNG_NS_URI).newSchema(getClass().getResource("oofficeDTDs/" + name));
656
        }
658
        }
657
 
659
 
658
        private synchronized Schema getSchema() throws SAXException {
660
        private synchronized Schema getSchema() throws SAXException {
659
            if (this.schema == null && this.schemaFile != null) {
661
            if (this.schema == null && this.schemaFile != null) {
660
                this.schema = this.createSchema(this.schemaFile);
662
                this.schema = this.createSchema(this.schemaFile);
661
            }
663
            }
662
            return this.schema;
664
            return this.schema;
663
        }
665
        }
664
 
666
 
665
        private synchronized Schema getManifestSchema() throws SAXException {
667
        private synchronized Schema getManifestSchema() throws SAXException {
666
            if (this.manifestSchema == null && this.manifestSchemaFile != null) {
668
            if (this.manifestSchema == null && this.manifestSchemaFile != null) {
667
                this.manifestSchema = this.createSchema(this.manifestSchemaFile);
669
                this.manifestSchema = this.createSchema(this.manifestSchemaFile);
668
            }
670
            }
669
            return this.manifestSchema;
671
            return this.manifestSchema;
670
        }
672
        }
671
 
673
 
672
        @Override
674
        @Override
673
        public Validator getValidator(final Document doc, final boolean ignoreForeign) {
675
        public Validator getValidator(final Document doc, final boolean ignoreForeign) {
674
            final Schema schema;
676
            final Schema schema;
675
            try {
677
            try {
676
                if (doc.getRootElement().getQualifiedName().equals("manifest:manifest"))
678
                if (doc.getRootElement().getQualifiedName().equals("manifest:manifest"))
677
                    schema = this.getManifestSchema();
679
                    schema = this.getManifestSchema();
678
                else
680
                else
679
                    schema = this.getSchema();
681
                    schema = this.getSchema();
680
            } catch (SAXException e) {
682
            } catch (SAXException e) {
681
                throw new IllegalStateException("relaxNG schemas pb", e);
683
                throw new IllegalStateException("relaxNG schemas pb", e);
682
            }
684
            }
683
            return schema == null ? null : new Validator.JAXPValidator(doc, ignoreForeign ? UNKNOWN_PRED : null, schema);
685
            return schema == null ? null : new Validator.JAXPValidator(doc, ignoreForeign ? UNKNOWN_PRED : null, schema);
684
        }
686
        }
685
 
687
 
686
        @Override
688
        @Override
687
        public Document createManifestDoc() {
689
        public Document createManifestDoc() {
688
            return new Document(new Element("manifest", this.getVersion().getManifest()), null);
690
            return new Document(new Element("manifest", this.getVersion().getManifest()), null);
689
        }
691
        }
690
 
692
 
691
        @Override
693
        @Override
692
        public String getOfficeScripts() {
694
        public String getOfficeScripts() {
693
            return "scripts";
695
            return "scripts";
694
        }
696
        }
695
 
697
 
696
        @Override
698
        @Override
697
        public String getOfficeScript() {
699
        public String getOfficeScript() {
698
            return "script";
700
            return "script";
699
        }
701
        }
700
 
702
 
701
        @Override
703
        @Override
702
        public String getOfficeEventListeners() {
704
        public String getOfficeEventListeners() {
703
            return "event-listeners";
705
            return "event-listeners";
704
        }
706
        }
705
 
707
 
706
        @Override
708
        @Override
707
        public String getEventListener() {
709
        public String getEventListener() {
708
            return "event-listener";
710
            return "event-listener";
709
        }
711
        }
710
 
712
 
711
        @Override
713
        @Override
712
        public final String[] getFontDecls() {
714
        public final String[] getFontDecls() {
713
            return new String[] { "font-face-decls", "style:font-face" };
715
            return new String[] { "font-face-decls", "style:font-face" };
714
        }
716
        }
715
 
717
 
716
        @Override
718
        @Override
717
        public final Element getTab() {
719
        public final Element getTab() {
718
            return new Element("tab", getVersion().getTEXT());
720
            return new Element("tab", getVersion().getTEXT());
719
        }
721
        }
720
 
722
 
721
        @Override
723
        @Override
722
        public String getFrameQName() {
724
        public String getFrameQName() {
723
            return "draw:frame";
725
            return "draw:frame";
724
        }
726
        }
725
 
727
 
726
        @Override
728
        @Override
727
        public Element createFormattingProperties(String family) {
729
        public Element createFormattingProperties(String family) {
728
            return new Element(family + "-properties", this.getVersion().getSTYLE());
730
            return new Element(family + "-properties", this.getVersion().getSTYLE());
729
        }
731
        }
730
    }
732
    }
731
 
733
 
732
    private static final class XML_OD_1_0 extends XML_OD {
734
    private static final class XML_OD_1_0 extends XML_OD {
733
        public XML_OD_1_0() {
735
        public XML_OD_1_0() {
734
            super("20061130", "1.0", null, null);
736
            super("20061130", "1.0", null, null);
735
        }
737
        }
736
    }
738
    }
737
 
739
 
738
    private static final class XML_OD_1_1 extends XML_OD {
740
    private static final class XML_OD_1_1 extends XML_OD {
739
        public XML_OD_1_1() {
741
        public XML_OD_1_1() {
740
            super("20070201", "1.1", "OpenDocument-strict-schema-v1.1.rng", "OpenDocument-manifest-schema-v1.1.rng");
742
            super("20070201", "1.1", "OpenDocument-strict-schema-v1.1.rng", "OpenDocument-manifest-schema-v1.1.rng");
741
        }
743
        }
742
    }
744
    }
743
 
745
 
744
    private static final class XML_OD_1_2 extends XML_OD {
746
    private static final class XML_OD_1_2 extends XML_OD_1_2plus {
745
        public XML_OD_1_2() {
747
        public XML_OD_1_2() {
746
            super("20110317", "1.2", "OpenDocument-v1.2-schema.rng", "OpenDocument-v1.2-manifest-schema.rng");
748
            super("20110317", "1.2", "OpenDocument-v1.2-schema.rng", "OpenDocument-v1.2-manifest-schema.rng");
747
        }
749
        }
-
 
750
    }
-
 
751
 
-
 
752
    private static abstract class XML_OD_1_2plus extends XML_OD {
-
 
753
        protected XML_OD_1_2plus(final String dateString, final String versionString, final String schemaFile, final String manifestSchemaFile) {
-
 
754
            super(dateString, versionString, schemaFile, manifestSchemaFile);
-
 
755
        }
748
 
756
 
749
        @Override
757
        @Override
750
        public Document createManifestDoc() {
758
        public Document createManifestDoc() {
751
            final Document res = super.createManifestDoc();
759
            final Document res = super.createManifestDoc();
752
            res.getRootElement().setAttribute("version", getFormatVersion().getOfficeVersion(), res.getRootElement().getNamespace());
760
            res.getRootElement().setAttribute("version", getFormatVersion().getOfficeVersion(), res.getRootElement().getNamespace());
753
            return res;
761
            return res;
754
        }
762
        }
-
 
763
    }
-
 
764
 
-
 
765
    // https://issues.oasis-open.org/issues/?jql=project%20%3D%20OFFICE%20AND%20resolution%20%3D%20Fixed%20AND%20fixVersion%20%3D%20%22ODF%201.3%22
-
 
766
    // - https://issues.oasis-open.org/browse/OFFICE-3860 : New attributes "min-decimal-places" and
-
 
767
    // "forced-exponent-sign" parsed in DataStyle.formatNumberOrScientificNumber()
-
 
768
    private static final class XML_OD_1_3 extends XML_OD_1_2plus {
-
 
769
        public XML_OD_1_3() {
-
 
770
            super("20191225", "1.3", "OpenDocument-schema-v1.3.rng", "OpenDocument-manifest-schema-v1.3.rng");
-
 
771
        }
755
    }
772
    }
756
}
773
}