/*******************************************************************************
* Copyright (c) 2007-2009, D. Lutz and Elexis
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* D. Lutz - initial implementation
* G. Weirich - additional methods
* medshare GmbH - XML validator
*
*******************************************************************************/
package ch.rgw.tools;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import javax.xml.transform.Source;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import ch.rgw.crypt.Base64Coder;
/**
* This class provides various helper methods for handling XML data.
*
* @author danlutz
*/
public class XMLTool {
static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; //$NON-NLS-1$
public static List<String> validateSchema(String schemaUrl, Source source){
MyErrorHandler errorHandler = new MyErrorHandler();
try {
// 1. Lookup a factory for the W3C XML Schema language
SchemaFactory factory = SchemaFactory.newInstance(W3C_XML_SCHEMA);
// 2. Compile the schema.
// Here the schema is loaded from a java.io.File, but you could use
// a java.net.URL or a javax.xml.transform.Source instead.
Schema schema = factory.newSchema();
if (schemaUrl != null) {
File schemaLocation = new File(schemaUrl);
schema = factory.newSchema(schemaLocation);
}
// 3. Get a validator from the schema.
Validator validator = schema.newValidator();
// 5. Check the document
validator.setErrorHandler(errorHandler);
validator.validate(source);
} catch (Exception ex) {
errorHandler.exception(ex);
}
return errorHandler.getMessageList();
}
private static class MyErrorHandler implements ErrorHandler {
public List<Exception> exceptions = new Vector<Exception>();
@Override
public void error(SAXParseException exception) throws SAXException{
exceptions.add(exception);
}
@Override
public void fatalError(SAXParseException exception) throws SAXException{
exceptions.add(exception);
}
@Override
public void warning(SAXParseException exception) throws SAXException{
// Nothing
}
public void exception(Exception exception){
// Nothing this is not an xml related error
}
public List<String> getMessageList(){
List<String> messageList = new Vector<String>();
for (Exception ex : exceptions) {
String msg = ex.getMessage();
if (msg == null || msg.length() == 0) {
msg = ex.toString();
}
messageList.add(msg);
}
return messageList;
}
}
public static String moneyToXmlDouble(Money money){
int cents = money.getCents();
// we force to use a literal "."
// honor signum
int absCents = Math.abs(cents);
int signum = Integer.signum(cents);
int abs = absCents / 100;
int frac = absCents % 100;
String xmlDouble = String.format("%d.%02d", signum * abs, frac);
return xmlDouble;
}
public static Money xmlDoubleToMoney(String xmlDouble) throws NumberFormatException{
if (xmlDouble == null) {
throw new NumberFormatException("xmlDouble must not be null");
}
Double d = Double.parseDouble(xmlDouble);
return new Money(d);
}
/**
* Convert a double value to String conforming to the double datatype of the XML specification.
* (This means mainly, that we have to use the swiss "." also if the PC's locale is set to
* germany or austria.)
*
* @param value
* the value to be converted
* @param factionalDigits
* the number of digits after the point
* @return the formated String
*/
public static String doubleToXmlDouble(double value, int factionalDigits){
long cents = Math.round(value * 100);
// we force to use a literal "."
/*
* String xmlDouble = String.format("%d.%02d", cents / 100, cents % 100);
*
* return xmlDouble;
*/
// honor signum
int absCents = Math.abs((int) cents);
int signum = Integer.signum((int) cents);
int abs = absCents / 100;
int frac = absCents % 100;
String dec = "%d.%0" + Integer.toString(factionalDigits) + "d";
// The - sign is lost if the integer is 0
if ((abs == 0) && (signum < 0)) {
dec = "-" + dec;
}
String xmlDouble = String.format(dec, signum * abs, frac);
return xmlDouble;
}
/**
* Convert a XML-Table formatted like <table> <row> <col1>Col 1<col1/>
* <col2>Col 2<col2/> </row> <row> ... </row> </table> to a
* csv table
*
* @param table
* the table to convert
* @param separator
* String that separates columns
* @return a string containing the csv table. Rows separated by \n, colums separated by
* separator
*/
@SuppressWarnings("unchecked")
public static String XMLTableToCSVTable(Element table, String separator){
List<Element> rows = table.getChildren();
StringBuilder ret = new StringBuilder();
for (Element row : rows) {
List<Element> cols = row.getChildren();
for (Element col : cols) {
ret.append(col.getText()).append(separator);
}
ret.replace(ret.length() - separator.length(), ret.length(), "\n");
}
return ret.toString();
}
/**
* Convert a XML-Table formatted like <table> <row> <col1>Col 1<col1/>
* <col2>Col 2<col2/> </row> <row> ... </row> </table> to a
* html table
*
* @param table
* the table to convert
* @return a string containing the html table.
*/
@SuppressWarnings("unchecked")
public static String XMLTableToHTMLTable(Element table){
List<Element> rows = table.getChildren();
StringBuilder ret = new StringBuilder();
ret.append("<table>");
for (Element row : rows) {
ret.append("<tr>");
List<Element> cols = row.getChildren();
for (Element col : cols) {
ret.append("<td>").append(col.getText()).append("</td>");
}
ret.append("</tr>");
}
ret.append("</table>");
return ret.toString();
}
/**
* Conversion betweeen Elexis id's and XML ID types. XML id types must not begin with a number
* but may contain letters and numbers. Elexis ID's are always hexadecimal strings thus will
* never contain a letter other than a-f but might start with a number. Thus if it starts with a
* number, we prefix an "x"
*
* @param id
* an elexis id
* @return a String conforming to tghe XML ID type
*/
public static String idToXMLID(String id){
if (id != null) {
if (id.matches("[0-9].+")) {
return "x" + id;
}
}
return id;
}
/**
* Since elexis id's never contain the letter "x" we can be sure that a starting letter x can be
* removed to leave us with the original elexis id
*
* @param xmlid
* an XML ID
* @return the conforming elexis id
*/
public static String xmlIDtoID(String xmlid){
if (xmlid != null) {
if (xmlid.startsWith("x")) {
return xmlid.substring(1);
}
}
return xmlid;
}
/**
* Convert a TimeTool into an XML dateTime type
*
* @param dateTime
* @return
*/
public static String dateTimeToXmlDateTime(String dateTime){
TimeTool tt = new TimeTool(dateTime);
return tt.toString(TimeTool.DATETIME_XML);
}
/**
* Copnvert a date part of a TimeTool to an XML date type
*
* @param date
* @return
*/
public static String dateToXmlDate(String date){
return new TimeTool(date).toString(TimeTool.DATE_ISO);
}
/**
* Convert a HashMap of String/Object pairs into a SOAP compatible XML structure. Ad this time,
* only String, int, long, byte, byte[] and Hashmaps thereof are supported as Object types
*
* @param hash
* @param name
* @param ns
* @return
*/
public static Element HashMapToXML(HashMap<String, Object> hash, String name, Namespace ns){
Element ret = new Element("hash", ns);
ret.setAttribute("name", name);
Set<Entry<String, Object>> vars = hash.entrySet();
for (Entry<String, Object> entry : vars) {
Element var;
String n = entry.getKey();
Object o = entry.getValue();
if (o instanceof String) {
var = new Element("string", ns);
var.setAttribute("name", n);
var.setText((String) o);
} else if ((o instanceof Integer) || (o instanceof Long) || (o instanceof Short)
|| (o instanceof Byte)) {
var = new Element("int", ns);
var.setAttribute("name", "n");
var.setText(o.toString());
} else if (o instanceof HashMap) {
var = HashMapToXML((HashMap) o, n, ns);
} else if (o instanceof byte[]) {
var = new Element("array", ns);
var.setAttribute("name", n);
var.setText(new String(Base64Coder.encode((byte[]) o)));
} else {
var = null;
}
if (var == null) {
return null;
}
ret.addContent(var);
}
return ret;
}
public static HashMap<String, Object> XMLToHashMap(Element elem){
HashMap<String, Object> ret = new HashMap<String, Object>();
List<Element> vars = elem.getChildren();
for (Element var : vars) {
String type = var.getName();
}
return null;
}
public static boolean writeXMLDocument(Document doc, String dest){
try {
FileOutputStream fout = new FileOutputStream(dest);
OutputStreamWriter cout = new OutputStreamWriter(fout, "UTF-8");
XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
xout.output(doc, cout);
cout.close();
fout.close();
return true;
} catch (Exception e) {
ExHandler.handle(e);
return false;
}
}
}