Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.
*
*/
package org.jopendocument.util;
import org.jopendocument.util.cc.IPredicate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.RandomAccess;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;
import org.jdom.Attribute;
import org.jdom.Content;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.Text;
import org.jdom.filter.Filter;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
/**
* @author ILM Informatique 26 juil. 2004
*/
public final class JDOMUtils {
public static final XMLOutputter OUTPUTTER;
private static final SAXBuilder BUILDER;
static {
final Format rawFormat = Format.getRawFormat();
// JDOM has \r\n hardcoded
rawFormat.setLineSeparator("\n");
OUTPUTTER = new XMLOutputter(rawFormat);
BUILDER = new SAXBuilder();
BUILDER.setValidation(false);
}
/**
* Analyse la chaine passée et retourne l'Element correspondant.
*
* @param xml une chaine contenant un élément XML.
* @param namespaces les namespaces utilisés dans la chaine.
* @return l'Element correspondant à la chaine passée.
* @throws JDOMException si l'xml n'est pas bien formé.
*/
public static Element parseElementString(String xml, Namespace[] namespaces) throws JDOMException {
// l'element passé est le seul enfant de dummy
// to be sure that the 0th can be cast use trim(), otherwise we might get a org.jdom.Text
return (Element) parseString(xml.trim(), namespaces).get(0);
}
/**
* Analyse la chaine passée et retourne la liste correspondante.
*
* @param xml une chaine contenant de l'XML.
* @param namespaces les namespaces utilisés dans la chaine.
* @return la liste correspondant à la chaine passée.
* @throws JDOMException si l'xml n'est pas bien formé.
*/
public static List parseString(String xml, Namespace[] namespaces) throws JDOMException {
// construit le dummy pour déclarer les namespaces
String dummy = "" + xml + "";
return parseStringDocument(xml).getRootElement().removeContent();
}
/**
* Analyse la chaine passée et retourne l'Element correspondant.
*
* @param xml une chaine contenant de l'XML.
* @return l'Element correspondant à la chaine passée.
* @throws JDOMException si l'xml n'est pas bien formé.
* @see #parseElementString(String, Namespace[])
*/
public static Element parseString(String xml) throws JDOMException {
return parseElementString(xml, new Namespace[0]);
}
/**
* Analyse la chaine passée avec un builder par défaut et retourne le Document correspondant.
*
* @param xml une chaine représentant un document XML.
* @return le document correspondant.
* @throws JDOMException si l'xml n'est pas bien formé.
* @see #parseStringDocument(String, SAXBuilder)
*/
public static synchronized Document parseStringDocument(String xml) throws JDOMException {
// BUILDER is not thread safe
return parseStringDocument(xml, BUILDER);
}
/**
* Analyse la chaine passée et retourne le Document correspondant.
*
* @param xml une chaine représentant un document XML.
* @param builder le builder à utiliser.
* @return le document correspondant.
* @throws JDOMException si l'xml n'est pas bien formé.
*/
public static Document parseStringDocument(String xml, SAXBuilder builder) throws JDOMException {
Document doc = null;
try {
doc = builder.build(new StringReader(xml));
} catch (IOException e) {
// peut pas arriver, lis depuis une String
e.printStackTrace();
}
return doc;
}
/**
* Ecrit l'XML en chaine, contrairement a toString().
*
* @param xml l'élément à écrire.
* @return l'XML en tant que chaine.
*/
public static String output(Element xml) {
return OUTPUTTER.outputString(xml);
}
/**
* Ecrit l'XML en chaine, contrairement a toString().
*
* @param xml l'élément à écrire.
* @return l'XML en tant que chaine.
*/
public static String output(Document xml) {
return OUTPUTTER.outputString(xml);
}
public static Element getAncestor(Element element, final String name, final Namespace ns) {
return getAncestor(element, new IPredicate() {
@Override
public boolean evaluateChecked(Element elem) {
return elem.getName().equals(name) && elem.getNamespace().equals(ns);
}
});
}
public static Element getAncestor(Element element, final IPredicate pred) {
Element current = element;
while (current != null) {
if (pred.evaluateChecked(current))
return current;
current = current.getParentElement();
}
return null;
}
/**
* Add namespace declaration to elem if needed. Necessary since JDOM uses a simple
* list.
*
* @param elem the element where namespaces should be available.
* @param c the namespaces to add.
* @see Element#addNamespaceDeclaration(Namespace)
*/
public static void addNamespaces(final Element elem, final Collection c) {
if (c instanceof RandomAccess && c instanceof List) {
final List list = (List) c;
final int stop = c.size() - 1;
for (int i = 0; i < stop; i++) {
final Namespace ns = list.get(i);
if (elem.getNamespace(ns.getPrefix()) == null)
elem.addNamespaceDeclaration(ns);
}
} else {
for (final Namespace ns : c) {
if (elem.getNamespace(ns.getPrefix()) == null)
elem.addNamespaceDeclaration(ns);
}
}
}
public static void addNamespaces(final Element elem, final Namespace... l) {
for (final Namespace ns : l) {
if (elem.getNamespace(ns.getPrefix()) == null)
elem.addNamespaceDeclaration(ns);
}
}
/**
* Get the requested child of parent or create one if necessary. The created child
* is {@link Element#addContent(Content) added at the end}.
*
* @param parent the parent.
* @param name the name of the requested child.
* @param ns the namespace of the requested child.
* @return an existing or new child of parent.
* @see Element#getChild(String, Namespace)
*/
public static Element getOrCreateChild(final Element parent, final String name, final Namespace ns) {
return getOrCreateChild(parent, name, ns, -1);
}
public static Element getOrCreateChild(final Element parent, final String name, final Namespace ns, final int index) {
Element res = parent.getChild(name, ns);
if (res == null) {
res = new Element(name, ns);
if (index < 0)
parent.addContent(res);
else
parent.addContent(index, res);
}
assert res.getParent() == parent;
return res;
}
/**
* Aka mkdir -p.
*
* @param current l'élément dans lequel créer la hierarchie.
* @param path le chemin des éléments à créer, chaque niveau séparé par "/".
* @return le dernier élément créé.
*/
public Element mkElem(Element current, String path) {
String[] items = path.split("/");
for (int i = 0; i < items.length; i++) {
String item = items[i];
String[] qname = item.split(":");
final Element elem;
if (qname.length == 1)
elem = new Element(item);
else
// MAYBE check if getNS return null and throw exn
elem = new Element(qname[1], current.getNamespace(qname[0]));
current.addContent(elem);
current = elem;
}
return current;
}
public static void insertAfter(final Element insertAfter, final Collection toAdd) {
insertSiblings(insertAfter, toAdd, true);
}
public static void insertBefore(final Element insertBefore, final Collection toAdd) {
insertSiblings(insertBefore, toAdd, false);
}
/**
* Add content before or after an element.
*
* @param sibling an element with a parent.
* @param toAdd the content to add alongside sibling.
* @param after true to add it after sibling.
*/
public static void insertSiblings(final Element sibling, final Collection toAdd, final boolean after) {
final Element parentElement = sibling.getParentElement();
final int index = parentElement.indexOf(sibling);
parentElement.addContent(after ? index + 1 : index, toAdd);
}
/**
* Test if two elements have the same namespace and name.
*
* @param elem1 an element, can be null.
* @param elem2 an element, can be null.
* @return true if both elements have the same name and namespace, or if both are
* null.
*/
public static boolean equals(Element elem1, Element elem2) {
if (elem1 == elem2 || elem1 == null && elem2 == null)
return true;
else if (elem1 == null || elem2 == null)
return false;
else
return elem1.getName().equals(elem2.getName()) && elem1.getNamespace().equals(elem2.getNamespace());
}
/**
* Compare two elements and their descendants (only Element and Text). Texts are merged and
* normalized.
*
* @param elem1 first element.
* @param elem2 second element.
* @return true if both elements are equal.
* @see #getContent(Element, IPredicate, boolean)
*/
public static boolean equalsDeep(Element elem1, Element elem2) {
return equalsDeep(elem1, elem2, true);
}
public static boolean equalsDeep(Element elem1, Element elem2, final boolean normalizeText) {
return getDiff(elem1, elem2, normalizeText) == null;
}
static String getDiff(Element elem1, Element elem2, final boolean normalizeText) {
if (elem1 == elem2)
return null;
if (!equals(elem1, elem2))
return "element name or namespace";
// ignore attributes order
@SuppressWarnings("unchecked")
final List attr1 = elem1.getAttributes();
@SuppressWarnings("unchecked")
final List attr2 = elem2.getAttributes();
if (attr1.size() != attr2.size())
return "attributes count";
for (final Attribute attr : attr1) {
if (!attr.getValue().equals(elem2.getAttributeValue(attr.getName(), attr.getNamespace())))
return "attribute value";
}
// use content order
final IPredicate filter = new IPredicate() {
@Override
public boolean evaluateChecked(Content input) {
return input instanceof Text || input instanceof Element;
}
};
// only check Element and Text (also merge them)
final Iterator contents1 = getContent(elem1, filter, true);
final Iterator contents2 = getContent(elem2, filter, true);
while (contents1.hasNext() && contents2.hasNext()) {
final Content content1 = contents1.next();
final Content content2 = contents2.next();
if (content1.getClass() != content2.getClass())
return "content";
if (content1 instanceof Text) {
final String s1 = normalizeText ? ((Text) content1).getTextNormalize() : content1.getValue();
final String s2 = normalizeText ? ((Text) content2).getTextNormalize() : content2.getValue();
if (!s1.equals(s2))
return "text";
} else {
final String rec = getDiff((Element) content1, (Element) content2, normalizeText);
if (rec != null)
return rec;
}
}
if (contents1.hasNext() || contents2.hasNext())
return "content size";
return null;
}
/**
* Get the filtered content of an element, optionnaly merging adjacent {@link Text}. Adjacent
* text can only happen programmatically.
*
* @param elem the parent.
* @param pred which content to return.
* @param mergeText true if adjacent Text should be merged into one,
* false to leave the list as it is.
* @return the filtered content (not supportting {@link Iterator#remove()}).
*/
public static Iterator getContent(final Element elem, final IPredicate pred, final boolean mergeText) {
@SuppressWarnings("unchecked")
final Iterator iter = (Iterator) elem.getContent(new Filter() {
@Override
public boolean matches(Object obj) {
return pred.evaluateChecked((Content) obj);
}
}).iterator();
if (!mergeText)
return iter;
return new Iterator() {
private Content next = null;
@Override
public boolean hasNext() {
return this.next != null || iter.hasNext();
}
@Override
public Content next() {
if (this.next != null) {
final Content res = this.next;
this.next = null;
return res;
}
Content res = iter.next();
assert res != null;
if (res instanceof Text && iter.hasNext()) {
this.next = iter.next();
Text concatText = null;
while (this.next instanceof Text) {
if (concatText == null) {
concatText = new Text(res.getValue());
}
concatText.append((Text) this.next);
this.next = iter.hasNext() ? iter.next() : null;
}
assert this.next != null;
if (concatText != null)
res = concatText;
}
return res;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
// @return SAXException If a SAX error occurs during parsing of doc.
static SAXException validate(final Document doc, final Schema schema, final ErrorHandler errorHandler) {
ByteArrayInputStream ins;
try {
ins = new ByteArrayInputStream(output(doc).getBytes("UTF8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("unicode not found ", e);
}
final Validator validator = schema.newValidator();
// ATTN workaround : contrary to documentation setting to null swallow exceptions
if (errorHandler != null)
validator.setErrorHandler(errorHandler);
try {
// don't use JDOMSource since it's as inefficient as this plus we can't control the
// output.
validator.validate(new StreamSource(ins));
return null;
} catch (IOException e) {
throw new IllegalStateException("Unable to read the document", e);
} catch (SAXException e) {
return e;
}
}
static void validateDTD(final Document doc, final SAXBuilder b, final ErrorHandler errorHandler) throws JDOMException {
final ErrorHandler origEH = b.getErrorHandler();
final boolean origValidation = b.getValidation();
try {
b.setErrorHandler(errorHandler);
b.setValidation(true);
JDOMUtils.parseStringDocument(output(doc), b);
} finally {
b.setErrorHandler(origEH);
b.setValidation(origValidation);
}
}
}