OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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