OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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