OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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