#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 System.Collections.Generic; using OSGeo.MapGuide.MaestroAPI.IO; using OSGeo.MapGuide.MaestroAPI.Schema; using System.Collections.Specialized; using OSGeo.MapGuide.MaestroAPI.CoordinateSystem; using OSGeo.MapGuide.ObjectModels; using System.Xml; using GeoAPI.Geometries; using System.Text.RegularExpressions; using System.Text; namespace OSGeo.MapGuide.MaestroAPI { /// /// Various helper functions /// public class Utility { public const string XML_EXCEPTION_KEY = "XmlError"; //NOXLATE /// /// Gets whether the thrown exception is related to DBXML /// /// /// public static bool IsDbXmlError(Exception ex) { return ex.Message.Contains("MgDbXmlException") || ex.Message.Contains("MgXmlParserException"); //NOXLATE } /// /// Gets whether the given exception has original xml content attached /// /// /// public static bool HasOriginalXml(Exception ex) { return ex.Data[XML_EXCEPTION_KEY] != null; } //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"); //NOXLATE /// /// Converts the specified name value collection into a connection string /// /// /// public static string ToConnectionString(NameValueCollection values) { List tokens = new List(); foreach (string name in values.Keys) { string value = values[name]; if (value.Contains(";")) value = "\"" + value + "\""; tokens.Add(name + "=" + value); } return string.Join(";", tokens.ToArray()); } /// /// Strips the version component from provider name /// /// The provider name. /// public static string StripVersionFromProviderName(string providername) { double x; string[] parts = providername.Split('.'); for (int i = parts.Length - 1; i >= 0; i--) { if (!double.TryParse(parts[i], System.Globalization.NumberStyles.Integer, null, out x)) { if (i != 0) return string.Join(".", parts, 0, i + 1); break; } } return providername; } /// /// 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 + "\""); } /// /// 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 ParseHTMLColorRGBA(string color) { if (color.Length == 8) { 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); int a = 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 ARGB 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; } /// /// Returns the HTML RGBA 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 SerializeHTMLColorRGBA(Color color, bool includeAlpha) { string res = ""; res += color.R.ToString("x02"); res += color.G.ToString("x02"); res += color.B.ToString("x02"); if (includeAlpha) res += color.A.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]; bool rewound = false; if (rewind) { if (source.CanSeek) { try { source.Position = 0; rewound = true; } catch { } } else { ReadOnlyRewindableStream roSource = source as ReadOnlyRewindableStream; if (roSource != null && roSource.CanRewind) { roSource.Rewind(); rewound = true; } } //if (!rewound) // throw new InvalidOperationException("Could not rewind the source stream. Most likely the source stream does not support seeking or rewinding"); //LOCALIZEME } 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; if (source.CanSeek) length = source.Length; 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; } /// /// 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; } /// /// 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(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; } /// /// 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 Regex EncRegExp = new System.Text.RegularExpressions.Regex(@"(\-x[0-2a-fA-F][0-9a-fA-F]\-)|(\-dot\-)|(\-colon\-)", System.Text.RegularExpressions.RegexOptions.Compiled); private static Regex TokenRegex = new Regex("^x[0-9a-fA-F][0-9a-fA-F]", RegexOptions.Compiled); private static Regex TokenRegex2 = new Regex("^_x[0-9a-fA-F][0-9a-fA-F]", RegexOptions.Compiled); /// /// FDO encodes a string /// /// /// /// /// FDO names must always be encoded when writing back to attributes in XML configuration documents as it may contain reserved characters that would render the final XML attribute content invalid. /// /// /// Consequently, such names must always be decoded when reading from XML configuration documents otherwise these escape characters may still be present after reading /// /// /// public static string EncodeFDOName(string name) { //Decode characters not allowed by FDO string lName = name.Replace("-dot-", ".") .Replace("-colon-", ":"); //Break the name up by '-' delimiters string[] tokens = lName.Split(new char[] { '-' }, StringSplitOptions.None); StringBuilder outName = new StringBuilder(); // Encode any characters that are not allowed in XML names. // The encoding pattern is "-x%x-" where %x is the character value in hexidecimal. // The dash delimeters were an unfortunate choice since dash cannot be the 1st character // in an XML name. When the 1st character needs to be encoded, it is encoded as "_x%x-" to // resolve this issue. for (int i = 0; i < tokens.Length; i++) { string token = tokens[i]; bool bMatchedToken = false; if (TokenRegex.Match(token, 0).Success) { bMatchedToken = true; // the token happens to match the encoding pattern. We want to avoid // decoding this sub-string on decode. This is done by encoding the leading // dash. if (outName.Length == 0) outName.Append(string.Format("_x{0:X}-", Convert.ToInt32('-')).ToLower()); else outName.Append(string.Format("-x{0:X}-", Convert.ToInt32('-')).ToLower()); } else if (TokenRegex2.Match(token, 0).Success && i == 0) { bMatchedToken = true; // the token happens to match the encoding pattern for the 1st character. // We want to avoid decoding this sub-string on decode. // This is done by prepending a dummy encoding for character 0. This character is // discarded on decode. outName.Append("_x00-"); } else { // The token doesn't match the encoding pattern, just write the dash // that was discarded by the tokenizer. if (i > 0) { if (outName.Length == 0) outName.Append("_x2d-"); // 1st character so lead with '_' else outName.Append("-"); } } outName.Append(bMatchedToken ? token : ReplaceBadChars(token, outName.Length == 0)); } char c = outName[0]; //Perform actual substitutions of bad characters outName = outName.Remove(0, 1); //Check if the first character requires a meta-escape character replacement string prefix = c + ""; switch (c) { case ' ': prefix = "_x20-"; break; case '-': prefix = "_x2d-"; break; case '&': prefix = "_x26-"; break; default: if (Char.IsDigit(c)) { prefix = "_x3" + c + "-"; } break; } string result = prefix + outName.ToString(); return result; } private static string ReplaceBadChars(string token, bool bFirstInString) { StringBuilder sb = new StringBuilder(); bool bFirstChar = bFirstInString; foreach (char c in token) { if (Char.IsDigit(c) || IsValidXmlChar(c)) sb.Append(c); else sb.Append(string.Format("{0}x{1:X}-", bFirstChar ? "_" : "-", Convert.ToInt32(c)).ToLower()); bFirstChar = false; } return sb.ToString(); } private static bool IsValidXmlChar(char c) { try { XmlConvert.VerifyNCName(c + ""); return true; } catch { return false; } } /// /// Converts FDO encoded characters into their original character. /// /// The FDO encoded string /// /// /// FDO names must always be encoded when writing back to attributes in XML configuration documents as it may contain reserved characters that would render the final XML attribute content invalid. /// /// /// Consequently, such names must always be decoded when reading from XML configuration documents otherwise these escape characters may still be present after reading /// /// /// The unencoded version of the string public static string DecodeFDOName(string name) { // The encoding pattern is delimited by '-' so break string up by '-'. string[] tokens = name.Split(new char[] { '-' }, StringSplitOptions.None); bool prevDecode = true; StringBuilder decoded = new StringBuilder(); for (int i = 0; i < tokens.Length; i++) { string token = tokens[i]; //This is a special character inserted during the encoding process. //If we find this at the beginning, discard it if (i == 0 && token == "_x00") continue; var m = TokenRegex.Match(token, 0); var m2 = TokenRegex2.Match(token, 0); if (token.Length == 4 && token.StartsWith("_x3") && Char.IsDigit(token[3])) { decoded.Append(token[3]); prevDecode = true; } else { if ((!prevDecode) && m.Success) { string replace = ((char)int.Parse(m.Value.Substring(1, 2), System.Globalization.NumberStyles.HexNumber)).ToString(); decoded.Append(replace); prevDecode = true; } else if ((i == 0) && m2.Success) { string replace = ((char)int.Parse(m2.Value.Substring(2, 2), System.Globalization.NumberStyles.HexNumber)).ToString(); decoded.Append(replace); prevDecode = true; } else { if (i > 0 && !prevDecode) decoded.Append("-"); decoded.Append(token); prevDecode = false; } } } return decoded.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)); } } } } /// /// Transforms the envelope. /// /// The env. /// The source coordinate system WKT. /// The destination coordinate system WKT. /// public static OSGeo.MapGuide.ObjectModels.Common.IEnvelope TransformEnvelope(OSGeo.MapGuide.ObjectModels.Common.IEnvelope env, string srcCsWkt, string dstCsWkt) { try { var trans = new DefaultSimpleTransform(srcCsWkt, dstCsWkt); var oldExt = env; double llx; double lly; double urx; double ury; trans.Transform(oldExt.MinX, oldExt.MinY, out llx, out lly); trans.Transform(oldExt.MaxX, oldExt.MaxY, out urx, out ury); return ObjectFactory.CreateEnvelope(llx, lly, urx, ury); } catch { return null; } } /// /// Gets an fdo-related attribute from the specified xml element using the /// unqualified name and trying again with the fdo: qualifier if it didn't exist /// /// /// /// internal static XmlAttribute GetFdoAttribute(XmlNode node, string name) { var att = node.Attributes[name]; if (att == null) return node.Attributes["fdo:" + name]; return att; } /// /// Gets an fdo-related element from the specified xml element using the /// unqualified name and trying again with the fdo: qualifier if it didn't exist /// /// /// /// internal static XmlElement GetFdoElement(XmlElement el, string name) { var element = el[name]; if (element == null) return el["fdo:" + name]; return element; } } }