All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jopendocument.dom.OOXML Maven / Gradle / Ivy

Go to download

jOpenDocument is a free library for developers looking to use Open Document files without OpenOffice.org.

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2008-2013 jOpenDocument, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU
 * General Public License Version 3 only ("GPL").  
 * You may not use this file except in compliance with the License. 
 * You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
 * See the License for the specific language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 * 
 */

/*
 * Créé le 28 oct. 2004
 */
package org.jopendocument.dom;

import org.jopendocument.util.CollectionUtils;
import org.jopendocument.util.JDOMUtils;
import org.jopendocument.util.Validator;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.XMLConstants;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.jdom.Content;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.Parent;
import org.jdom.Text;
import org.jdom.xpath.XPath;
import org.xml.sax.SAXException;

/**
 * Various bits of OpenDocument XML.
 * 
 * @author Sylvain CUAZ
 * @see #get(XMLFormatVersion)
 */
public abstract class OOXML implements Comparable {

    /**
     * If this system property is set to true then {@link #get(XMLFormatVersion)} will
     * never return null, allowing to support unknown versions.
     */
    public static final String LAST_FOR_UNKNOWN_PROP = OOXML.class.getPackage().getName() + ".lastOOXMLForUnknownVersion";
    private static final XML_OO instanceOO = new XML_OO();
    private static final SortedMap instancesODByDate = new TreeMap();
    private static final Map instancesODByVersion = new HashMap();
    private static final List values;
    private static OOXML defaultInstance;
    private static final Pattern WHITE_SPACE_TO_ENCODE = Pattern.compile("\n|\t| {2,}");

    static {
        register(new XML_OD_1_0());
        register(new XML_OD_1_1());
        register(new XML_OD_1_2());
        values = new ArrayList(instancesODByDate.size() + 1);
        values.add(instanceOO);
        values.addAll(instancesODByDate.values());

        setDefault(getLast());
    }

    private static void register(XML_OD xml) {
        assert xml.getVersion() == XMLVersion.OD;
        instancesODByDate.put(xml.getDateString(), xml);
        instancesODByVersion.put(xml.getFormatVersion().getOfficeVersion(), xml);
    }

    /**
     * Returns the instance that match the requested version.
     * 
     * @param version the version.
     * @return the corresponding instance, null for unsupported versions.
     * @see #LAST_FOR_UNKNOWN_PROP
     */
    public static OOXML get(XMLFormatVersion version) {
        return get(version, Boolean.getBoolean(LAST_FOR_UNKNOWN_PROP));
    }

    public static OOXML get(XMLFormatVersion version, final boolean lastForUnknown) {
        if (version.getXMLVersion() == XMLVersion.OOo) {
            return instanceOO;
        } else {
            final XML_OD res = instancesODByVersion.get(version.getOfficeVersion());
            if (res == null && lastForUnknown)
                return getLast(version.getXMLVersion());
            else
                return res;
        }
    }

    public static OOXML get(Element root) {
        return XMLFormatVersion.get(root).getXML();
    }

    /**
     * Return all known instances in the order they were published.
     * 
     * @return all known instances ordered.
     * @see #compareTo(OOXML)
     */
    static public final List values() {
        return values;
    }

    static public final OOXML getLast() {
        return CollectionUtils.getLast(values);
    }

    static public final OOXML getLast(XMLVersion version) {
        if (version == XMLVersion.OOo)
            return instanceOO;
        else
            return instancesODByDate.get(instancesODByDate.lastKey());
    }

    public static void setDefault(OOXML ns) {
        defaultInstance = ns;
    }

    public static OOXML getDefault() {
        return defaultInstance;
    }

    static private final String rt2oo(String content, String tagName, String styleName) {
        return content.replaceAll("\\[" + tagName + "\\]", "").replaceAll("\\[/" + tagName + "\\]", "");
    }

    // from OpenDocument-v1.2-schema.rng : a coordinate is a length
    static private final BigDecimal parseCoordinate(final Element elem, final String attrName, final Namespace ns, LengthUnit unit) {
        return parseLength(elem, attrName, ns, unit);
    }

    static private final BigDecimal parseLength(final Element elem, final String attrName, final Namespace ns, LengthUnit unit) {
        final String attr = elem.getAttributeValue(attrName, ns);
        if (attr == null)
            return null;
        return LengthUnit.parseLength(attr, unit);
    }

    // *** instances

    private final XMLFormatVersion version;
    private final String dateString;

    private OOXML(XMLFormatVersion version, final String dateString) {
        this.version = version;
        this.dateString = dateString;
    }

    /**
     * The date the specification was published.
     * 
     * @return the date in "yyyyMMdd" format.
     */
    public final String getDateString() {
        return this.dateString;
    }

    /**
     * Compare the date the specification was published.
     * 
     * @param o the object to be compared.
     * @see #getDateString()
     */
    @Override
    public int compareTo(OOXML o) {
        return this.dateString.compareTo(o.dateString);
    }

    public final XMLVersion getVersion() {
        return this.getFormatVersion().getXMLVersion();
    }

    public final XMLFormatVersion getFormatVersion() {
        return this.version;
    }

    public abstract boolean canValidate();

    /**
     * Verify that the passed document is a valid OpenOffice.org 1 or ODF document.
     * 
     * @param doc the xml to test.
     * @return a validator on doc.
     */
    public abstract Validator getValidator(Document doc);

    /**
     * Return the names of font face declarations.
     * 
     * @return at index 0 the name of the container element, at 1 the qualified name of its
     *         children.
     */
    public abstract String[] getFontDecls();

    /**
     * Return the top-level script element in the content.
     * 
     * @return the top-level script element name.
     */
    public abstract String getOfficeScripts();

    /**
     * The name of the elements where scripts are defined.
     * 
     * @return the name of the children of {@link #getOfficeScripts()} defining scripts.
     */
    public abstract String getOfficeScript();

    /**
     * The name of the element where event listeners are defined.
     * 
     * @return the name of the child of {@link #getOfficeScripts()} defining event listeners.
     */
    public abstract String getOfficeEventListeners();

    public abstract String getEventListener();

    public final Element getLineBreak() {
        return new Element("line-break", getVersion().getTEXT());
    }

    public abstract Element getTab();

    public abstract String getFrameQName();

    public abstract Element createFormattingProperties(final String family);

    protected final List encodeRT_L(String content, Map styles) {
        String res = JDOMUtils.OUTPUTTER.escapeElementEntities(content);
        for (final Entry e : styles.entrySet()) {
            res = rt2oo(res, e.getKey(), e.getValue());
        }
        try {
            return JDOMUtils.parseString(res, getVersion().getALL());
        } catch (JDOMException e) {
            // should not happpen as we did escapeElementEntities which gives valid xml and then
            // rt2oo which introduce only static xml
            throw new IllegalStateException("could not parse " + res, e);
        }
    }

    /**
     * Convert rich text (with [] tags) into XML.
     * 
     * @param content the string to convert, eg "texte [b]gras[/b]".
     * @param styles the mapping from tagname (eg "b") to the name of the character style (eg
     *        "Gras").
     * @return the corresponding element.
     */
    public final Element encodeRT(String content, Map styles) {
        return new Element("span", getVersion().getTEXT()).addContent(encodeRT_L(content, styles));
    }

    // create the necessary 
    private Element createSpaces(String spacesS) {
        return new Element("s", getVersion().getTEXT()).setAttribute("c", spacesS.length() + "", getVersion().getTEXT());
    }

    /**
     * Encode a String to OO XML. Handles substition of whitespaces to their OO equivalent.
     * 
     * @param s a plain ole String, eg "term\tdefinition".
     * @return an Element suitable to be inserted in an OO XML document, eg
     * 
     *         
     *     <text:span>term<text:tab-stop/>definition</text:span>
     * 
* * . */ public final Element encodeWS(final String s) { return new Element("span", getVersion().getTEXT()).setContent(encodeWSasList(s)); } public final List encodeWSasList(final String s) { final List res = new ArrayList(); final Matcher m = WHITE_SPACE_TO_ENCODE.matcher(s); int last = 0; while (m.find()) { res.add(new Text(s.substring(last, m.start()))); switch (m.group().charAt(0)) { case '\n': res.add(getLineBreak()); break; case '\t': res.add(getTab()); break; case ' ': res.add(createSpaces(m.group())); break; default: throw new IllegalStateException("unknown item: " + m.group()); } last = m.end(); } res.add(new Text(s.substring(last))); return res; } @SuppressWarnings("unchecked") public final void encodeWS(final Text t) { final Parent parent = t.getParent(); final int ind = parent.indexOf(t); t.detach(); parent.getContent().addAll(ind, encodeWSasList(t.getText())); } @SuppressWarnings("unchecked") public final Element encodeWS(final Element elem) { final XPath path; try { path = OOUtils.getXPath(".//text()", getVersion()); } catch (JDOMException e) { // static path, hence always valid throw new IllegalStateException("cannot create XPath", e); } try { final Iterator iter = new ArrayList(path.selectNodes(elem)).iterator(); while (iter.hasNext()) { final Text t = (Text) iter.next(); encodeWS(t); } } catch (JDOMException e) { throw new IllegalArgumentException("cannot find text nodes of " + elem, e); } return elem; } /** * Return the coordinates of the top-left and bottom-right of the passed shape. * * @param elem an XML element. * @param unit the unit of the returned numbers. * @return an array of 4 numbers, null if elem is not a shape, numbers * themselves are never null. */ public final BigDecimal[] getCoordinates(Element elem, LengthUnit unit) { return this.getCoordinates(elem, unit, true, true); } /** * Return the coordinates of the top-left and bottom-right of the passed shape. * * @param elem an XML element. * @param unit the unit of the returned numbers. * @param horizontal true if the x coordinates should be computed, * false meaning items 0 and 2 of the result are null. * @param vertical true if the y coordinates should be computed, false * meaning items 1 and 3 of the result are null. * @return an array of 4 numbers, null if elem is not a shape, numbers * themselves are only null if requested with horizontal or * vertical. */ public final BigDecimal[] getCoordinates(Element elem, LengthUnit unit, final boolean horizontal, final boolean vertical) { return getCoordinates(elem, getVersion().getNS("svg"), unit, horizontal, vertical); } static private final BigDecimal[] getCoordinates(Element elem, final Namespace svgNS, LengthUnit unit, final boolean horizontal, final boolean vertical) { if (elem.getName().equals("g") && elem.getNamespacePrefix().equals("draw")) { // put below if to allow null to be returned by getLocalCoordinates() if elem isn't a // shape if (!horizontal && !vertical) return new BigDecimal[] { null, null, null, null }; // an OpenDocument group (of shapes) doesn't have any coordinates nor any width and // height so iterate through its components to find its coordinates BigDecimal minX = null, minY = null; BigDecimal maxX = null, maxY = null; for (final Object c : elem.getChildren()) { final Element child = (Element) c; final BigDecimal[] childCoord = getCoordinates(child, svgNS, unit, horizontal, vertical); // e.g. , , if (childCoord != null) { { final BigDecimal x = childCoord[0]; final BigDecimal x2 = childCoord[2]; if (x != null) { assert x2 != null; if (minX == null || x.compareTo(minX) < 0) minX = x; if (maxX == null || x2.compareTo(maxX) > 0) maxX = x2; } } { final BigDecimal y = childCoord[1]; final BigDecimal y2 = childCoord[3]; if (y != null) { assert y2 != null; if (minY == null || y.compareTo(minY) < 0) minY = y; if (maxY == null || y2.compareTo(maxY) > 0) maxY = y2; } } } } // works because we check above if both horizontal and vertical are false if (minX == null && minY == null) throw new IllegalArgumentException("Empty group : " + JDOMUtils.output(elem)); return new BigDecimal[] { minX, minY, maxX, maxY }; } else { return getLocalCoordinates(elem, svgNS, unit, horizontal, vertical); } } // return null if elem isn't a shape (no x/y or no width/height) // BigDecimal null if and only if horizontal/vertical is false static private final BigDecimal[] getLocalCoordinates(Element elem, final Namespace svgNS, LengthUnit unit, final boolean horizontal, final boolean vertical) { final BigDecimal x = parseCoordinate(elem, "x", svgNS, unit); final BigDecimal x1 = parseCoordinate(elem, "x1", svgNS, unit); if (x == null && x1 == null) return null; final BigDecimal y = parseCoordinate(elem, "y", svgNS, unit); final BigDecimal y1 = parseCoordinate(elem, "y1", svgNS, unit); if (y == null && y1 == null) throw new IllegalArgumentException("Have x but missing y in " + JDOMUtils.output(elem)); final BigDecimal startX; final BigDecimal endX; if (horizontal) { if (x == null) { startX = x1; endX = parseCoordinate(elem, "x2", svgNS, unit); } else { startX = x; final BigDecimal width = parseLength(elem, "width", svgNS, unit); endX = width == null ? null : startX.add(width); } // return null if there's no second coordinate (it's a point) if (endX == null) return null; } else { startX = null; endX = null; } final BigDecimal startY; final BigDecimal endY; if (vertical) { if (y == null) { startY = y1; endY = parseCoordinate(elem, "y2", svgNS, unit); } else { startY = y; final BigDecimal height = parseLength(elem, "height", svgNS, unit); endY = height == null ? null : startY.add(height); } // return null if there's no second coordinate (it's a point) if (endY == null) return null; } else { startY = null; endY = null; } return new BigDecimal[] { startX, startY, endX, endY }; } private static final class XML_OO extends OOXML { public XML_OO() { super(XMLFormatVersion.getOOo(), "20020501"); } @Override public boolean canValidate() { return true; } @Override public Validator getValidator(Document doc) { // DTDs are stubborn, xmlns have to be exactly where they want // in this case the root element for (final Namespace n : getVersion().getALL()) doc.getRootElement().addNamespaceDeclaration(n); return new Validator.DTDValidator(doc, OOUtils.getBuilderLoadDTD()); } @Override public String getOfficeScripts() { return "script"; } @Override public String getOfficeScript() { return "script-data"; } @Override public String getOfficeEventListeners() { return "events"; } @Override public String getEventListener() { return "event"; } @Override public String[] getFontDecls() { return new String[] { "font-decls", "style:font-decl" }; } @Override public final Element getTab() { return new Element("tab-stop", getVersion().getTEXT()); } @Override public String getFrameQName() { return "draw:text-box"; } @Override public Element createFormattingProperties(String family) { return new Element("properties", this.getVersion().getSTYLE()); } } private static class XML_OD extends OOXML { private final String schemaFile; private Schema schema = null; public XML_OD(final String dateString, final String versionString, final String schemaFile) { super(XMLFormatVersion.get(XMLVersion.OD, versionString), dateString); this.schemaFile = schemaFile; } @Override public boolean canValidate() { return this.schemaFile != null; } private Schema getSchema() throws SAXException { if (this.schema == null && this.schemaFile != null) { this.schema = SchemaFactory.newInstance(XMLConstants.RELAXNG_NS_URI).newSchema(getClass().getResource("oofficeDTDs/" + this.schemaFile)); } return this.schema; } @Override public Validator getValidator(Document doc) { final Schema schema; try { schema = this.getSchema(); } catch (SAXException e) { throw new IllegalStateException("relaxNG schemas pb", e); } return schema == null ? null : new Validator.JAXPValidator(doc, schema); } @Override public String getOfficeScripts() { return "scripts"; } @Override public String getOfficeScript() { return "script"; } @Override public String getOfficeEventListeners() { return "event-listeners"; } @Override public String getEventListener() { return "event-listener"; } @Override public final String[] getFontDecls() { return new String[] { "font-face-decls", "style:font-face" }; } @Override public final Element getTab() { return new Element("tab", getVersion().getTEXT()); } @Override public String getFrameQName() { return "draw:frame"; } @Override public Element createFormattingProperties(String family) { return new Element(family + "-properties", this.getVersion().getSTYLE()); } } private static final class XML_OD_1_0 extends XML_OD { public XML_OD_1_0() { super("20061130", "1.0", null); } } private static final class XML_OD_1_1 extends XML_OD { public XML_OD_1_1() { super("20070201", "1.1", "OpenDocument-strict-schema-v1.1.rng"); } } private static final class XML_OD_1_2 extends XML_OD { public XML_OD_1_2() { super("20110317", "1.2", "OpenDocument-v1.2-schema.rng"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy