/*
* 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.JDOMUtils;
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.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
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;
import com.thaiopensource.relaxng.jaxp.XMLSyntaxSchemaFactory;
/**
* Various bits of OpenDocument XML.
*
* @author Sylvain CUAZ
* @see #get(String)
*/
public class OOXML {
private static final Map instances = LazyMap.decorate(new HashMap(), new Transformer() {
public Object transform(Object input) {
return new OOXML((XMLVersion) input);
}
});
/**
* Returns the instance that match the requested version.
*
* @param version the version.
* @return the corresponding instance.
*/
public static OOXML get(XMLVersion version) {
return (OOXML) instances.get(version);
}
static public final String getLineBreakS() {
return "<text:line-break/>";
}
static private final String rt2oo(String content, String tagName, String styleName) {
return content.replaceAll("\\[" + tagName + "\\]", "<text:span text:style-name=\"" + styleName + "\">").replaceAll("\\[/" + tagName + "\\]", "</text:span>");
}
/**
* Encode spaces for OpenOffice 1, and escape characters for XML.
*
* @param s a string to encode, eg "hi\n 3<4".
* @return the string encoded in XML, eg "hi<text:line-break/><text:s text:c="2"/>3<4".
* @deprecated see {@link #encodeWS(String)}
*/
static public final String encodeOOWS(final String s) {
String tmp = JDOMUtils.OUTPUTTER.escapeElementEntities(s).replaceAll("\n", getLineBreakS()).replaceAll("\t", OOXML.get(XMLVersion.OOo).getTabS());
String res = "";
// les séries de plus d'un espace consécutifs
final Pattern p = Pattern.compile(" +");
final Matcher m = p.matcher(tmp);
int lastEnd = 0;
while (m.find()) {
// c == le nombre d'espaces
res += tmp.substring(lastEnd, m.start()) + "<text:s text:c=\"" + (m.group().length()) + "\"/>";
lastEnd = m.end();
}
res += tmp.substring(lastEnd);
return res;
}
// *** instances
private final XMLVersion version;
private OOXML(XMLVersion version) {
this.version = version;
}
public final XMLVersion getVersion() {
return this.version;
}
/**
* Verify that the passed document is a valid OpenOffice.org 1 or ODF document.
*
* @param doc the xml to test.
* @return <code>null</code> if <code>doc</code> is valid, a String describing the pb otherwise.
*/
public final String isValid(Document doc) {
if (this.getVersion().equals(XMLVersion.OD))
try {
return JDOMUtils.isValid(doc, XMLConstants.RELAXNG_NS_URI, XMLSyntaxSchemaFactory.class.getName(), getClass().getResource("oofficeDTDs/OpenDocument-strict-schema-v1.1.rng"));
} catch (SAXException e) {
throw new IllegalStateException("relaxNG schemas pb", e);
}
else {
// 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 JDOMUtils.isValidDTD(doc, OOUtils.getBuilderLoadDTD());
}
}
public final Element getLineBreak() {
return new Element("line-break", getVersion().getTEXT());
}
public final Element getTab() {
return new Element(this.getVersion().equals(XMLVersion.OD) ? "tab" : "tab-stop", getVersion().getTEXT());
}
/**
* How to encode a tab.
*
* @return the xml string to encode a tab.
* @deprecated use {@link #getTab()}
*/
public final String getTabS() {
return this.getVersion().equals(XMLVersion.OD) ? "<text:tab/>" : "<text:tab-stop/>";
}
protected final List encodeRT_L(String content, Map styles) {
String res = JDOMUtils.OUTPUTTER.escapeElementEntities(content);
final Iterator iter = styles.entrySet().iterator();
while (iter.hasNext()) {
final Entry e = (Entry) iter.next();
res = rt2oo(res, (String) e.getKey(), (String) 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 <text:s c="n"/>
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
*
* <pre>
* <text:span>term<text:tab-stop/>definition</text:span>
* </pre>
*
* .
*/
public final Element encodeWS(final String s) {
return new Element("span", getVersion().getTEXT()).setContent(encodeWSasList(s));
}
private final List<Content> encodeWSasList(final String s) {
final List<Content> res = new ArrayList<Content>();
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()", XMLVersion.getVersion(elem));
} 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;
}
}