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