/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.util;
import gov.nasa.worldwind.avlist.AVList;
import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.layers.mercator.MercatorSector;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.WWXML;
import java.awt.Color;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map.Entry;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import au.gov.ga.earthsci.worldwind.common.util.transform.URLTransformer;
/**
* An extension of the World-Wind provided {@link WWXML} utilities, provides
* additional helper methods for dealing with XML documents.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
* @author James Navin (james.navin@ga.gov.au)
*/
public class XMLUtil extends WWXML
{
private final static double EPSILON = 1e-10;
/**
* @return The XML element from a generic source. If the source is an
* {@link Element}, will return the provided source. Otherwise, will
* attempt to open the source as an XML {@link Document} and will
* return the document element.
*
* @see {@link WWXML#openDocument(Object)}
*/
public static Element getElementFromSource(Object source)
{
if (source == null)
{
return null;
}
if (source instanceof Element)
{
return (Element) source;
}
else if (source instanceof Document)
{
return ((Document) source).getDocumentElement();
}
else
{
Document document = openDocument(source);
if (document != null)
{
return document.getDocumentElement();
}
}
return null;
}
public static String getText(Element context, String path, String def)
{
return getText(context, path, def, null);
}
public static String getText(Element context, String path, String def, XPath xpath)
{
String s = getText(context, path, xpath);
if (s == null)
{
return def;
}
return s;
}
public static boolean getBoolean(Element context, String path, boolean def)
{
return getBoolean(context, path, def, null);
}
public static boolean getBoolean(Element context, String path, boolean def, XPath xpath)
{
Boolean b = getBoolean(context, path, xpath);
if (b == null)
{
return def;
}
return b;
}
public static double getDouble(Element context, String path, double def)
{
return getDouble(context, path, def, null);
}
public static double getDouble(Element context, String path, double def, XPath xpath)
{
Double d = getDouble(context, path, xpath);
if (d == null)
{
return def;
}
return d;
}
public static int getInteger(Element context, String path, int def)
{
return getInteger(context, path, def, null);
}
public static int getInteger(Element context, String path, int def, XPath xpath)
{
Integer i = getInteger(context, path, xpath);
if (i == null)
{
return def;
}
return i;
}
public static long getLong(Element context, String path, long def)
{
return getLong(context, path, def, null);
}
public static long getLong(Element context, String path, long def, XPath xpath)
{
Long i = getLong(context, path, xpath);
if (i == null)
{
return def;
}
return i;
}
public static MercatorSector getMercatorSector(Element context, String path, XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Element el = path == null ? context : getElement(context, path, xpath);
if (el == null)
return null;
LatLon sw = getMercatorLatLon(el, "SouthWest/LatLon", xpath);
LatLon ne = getMercatorLatLon(el, "NorthEast/LatLon", xpath);
if (sw == null || ne == null)
return null;
return new MercatorSector(MercatorSector.gudermannianInverse(sw.latitude),
MercatorSector.gudermannianInverse(ne.latitude), sw.longitude, ne.longitude);
}
protected static LatLon getMercatorLatLon(Element context, String path, XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
try
{
Element el = path == null ? context : getElement(context, path, xpath);
if (el == null)
return null;
String units = getText(el, "@units", xpath);
boolean degrees = units == null || "degrees".equals(units);
boolean radians = "radians".equals(units);
Double lat = getDouble(el, "@latitude", xpath);
Double latPercent = getDouble(el, "@latitudePercent", xpath);
Double lon = getDouble(el, "@longitude", xpath);
if (lat == null && latPercent != null)
{
Angle latAngle = MercatorSector.gudermannian(latPercent);
lat = radians ? latAngle.radians : latAngle.degrees;
}
if (lat == null || lon == null)
return null;
if (degrees)
return LatLon.fromDegrees(lat, lon);
if (radians)
return LatLon.fromRadians(lat, lon);
// Warn that units are not recognized
String message = Logging.getMessage("XML.UnitsUnrecognized", units);
Logging.logger().warning(message);
return null;
}
catch (NumberFormatException e)
{
String message = Logging.getMessage("generic.ConversionError", path);
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
return null;
}
}
public static URL getURL(Element element, String path, URL context) throws MalformedURLException
{
String text = getText(element, path);
return getURL(text, context);
}
public static URL getURL(Element element, String path, URL context, XPath xpath) throws MalformedURLException
{
String text = getText(element, path, xpath);
return getURL(text, context);
}
protected static URL getURL(String text, URL context) throws MalformedURLException
{
URL url = textToURL(text, context);
return URLTransformer.transform(url);
}
protected static URL textToURL(String text, URL context) throws MalformedURLException
{
if (text == null || text.length() == 0)
{
return null;
}
if (context == null)
{
return new URL(text);
}
return new URL(context, text);
}
public static File getFile(Element element, String path)
{
try
{
URL url = getURL(element, path, null);
if (url == null)
{
return null;
}
return new File(url.toURI());
}
catch (Exception e)
{
return null;
}
}
public static Element appendColor(Element context, String path, Color color)
{
Element element = WWXML.appendElement(context, path);
WWXML.setIntegerAttribute(element, "red", color.getRed());
WWXML.setIntegerAttribute(element, "green", color.getGreen());
WWXML.setIntegerAttribute(element, "blue", color.getBlue());
WWXML.setIntegerAttribute(element, "alpha", color.getAlpha());
return element;
}
public static Position getPosition(Element context, String path, XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
try
{
Element el = path == null ? context : getElement(context, path, xpath);
if (el == null)
{
return null;
}
String units = getText(el, "@units", xpath);
Double lat = getDouble(el, "@latitude", xpath);
Double lon = getDouble(el, "@longitude", xpath);
Double elev = getDouble(el, "@elevation", xpath);
if (lat == null || lon == null)
{
return null;
}
if (elev == null)
{
elev = 0d;
}
if (units == null || units.length() == 0 || units.equals("degrees"))
{
return Position.fromDegrees(lat, lon, elev);
}
if (units.equals("radians"))
{
return Position.fromRadians(lat, lon, elev);
}
// Warn that units are not recognized
String message = Logging.getMessage("XML.UnitsUnrecognized", units);
Logging.logger().warning(message);
return null;
}
catch (NumberFormatException e)
{
String message = Logging.getMessage("generic.ConversionError", path);
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
return null;
}
}
public static Element appendPosition(Element context, String path, Position pos)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (pos == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Element el = appendElementPath(context, path);
setTextAttribute(el, "units", "degrees");
setDoubleAttribute(el, "latitude", pos.getLatitude().degrees);
setDoubleAttribute(el, "longitude", pos.getLongitude().degrees);
setDoubleAttribute(el, "elevation", pos.getElevation());
return el;
}
public static Vec4 getVec4(Element context, String path, XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
try
{
Element el = path == null ? context : getElement(context, path, xpath);
if (el == null)
{
return null;
}
Double x = getDouble(el, "@x", xpath);
Double y = getDouble(el, "@y", xpath);
Double z = getDouble(el, "@z", xpath);
Double w = getDouble(el, "@w", xpath);
if (x == null || y == null || z == null)
{
return null;
}
if (w == null)
{
w = 1d;
}
return new Vec4(x, y, z, w);
}
catch (NumberFormatException e)
{
String message = Logging.getMessage("generic.ConversionError", path);
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
return null;
}
}
public static Element appendVec4(Element context, String path, Vec4 v)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (v == null)
{
String message = Logging.getMessage("nullValue.Vec4IsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Element el = appendElementPath(context, path);
setDoubleAttribute(el, "x", v.x);
setDoubleAttribute(el, "y", v.y);
setDoubleAttribute(el, "z", v.z);
if (v.w != 1)
{
setDoubleAttribute(el, "w", v.w);
}
return el;
}
public static Long getFormattedDate(Element context, String path, XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
try
{
Element el = path == null ? context : getElement(context, path, xpath);
if (el == null)
{
return null;
}
String value = getText(el, "@value", xpath);
String format = getText(el, "@format", xpath);
DateFormat dateFormat = new SimpleDateFormat(format);
Date date = dateFormat.parse(value);
return date.getTime();
}
catch (ParseException e)
{
String message = Logging.getMessage("generic.ConversionError", path);
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
return null;
}
}
public static ColorMap getColorMap(Element context, String path, XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (xpath == null)
{
xpath = makeXPath();
}
Element element = getElement(context, path, xpath);
if (element == null)
{
return null;
}
ColorMap colorMap = new ColorMap();
Boolean b = XMLUtil.getBoolean(element, "@interpolateHue", xpath);
if (b != null)
{
colorMap.setInterpolateHue(b);
}
b = XMLUtil.getBoolean(element, "@percentages", xpath);
if (b != null)
{
colorMap.setValuesPercentages(b);
}
Element[] mapEntries = WWXML.getElements(element, "Entry", xpath);
if (mapEntries != null)
{
for (Element entry : mapEntries)
{
Double value = WWXML.getDouble(entry, "@value", xpath);
if (value == null)
{
value = WWXML.getDouble(entry, "@elevation", xpath);
}
if (value == null)
{
continue;
}
//don't allow a duplicate key
while (colorMap.containsKey(value))
{
value += EPSILON;
}
int red = XMLUtil.getInteger(entry, "@red", 0, xpath);
int green = XMLUtil.getInteger(entry, "@green", 0, xpath);
int blue = XMLUtil.getInteger(entry, "@blue", 0, xpath);
int alpha = XMLUtil.getInteger(entry, "@alpha", 255, xpath);
Color color = new Color(red, green, blue, alpha);
colorMap.put(value, color);
}
}
return colorMap;
}
public static void appendColorMap(Element element, String path, ColorMap colorMap)
{
Element colorMapElement = appendElement(element, path);
setBooleanAttribute(colorMapElement, "interpolateHue", colorMap.isInterpolateHue());
if (colorMap.isValuesPercentages())
{
setBooleanAttribute(colorMapElement, "percentages", colorMap.isValuesPercentages());
}
for (Entry<Double, Color> colorMapEntry : colorMap.entrySet())
{
Element entryElement = appendElement(colorMapElement, "Entry");
setDoubleAttribute(entryElement, "value", colorMapEntry.getKey());
setIntegerAttribute(entryElement, "red", colorMapEntry.getValue().getRed());
setIntegerAttribute(entryElement, "green", colorMapEntry.getValue().getGreen());
setIntegerAttribute(entryElement, "blue", colorMapEntry.getValue().getBlue());
setIntegerAttribute(entryElement, "alpha", colorMapEntry.getValue().getAlpha());
}
}
public static void checkAndSetFormattedDateParam(Element context, AVList params, String paramKey, String paramName,
XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ElementIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (params == null)
{
String message = Logging.getMessage("nullValue.ParametersIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (paramKey == null)
{
String message = Logging.getMessage("nullValue.ParameterKeyIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (paramName == null)
{
String message = Logging.getMessage("nullValue.ParameterNameIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Long l = getFormattedDate(context, paramName, xpath);
if (l != null)
{
params.setValue(paramKey, l);
}
}
public static void checkAndSetURLParam(Element context, AVList params, String paramKey, String paramName,
XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ElementIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (params == null)
{
String message = Logging.getMessage("nullValue.ParametersIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (paramKey == null)
{
String message = Logging.getMessage("nullValue.ParameterKeyIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (paramName == null)
{
String message = Logging.getMessage("nullValue.ParameterNameIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Object o = params.getValue(paramKey);
if (o == null)
{
String s = getText(context, paramName, xpath);
if (s != null && s.length() > 0)
{
try
{
URL url = new URL(s);
params.setValue(paramKey, url);
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
}
}
}
public static void checkAndSetMercatorSectorParam(Element context, AVList params, String paramKey,
String paramName, XPath xpath)
{
if (context == null)
{
String message = Logging.getMessage("nullValue.ElementIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (params == null)
{
String message = Logging.getMessage("nullValue.ParametersIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (paramKey == null)
{
String message = Logging.getMessage("nullValue.ParameterKeyIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (paramName == null)
{
String message = Logging.getMessage("nullValue.ParameterNameIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Object o = params.getValue(paramKey);
if (o == null)
{
Sector sector = getMercatorSector(context, paramName, xpath);
if (sector != null)
params.setValue(paramKey, sector);
}
}
/**
* Saves the provided XML document to the provided file location in a
* pretty-printed, human readable, indented format.
* <p/>
* This method will overwrite any existing contents in the provided file
* location.
*
* @param doc
* The xml document to save
* @param filePath
* The location to save to. Will overwrite any existing file.
*/
public static void saveDocumentToFormattedFile(Document doc, String filePath)
{
if (doc == null)
{
String message = Logging.getMessage("nullValue.DocumentIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (filePath == null)
{
String message = Logging.getMessage("nullValue.FilePathIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
try
{
FileOutputStream outputStream = new FileOutputStream(filePath);
saveDocumentToFormattedStream(doc, outputStream);
}
catch (Exception e)
{
String message = Logging.getMessage("generic.ExceptionAttemptingToWriteXml", filePath);
Logging.logger().severe(message);
throw new WWRuntimeException(e);
}
}
/**
* Saves the provided XML document to the provided output stream in a
* pretty-printed, human readable, indented format.
*
* @param doc
* The xml document to save
* @param outputStream
* The output stream to write the output to
*/
public static void saveDocumentToFormattedStream(Document doc, OutputStream outputStream)
{
if (doc == null)
{
String message = Logging.getMessage("nullValue.DocumentIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (outputStream == null)
{
String message = Logging.getMessage("nullValue.FilePathIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
try
{
Source source = new DOMSource(doc);
Result result = new StreamResult(outputStream);
Transformer transformer = createTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "4");
transformer.transform(source, result);
outputStream.close();
}
catch (Exception e)
{
String message = Logging.getMessage("generic.ExceptionAttemptingToWriteXml");
Logging.logger().severe(message);
throw new WWRuntimeException(e);
}
}
}