#region Disclaimer / License // Copyright (C) 2009, Kenneth Skovhede // http://www.hexad.dk, opensource@hexad.dk // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Drawing; using Topology.Geometries; using System.Collections.Generic; namespace OSGeo.MapGuide.MaestroAPI { /// /// Various helper functions /// public class Utility { //Americans NEVER obey nationalization when outputting decimal values, so the rest of the world always have to work around their bugs :( private static System.Globalization.CultureInfo m_enCI = new System.Globalization.CultureInfo("en-US"); /// /// Parses a color in HTML notation (ea. #ffaabbff) /// /// The HTML representation of the color /// The .Net color structure that matches the color public static Color ParseHTMLColor(string color) { if (color.Length == 8) { int a = int.Parse(color.Substring(0,2), System.Globalization.NumberStyles.HexNumber); int r = int.Parse(color.Substring(2,2), System.Globalization.NumberStyles.HexNumber); int g = int.Parse(color.Substring(4,2), System.Globalization.NumberStyles.HexNumber); int b = int.Parse(color.Substring(6,2), System.Globalization.NumberStyles.HexNumber); return Color.FromArgb(a, r,g,b); } else if (color.Length == 6) { int r = int.Parse(color.Substring(0,2), System.Globalization.NumberStyles.HexNumber); int g = int.Parse(color.Substring(2,2), System.Globalization.NumberStyles.HexNumber); int b = int.Parse(color.Substring(4,2), System.Globalization.NumberStyles.HexNumber); return Color.FromArgb(r,g,b); } else throw new Exception("Bad HTML color: \"" + color + "\""); } /// /// Returns the HTML representation of an .Net color structure /// /// The color to encode /// A flag indicating if the color structures alpha value should be included /// The HTML representation of the color structure public static string SerializeHTMLColor(Color color, bool includeAlpha) { string res = ""; if (includeAlpha) res += color.A.ToString("x02"); res += color.R.ToString("x02"); res += color.G.ToString("x02"); res += color.B.ToString("x02"); return res; } /// /// Parses a string with a decimal value in EN-US format /// /// The string value /// The parsed value public static float ParseDigit(string digit) { return (float)double.Parse(digit, m_enCI); } /// /// Turns a decimal value into a string representation in EN-US format /// /// The value to encode /// The encoded value public static string SerializeDigit(float digit) { return digit.ToString(m_enCI); } /// /// Turns a decimal value into a string representation in EN-US format /// /// The value to encode /// The encoded value public static string SerializeDigit(double digit) { return digit.ToString(m_enCI); } /// /// Copies the content of a stream into another stream. /// Automatically attempts to rewind the source stream. /// /// The source stream /// The target stream public static void CopyStream(System.IO.Stream source, System.IO.Stream target) { CopyStream(source, target, true); } /// /// Copies the content of a stream into another stream. /// /// The source stream /// The target stream /// True if the source stream should be rewound before being copied public static void CopyStream(System.IO.Stream source, System.IO.Stream target, bool rewind) { int r; byte[] buf = new byte[1024]; if (rewind && source.CanSeek) try { source.Position = 0; } catch { } do { r = source.Read(buf, 0, buf.Length); target.Write(buf, 0, r); } while (r > 0); } /// /// A delegate used to update a progress bar while copying a stream. /// /// The number of bytes copied so far /// The number of bytes left in the stream. -1 if the stream length is not known /// The total number of bytes in the source stream. -1 if the stream length is unknown public delegate void StreamCopyProgressDelegate(long copied, long remaining, long total); /// /// Copies the content of a stream into another stream, with callbacks. /// /// The source stream /// The target stream /// An optional callback delegate, may be null. /// The number of bytes to copy before calling the callback delegate, set to 0 to get every update public static void CopyStream(System.IO.Stream source, System.IO.Stream target, StreamCopyProgressDelegate callback, long updateFrequence) { long length = -1; try { length = source.Length; } catch { } long copied = 0; long freqCount = 0; if (callback != null) callback(copied, length > 0 ? (length - copied) : -1 , length); int r; byte[] buf = new byte[1024]; do { r = source.Read(buf, 0, buf.Length); target.Write(buf, 0, r); copied += r; freqCount += r; if (freqCount > updateFrequence) { freqCount = 0; if (callback != null) callback(copied, length > 0 ? (length - copied) : -1, length); } } while (r > 0); if (callback != null) callback(copied, 0, copied); } /// /// Builds a copy of the object by serializing it to xml, and then deserializing it. /// Please note that this function is has a large overhead. /// /// The object to copy /// A copy of the object public static object XmlDeepCopy(object source) { if (source == null) return null; System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(source.GetType()); System.IO.MemoryStream ms = new System.IO.MemoryStream(); ser.Serialize(ms, source); ms.Position = 0; return ser.Deserialize(ms); } /// /// Makes a deep copy of an object, by copying all the public properties. /// This overload tries to maintain object references by assigning properties /// /// The object to copy /// The object to assign to /// A copied object public static object DeepCopy(object source, object target) { foreach(System.Reflection.PropertyInfo pi in source.GetType().GetProperties()) { if (!pi.CanRead || !pi.CanWrite) continue; if (!pi.PropertyType.IsClass || pi.PropertyType == typeof(string) ) pi.SetValue(target, pi.GetValue(source, null) , null); else if (pi.GetValue(source, null) == null) pi.SetValue(target, null, null); else if (pi.GetValue(source, null).GetType().GetInterface(typeof(System.Collections.ICollection).FullName) != null) { System.Collections.ICollection srcList = (System.Collections.ICollection)pi.GetValue(source, null); System.Collections.ICollection trgList = (System.Collections.ICollection)Activator.CreateInstance(srcList.GetType()); foreach(object o in srcList) trgList.GetType().GetMethod("Add").Invoke(trgList, new object[] { DeepCopy(o) } ); pi.SetValue(target, trgList, null); } else if (pi.GetValue(source, null).GetType().IsArray) { System.Array sourceArr = (System.Array)pi.GetValue(source, null); System.Array targetArr = (System.Array)Activator.CreateInstance(sourceArr.GetType(), new object[] { sourceArr.Length }); for(int i = 0; i < targetArr.Length; i++) targetArr.SetValue(DeepCopy(sourceArr.GetValue(i)), i); pi.SetValue(target, targetArr, null); } else { if (pi.GetValue(target, null) == null) pi.SetValue(target, Activator.CreateInstance(pi.GetValue(source, null).GetType()), null); DeepCopy(pi.GetValue(source, null), pi.GetValue(target, null)); } } return target; } /// /// Makes a deep copy of an object, by copying all the public properties /// /// The object to copy /// A copied object public static object DeepCopy(object source) { if (source == null) return null; object target = Activator.CreateInstance(source.GetType()); foreach(System.Reflection.PropertyInfo pi in source.GetType().GetProperties()) { if (!pi.CanRead || !pi.CanWrite) continue; if (!pi.PropertyType.IsClass || pi.PropertyType == typeof(string) ) pi.SetValue(target, pi.GetValue(source, null) , null); else if (pi.GetValue(source, null) == null) pi.SetValue(target, null, null); else if (pi.GetValue(source, null).GetType().GetInterface(typeof(System.Collections.ICollection).FullName) != null) { System.Collections.ICollection srcList = (System.Collections.ICollection)pi.GetValue(source, null); System.Collections.ICollection trgList = (System.Collections.ICollection)Activator.CreateInstance(srcList.GetType()); foreach(object o in srcList) trgList.GetType().GetMethod("Add").Invoke(trgList, new object[] { DeepCopy(o) } ); pi.SetValue(target, trgList, null); } else if (pi.GetValue(source, null).GetType().IsArray) { System.Array sourceArr = (System.Array)pi.GetValue(source, null); System.Array targetArr = (System.Array)Activator.CreateInstance(sourceArr.GetType(), new object[] { sourceArr.Length }); for(int i = 0; i < targetArr.Length; i++) targetArr.SetValue(DeepCopy(sourceArr.GetValue(i)), i); pi.SetValue(target, targetArr, null); } else pi.SetValue(target, DeepCopy(pi.GetValue(source, null)), null); } return target; } /// /// Converts a MgStream to a .Net Stream object. /// Due to some swig issues, it is not possible to pass on an MgStream to a function, /// so this function calls a method to retrieve the stream locally. /// /// The object which has a stream /// The method to call /// Arguments to the method /// A memorystream with the MgStream's content public static System.IO.MemoryStream MgStreamToNetStream(object source, System.Reflection.MethodInfo mi, object[] args) { try { OSGeo.MapGuide.MgByteReader rd = (OSGeo.MapGuide.MgByteReader)mi.Invoke(source, args); System.IO.MemoryStream ms = new System.IO.MemoryStream(); byte[] buf = new byte[1024]; int c = 0; do { c = rd.Read(buf, buf.Length); ms.Write(buf, 0, c); } while (c != 0); ms.Position = 0; return ms; } catch (System.Reflection.TargetInvocationException tex) { if (tex.InnerException != null) throw tex.InnerException; else throw; } } /// /// Reads all data from a stream, and returns it as a single array. /// Note that this is very inefficient if the stream is several megabytes long. /// /// The stream to exhaust /// The streams content as an array public static byte[] StreamAsArray(System.IO.Stream s) { if (s as System.IO.MemoryStream != null) return ((System.IO.MemoryStream)s).ToArray(); if (!s.CanSeek) { System.IO.MemoryStream ms = new System.IO.MemoryStream(); byte[] buf = new byte[1024]; int c; while((c = s.Read(buf, 0, buf.Length)) > 0) ms.Write(buf, 0, c); return ms.ToArray(); } else { byte[] buf = new byte[s.Length]; s.Position = 0; s.Read(buf, 0, buf.Length); return buf; } } /// /// Creates a copy of the stream, with removed Utf8 BOM, if any /// /// The stream to fix /// A stream with no Utf8 BOM public static System.IO.MemoryStream RemoveUTF8BOM(System.IO.MemoryStream ms) { //Skip UTF file header, since the MapGuide XmlParser is broken ms.Position = 0; byte[] utfheader = new byte[3]; if (ms.Read(utfheader, 0, utfheader.Length) == utfheader.Length) if (utfheader[0] == 0xEF && utfheader[1] == 0xBB && utfheader[2] == 0xBF) { ms.Position = 3; System.IO.MemoryStream mxs = new System.IO.MemoryStream(); Utility.CopyStream(ms, mxs, false); mxs.Position = 0; return mxs; } ms.Position = 0; return ms; } /// /// Gets the type of an item, givne the MapGuide type id /// /// The MapGuide type id /// The corresponding .Net type public static Type ConvertMgTypeToNetType(int MgType) { switch(MgType) { case OSGeo.MapGuide.MgPropertyType.Byte: return typeof(byte); case OSGeo.MapGuide.MgPropertyType.Int16: return typeof(short); case OSGeo.MapGuide.MgPropertyType.Int32: return typeof(int); case OSGeo.MapGuide.MgPropertyType.Int64: return typeof(long); case OSGeo.MapGuide.MgPropertyType.Single: return typeof(float); case OSGeo.MapGuide.MgPropertyType.Double: return typeof(double); case OSGeo.MapGuide.MgPropertyType.Boolean: return typeof(bool); case OSGeo.MapGuide.MgPropertyType.Geometry: return Utility.GeometryType; case OSGeo.MapGuide.MgPropertyType.String: return typeof(string); case OSGeo.MapGuide.MgPropertyType.DateTime: return typeof(DateTime); case OSGeo.MapGuide.MgPropertyType.Raster: return Utility.RasterType; case OSGeo.MapGuide.MgPropertyType.Blob: return typeof(byte[]); case OSGeo.MapGuide.MgPropertyType.Clob: return typeof(byte[]); default: throw new Exception("Failed to find type for: " + MgType.ToString()); } } /// /// Gets the MapGuide id for a given type /// /// The .Net type /// The corresponding MapGuide type id public static int ConvertNetTypeToMgType(Type type) { if (type == typeof(short)) return OSGeo.MapGuide.MgPropertyType.Int16; else if (type == typeof(byte)) return OSGeo.MapGuide.MgPropertyType.Byte; else if (type == typeof(bool)) return OSGeo.MapGuide.MgPropertyType.Boolean; else if (type == typeof(int)) return OSGeo.MapGuide.MgPropertyType.Int32; else if (type == typeof(long)) return OSGeo.MapGuide.MgPropertyType.Int64; else if (type == typeof(float)) return OSGeo.MapGuide.MgPropertyType.Single; else if (type == typeof(double)) return OSGeo.MapGuide.MgPropertyType.Double; else if (type == Utility.GeometryType) return OSGeo.MapGuide.MgPropertyType.Geometry; else if (type == typeof(string)) return OSGeo.MapGuide.MgPropertyType.String; else if (type == typeof(DateTime)) return OSGeo.MapGuide.MgPropertyType.DateTime; else if (type == Utility.RasterType) return OSGeo.MapGuide.MgPropertyType.Raster; else if (type == typeof(byte[])) return OSGeo.MapGuide.MgPropertyType.Blob; throw new Exception("Failed to find type for: " + type.FullName.ToString()); } /// /// Returns a type used to define an unknown column type in a feature reader /// public static Type UnmappedType { get { return typeof(UnmappedDataType); } } /// /// Returns a type used to define a raster column in a feature reader /// public static Type RasterType { get { return typeof(System.Drawing.Bitmap); } } /// /// Returns the type used to define a geometry column in a feature reader /// public static Type GeometryType { get { return typeof(Topology.Geometries.IGeometry); } } /// /// This method tries to extract the html content of a WebException. /// If succesfull, it will return an exception with an error message corresponding to the html text. /// If not, it will return the original exception object. /// /// The exception object to extract from /// A potentially better exeception public static Exception ThrowAsWebException(Exception ex) { if (ex as System.Net.WebException != null) { try { System.Net.WebException wex = ex as System.Net.WebException; using (System.IO.StreamReader sr = new System.IO.StreamReader(wex.Response.GetResponseStream())) { string html = sr.ReadToEnd(); System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex("(\\)(.+)\\<\\/body\\>", System.Text.RegularExpressions.RegexOptions.Singleline); System.Text.RegularExpressions.Match m = r.Match(html); if (m.Success && m.Groups.Count == 3) { html = m.Groups[2].Value; int n = html.IndexOf(""); if (n > 0) html = html.Substring(n + "".Length); } return new Exception(wex.Message + ": " + html, wex); } } catch { } } return ex; } /// /// Removes the outer <FeatureInformation> tag, and returns a blank string for empty sets. /// This eases the use of SelectionXml, because different parts of MapGuide represents it differently. /// /// The string to clean /// The cleaned string public static string CleanUpFeatureSet(string input) { if (string.IsNullOrEmpty(input)) return input; System.Xml.XmlDocument doc1 = new System.Xml.XmlDocument(); doc1.LoadXml(input); System.Xml.XmlDocument doc2 = new System.Xml.XmlDocument(); System.Xml.XmlNode root1 = doc1["FeatureInformation"]; if (root1 == null) root1 = doc1; if (root1["FeatureSet"] != null) { System.Xml.XmlNode root2 = doc2.AppendChild(doc2.CreateElement("FeatureSet")); root2.InnerXml = root1["FeatureSet"].InnerXml; } return doc2.OuterXml == "" ? "" : doc2.OuterXml; } private static void CopyNodeRecursive(System.Xml.XmlNode n1, System.Xml.XmlNode n2) { foreach (System.Xml.XmlAttribute attr in n1.Attributes) n2.Attributes.Append(n2.OwnerDocument.CreateAttribute(attr.Name)).Value = attr.Value; foreach (System.Xml.XmlNode n in n1.ChildNodes) CopyNodeRecursive(n, n2.AppendChild(n2.OwnerDocument.CreateElement(n.Name))); } /// /// Formats a number of bytes to a human readable format, ea.: 2.56 Kb /// /// The size in bytes /// The human readable string public static string FormatSizeString(long size) { if (size > 1024 * 1024 * 1024) return string.Format("{0:N} GB", (double)size / (1024 * 1024 * 1024)); else if (size > 1024 * 1024) return string.Format("{0:N} MB", (double)size / (1024 * 1024)); else if (size > 1024) return string.Format("{0:N} KB", (double)size / 1024); else return string.Format("{0} bytes", size); } private static System.Text.RegularExpressions.Regex EncRegExp = new System.Text.RegularExpressions.Regex(@"(\-x([0-9]|[a-e]|[A-E])([0-9]|[a-e]|[A-E])\-)|(\-dot\-)|(\-colon\-)", System.Text.RegularExpressions.RegexOptions.Compiled); /// /// Converts FDO encoded characters into their original character. /// Encoded characters have the form -x00-. /// /// The FDO encoded string /// The unencoded version of the string public static string DecodeFDOName(string name) { System.Text.RegularExpressions.Match m = EncRegExp.Match(name); System.Text.StringBuilder sb = new System.Text.StringBuilder(); int previndex = 0; while (m != null && m.Success) { string replaceval; if (m.Value == "-dot-") replaceval = "."; else if (m.Value == "-colon-") replaceval = ":"; else replaceval = ((char)int.Parse(m.Value.Substring(2, 2), System.Globalization.NumberStyles.HexNumber)).ToString(); sb.Append(name.Substring(previndex, m.Index - previndex)); sb.Append(replaceval); previndex = m.Index + m.Value.Length; m = m.NextMatch(); } if (sb.Length == 0) return name; else { sb.Append(name.Substring(previndex)); return sb.ToString(); } } /// /// Enumerates all xml nodes in the document, and looks for tags or attributes named ResourceId /// /// The xml item to examine /// A list with all found elements public static List> GetResourceIdPointers(System.Xml.XmlNode item) { Queue lst = new Queue(); List> res = new List>(); lst.Enqueue(item); while (lst.Count > 0) { System.Xml.XmlNode n = lst.Dequeue(); foreach (System.Xml.XmlNode nx in n.ChildNodes) if (nx.NodeType == System.Xml.XmlNodeType.Element) lst.Enqueue(nx); if (n.Name == "ResourceId") res.Add(new KeyValuePair(n, n.InnerXml)); if (n.Attributes != null) foreach (System.Xml.XmlAttribute a in n.Attributes) if (a.Name == "ResourceId") res.Add(new KeyValuePair(a, a.Value)); } return res; } /// /// Enumerates all objects by reflection, returns the list of referenced objects /// /// The object to examine /// Te combined list of references public static List EnumerateObjects(object obj) { EnumerateObjectCollector c = new EnumerateObjectCollector(); EnumerateObjects(obj, new EnumerateObjectCallback(c.AddItem)); return c.items; } public delegate void EnumerateObjectCallback(object obj); /// /// Helper class for easy enumeration collection /// private class EnumerateObjectCollector { public List items = new List(); public void AddItem(object o) { items.Add(o); } } /// /// Enumerates all objects by reflection, calling the supplied callback method for each object /// /// The object to examine /// The callback function public static void EnumerateObjects(object obj, EnumerateObjectCallback c) { if (obj == null || c == null) return; Dictionary visited = new Dictionary(); Queue items = new Queue(); items.Enqueue(obj); while (items.Count > 0) { object o = items.Dequeue(); if (visited.ContainsKey(o)) continue; //Prevent infinite recursion by circular reference visited.Add(o, o); c(o); //Try to find the object properties foreach (System.Reflection.PropertyInfo pi in o.GetType().GetProperties()) { //Only index free read-write properties are taken into account if (!pi.CanRead || !pi.CanWrite || pi.GetIndexParameters().Length != 0 || pi.GetValue(o, null) == null) continue; if (pi.GetValue(o, null).GetType().GetInterface(typeof(System.Collections.ICollection).FullName) != null) { //Handle collections System.Collections.ICollection srcList = (System.Collections.ICollection)pi.GetValue(o, null); foreach (object ox in srcList) items.Enqueue(ox); } else if (pi.GetValue(o, null).GetType().IsArray) { //Handle arrays System.Array sourceArr = (System.Array)pi.GetValue(o, null); for (int i = 0; i < sourceArr.Length; i++) items.Enqueue(sourceArr.GetValue(i)); } else if (pi.PropertyType.IsClass) { //Handle subobjects items.Enqueue(pi.GetValue(o, null)); } } } } } }