org.jopendocument.dom.OOXML Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008 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;
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();
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 = Pattern.compile("\n|\t| {2,}").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[] 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 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 - 2025 Weber Informatics LLC | Privacy Policy