C# Convert XML file to Multiple Polygon Objects - c#
After converting multiple MapInfo files into a unique Shapefile, and then converting that file again to .KML, I got the following .XML file. My idea is to extract each set of 'coordinates' sections, and build polygons using them.
Other attempted solution:
Given the excessive time facing this blockage, I tried obtaining each pair of 'coordinates' tags and using Substring to get the coordinates. Unfortunately given the size of the file (>400 MB) this dirty approach is not practical.
Xml file
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document id="root_doc">
<Schema id="PruebaKML4g.schema">
<SimpleField name="FID" type="float"/>
<SimpleField name="REGION" type="float"/>
<SimpleField name="NOMBRE" type="string"/>
<SimpleField name="layer" type="string"/>
<SimpleField name="path" type="string"/>
</Schema>
<Document id="PruebaKML4g">
<name>PruebaKML4g</name>
<Placemark id="PruebaKML4g.1">
<ExtendedData>
<SchemaData schemaUrl="#PruebaKML4g.schema">
<SimpleData name="FID">5</SimpleData>
<SimpleData name="REGION">1</SimpleData>
<SimpleData name="NOMBRE">BAJA CALIFORNIA</SimpleData>
<SimpleData name="layer">LBS_REGION_1_region</SimpleData>
<SimpleData name="path">C:/Files/LBS_REGION_1_region.shp</SimpleData>
</SchemaData>
</ExtendedData>
<MultiGeometry>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
-105.258751,21.782028,0
-105.247174,21.81173,0
-105.241826,21.809401,0
-105.236994,21.806241,0
-105.232822,21.802344,0
-105.229439,21.79783,0
-105.228552,21.796052,0
-105.228974,21.795899,0
-105.230294,21.79522,0
-105.231872,21.79511,0
-105.234048,21.79431,0
-105.235131,21.794083,0
-105.236824,21.793857,0
-105.238518,21.793295,0
-105.239365,21.792389,0
-105.240327,21.790914,0
-105.242379,21.79046,0
-105.243829,21.790459,0
-105.245644,21.788766,0
-105.247331,21.785709,0
-105.24817,21.783115,0
-105.248701,21.780372,0
-105.258751,21.782028,0
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
...
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
-103.704559,20.767933,0
-103.702714,20.773608,0
-103.701694,20.77322,0
-103.700762,20.772672,0
-103.699944,20.77198,0
-103.699267,20.771165,0
-103.698751,20.770252,0
-103.698411,20.769268,0
-103.698258,20.768243,0
-103.698297,20.76721,0
-103.704559,20.767933,0
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
-105.160778,20.766278,0
-105.162411,20.77201,0
-105.161328,20.77219,0
-105.160228,20.77219,0
-105.159145,20.77201,0
-105.158111,20.771656,0
-105.157159,20.771139,0
-105.156317,20.770474,0
-105.15561,20.769682,0
-105.15506,20.768786,0
-105.160778,20.766278,0
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
-117.125814,32.524285,0
-117.125516,32.524512,0
-117.125142,32.524428,0
-117.124876,32.524169,0
-117.124754,32.524513,0
-117.124784,32.525361,0
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</MultiGeometry>
</Placemark>
</Document>
</Document>
</kml>
I tried to use the following code:
Main
public T DeserializeToObject<T>(string filepath) where T : class
{
System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (StreamReader streamReader = new StreamReader(filepath))
{
return (T)xmlSerializer.Deserialize(streamReader);
}
}
private void Form1_Load(object sender, EventArgs e)
{
String pathKml = #"C:\PruebaKML4g.kml";
List<Kml> elementsList = DeserializeToObject<List<Kml>>(pathKml);
}
Kml.cs
/*[XmlRoot(ElementName = "kml")] changed by Mike Clark suggestion*/
[XmlRoot(ElementName = "kml", Namespace = "http://www.opengis.net/kml/2.2")]
public class Kml
{
public List<Polygon> polygons = new List<Polygon>();
}
public class Polygon
{
[XmlAttribute("outerBoundaryIs")]
public String outerBoundaryIs { get; set; }
[XmlAttribute("linearRing")]
public String linearRing { get; set; }
[XmlAttribute("coordinates")]
public String coordinates { get; set; }
}
However, the SimpleData elements in the XML file appear to be interfering with my parsing, generating the following error
InvalidOperationException: xmlns='http://www.opengis.net/kml/2.2'> was not expected.
Any clue about where my mistake is will be appreciated.
With all those conversion steps, perhaps the XML file is malformed? Or maybe a memory error with so much data? Try parsing the file with a low-memory requirement SAX parser to let it find any syntactical errors that might be buried deep in the file. Do you have Python installed?
python -c "import xml.sax;p=xml.sax.make_parser();p.parse(r'yourfile.xml')"
Change yourfile.xml to the correct path and filename of your XML file.
If it prints nothing, the file is syntactically valid. If it prints an error, try to use the line:column info in the error to spot the error in your XML.
Part 2:
List<Kml> elementsList = DeserializeToObject<List<Kml>>(pathKml);
might be wrong. XML documents can have one and only one root <element> (in this case, <kml>) so I think having a list of Kml instances will not make make sense for the parser. Try this instead:
Kml root = DeserializeToObject<Kml>(pathKml);
But that is a simple problem compared to the next problem, which is that I think your C# class structure needs to mirror exactly the hierarchical structure of the XML. The polygons are under this hierarchy:
kml > Document > Document > Placemark > MultiGeometry
which means you would need something like
class Kml {
Document Document;
}
class Document {
Document Document;
Placemark Placemark;
}
class Placemark {
Polygon[] MultiGeometry;
}
class Polygon {
OuterBoundaryIs outerBoundaryIs;
}
class OuterBoundaryIs {
LinearRing LinearRing;
}
class LinearRing {
string coordinates;
}
Then you would need something like
var polygons = kml.Document.Document.Placemark.MultiGeometry;
for(int i = 0; i < polygons.Length; i++) {
var polygon = polygons[i];
string coordinates = polygon.outerBoundaryIs.LinearRing.coordinates;
// do something with coordinates
}
By the way, a better kind of parser for this type of thing would be an XPath parser which can avoid the need to model the XML structure with C# classes. It takes a little practice and research to craft an XPath query, but the resulting code is cleaner, and it's a good skill to have some experience with. More XPath see:
https://stackoverflow.com/a/16012736/11611195
https://learn.microsoft.com/en-us/dotnet/standard/data/xml/select-nodes-using-xpath-navigation
Related
GetElementByID.GetElementsByTagName returns null
My friend wrote this piece of XML code for simple testing in our game when it comes to loading and writing save data. The problem is that his code does not work at all and I have never wrote any piece of XML code before so this is new for me and I decided to learn XML. All it wants is the x and y values to create a new Tile object. When running the code it gives the error: System.NullReferenceException was unhandled HResult=-2147467261 Message=Object reference not set to an instance of an object. TL;DR It loads simple Tiles and all it's looking for is the x and y position to later add to a list that is accessed in the Level class. The code he wrote: public class XmlHandler { private List<Base.Tile> tiles; public XmlHandler() { } public void Load() { XmlDocument documentFile = new XmlDocument(); documentFile.Load(#"C:\Tiles\0.xml"); var listOfTiles = documentFile.GetElementById("tiles").GetElementsByTagName("tile"); foreach(XmlElement tile in listOfTiles) { var x = ((XmlElement)tile.GetElementsByTagName("position")[0]).GetAttribute("x"); var y = ((XmlElement)tile.GetElementsByTagName("position")[0]).GetAttribute("y"); Classes.Base.Tile t = new Base.Tile(new Vector2(float.Parse(x), float.Parse(y))); this.tiles.Add(t); } } public List<Base.Tile> GetTiles() { return this.tiles; } } The current XML file, modified from the original as shown below <tiles> <tile> <position x="10" y="20" /> </tile> <tile> <position x="50" y="20" /> </tile> <tile> <position x="30" y="40" /> </tile> </tiles> And this is the original XML that I modified because the first line caused errors <?xml encoding="utf-8"?> <tiles> <tile> <id>1</id> <position x="10" y="20" /> </tile> <tile> <id>2</id> <position x="50" y="20" /> </tile> <tile> <id>3</id> <position x="30" y="40" /> </tile> </tiles> Thanks for viewing/reading. Any help is appreciated!
The bug is here: var listOfTiles = documentFile.GetElementById("tiles").GetElementsByTagName("tile"); Change it this way: var listOfTiles = documentFile.GetElementsByTagName("tiles").GetElementsByTagName("tile"); <tiles> is a tag and it hasn't got any id.
I'm not sure whether this is the complete class or not, but firstly it looks like you haven't created a list for the tiles. Somewhere before you start adding tiles to the list 'tiles', you need to write: tiles = new List(); I suggest the constructor. It would also be helpful to know where the null reference exception is happening. Make sure your list is set, and then if the problem still occurs, add a comment and I'll run your code on my machine.
Per the docs, GetElementById will return elements with an id attribute as defined in a DTD (or just id by default). You don't have any id attributes, so this returns null - hence the exception. If you change the offending line to this: var listOfTiles = documentFile.GetElementsByTagName("tile"); Then your current code will work fine. However... LINQ to XML is a far cleaner API, you could write your entire method as below: var doc = XDocument.Load(#"C:\Tiles\0.xml"); var tiles = from tile in doc.Descendants("tile") from position in tile.Elements("position") let x = (float)position.Attribute("x") let y = (float)position.Attribute("y") select new Base.Tile(new Vector2(x, y)); this.tiles = tiles.ToList();
XML reader can't find elements when using xmlns attribute
I'm using visual studio for windows phone and my code for the XML reader does not work when there is attributes in the parent of the XML data. My C# code namespace youtube_xml { public partial class MainPage : PhoneApplicationPage { // Constructor public MainPage() { InitializeComponent(); SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape; } private void listBox1_Loaded(object sender, RoutedEventArgs e) { var element = XElement.Load("Authors.xml"); var authors = from var in element.Descendants("feed") select new Authors { AuthorName = var.Attribute("scheme").Value, }; listBoxAuthors.DataContext = authors; } public ImageSource GetImage(string path) { return new BitmapImage(new Uri(path, UriKind.Relative)); } } } The Working XML data <?xml version='1.0' encoding='UTF-8'?> <feed> <category scheme='http://schemas.google.com/g/2005#kind'/> </feed> NOT working data (note: the attribute "xmlns" in the root element "feed") <?xml version='1.0' encoding='UTF-8'?> <feed xmlns='http://www.w3.org/2005/Atom' > <category scheme='http://schemas.google.com/g/2005#kind'/> </feed>
Welcome to the world of XML namespaces! The problem isn't the fact that "there's an attribute" - it's the fact that it's causing everything below it to be in a namespace. You can no longer say .Attribute("scheme") because that only looks for things in the empty namespace. Namespaces are used via a contraption based on operator overloading: XNamespace atom = "http://www.w3.org/2005/Atom'"; // And now you can say: .Descendants(atom + "feed") .Attribute(atom + "scheme") Et cetera. The ability to assign a string into an XNamespace variable is thanks to an implicit conversion operator. The + here actually constructs an XName (which, by the way, also has an implicit conversion from string - that's why you plain .Elements("feed") works even though the parameter type is not string) Handy tip: You can cast an attribute into certain types instead of using .Value, for instance (string)foo.Attribute(atom + "scheme"). It also works with a bunch of other types, for instance int.
Extracting values from XML returned by azure service management API
i have tried several methods of trying to extract values from an XML file but none of them seem to work. I am using C#. The XML Is as follows <?xml version="1.0" encoding="utf-8"?> <HostedService xmlns="http://schemas.microsoft.com/windowsazure"> <Url>hosted-service-url</Url> <ServiceName>hosted-service-name</ServiceName> <HostedServiceProperties> <Description>description</Description> <Location>location</Location> <AffinityGroup>affinity-group</AffinityGroup> <Label>label</Label> </HostedServiceProperties> </HostedService> I would like to retrieve hosted-service-url, hosted-service-name, description, location, affinity-group and label What would be the best of way of retrieving these values? Edit : Thanks L.B that method works perfectly. However i have just been told i will have to use the larger XML that is below. <?xml version="1.0" encoding="utf-8"?> <HostedService xmlns="http://schemas.microsoft.com/windowsazure"> <Url>hosted-service-url</Url> <ServiceName>hosted-service-name</ServiceName> <HostedServiceProperties> <Description>description</Description> <Location>location</Location> <AffinityGroup>affinity-group</AffinityGroup> <Label>base-64-encoded-name-of-the-service</Label> </HostedServiceProperties> <Deployments> <Deployment> <Name>deployment-name</Name> <DeploymentSlot>deployment-slot</DeploymentSlot> <PrivateID>deployment-id</PrivateID> <Status>deployment-status</Status> <Label>base64-encoded-deployment-label</Label> <Url>deployment-url</Url> <Configuration>base-64-encoded-configuration-file</Configuration> <RoleInstanceList> <RoleInstance> <RoleName>role-name</RoleName> <InstanceName>role-instance-name</InstanceName> <InstanceStatus>instance-status</InstanceStatus> </RoleInstance> </RoleInstanceList> <UpgradeDomainCount>upgrade-domain-count</UpgradeDomainCount> <RoleList> <Role> <RoleName>role-name</RoleName> <OsVersion>operating-system-version</OsVersion> </Role> </RoleList> <SdkVersion>sdk-version-used-to-create-package</SdkVersion> <InputEndpointList> <InputEndpoint> <RoleName>role-name</RoleName> <Vip>virtual-ip-address</Vip> <Port>port-number</Port> </InputEndpoint> … </InputEndpointList> <Locked>deployment-write-allowed-status</Locked> <RollbackAllowed>rollback-operation-allowed</RollbackAllowed> </Deployment> </Deployments> </HostedService> My final question is, there is several repeated tags, such as , how can i differentiate between them?
you can use Xml to Linq to parse your xml string. For ex, var xElem = XElement.Load(new StringReader(xml)); var ns = XNamespace.Get("http://schemas.microsoft.com/windowsazure"); var obj = new { ServiceName = xElem.Descendants(ns + "ServiceName").First().Value, Description = xElem.Descendants(ns + "Description").First().Value, }; or you can use XmlSerializer XmlSerializer xs = new XmlSerializer(typeof(HostedService), "http://schemas.microsoft.com/windowsazure"); var obj2 = (HostedService)xs.Deserialize(new StringReader(xml)); public class HostedService { public string Url; public string ServiceName; public HostedServiceProperties HostedServiceProperties; } public class HostedServiceProperties { public string Description; public string Location; public string AffinityGroup; public string Label; }
Maybe you can try samples from XmlDocument ( http://msdn.microsoft.com/en-us/library/d271ytdx.aspx) and and LINQ to XML -( http://msdn.microsoft.com/en-us/library/bb669152.aspx) first and than apply it to your case.
Is there an easier way to deserialize similar XML files?
I am writing a class library which abstracts data contained in XML files on a web site. Each XML file uses the same root element: page. The descendant(s) of page depend on the specific file that I am downloading. For example: <!-- http://.../groups.xml --> <page url="/groups.xml"> <groups> <group id="1" > <members> <member name="Adrian" /> <member name="Sophie" /> <member name="Roger" /> </members> </group> </groups> </page> <!-- http://.../project.xml?n=World%20Domination --> <page url="/project.xml"> <projectInfo> <summary classified="true" deadline="soon" /> <team> <member name="Pat" /> <member name="George" /> </team> </projectInfo> </page> There are also several additional XML files that I would like to download and process, eventually. For that reason, I have been trying to come up with a nice, clean way to deserialize the data. I've tried a few approaches, but each approach leaves me feeling a little dirty when I look back over my code. My latest incarnation utilizes the following method: internal class Provider { /// <summary>Download information from the host.</summary> /// <typeparam name="T">The type of data being downloaded.</typeparam> internal T Download<T>(string url) where T : IXmlSerializable, new() { try { var request = (HttpWebRequest)WebRequest.Create(url); var response = (HttpWebResponse)request.GetResponse(); using (var reader = XmlReader.Create(response.GetResponseStream())) { // Skip the XML prolog (declaration and stylesheet reference). reader.MoveToContent(); // Skip the `page` element. if (reader.LocalName == "page") reader.ReadStartElement(); var serializer = new XmlSerializer(typeof(T)); return (T)serializer.Deserialize(reader); } } catch (WebException ex) { /* The tubes are clogged. */ } } } [XmlRoot(TypeName = "groups")] public class GroupList : List<Group>, IXmlSerializable { private List<Group> _list; public void ReadXml(XmlReader reader) { if (_list == null) _list = new List<Group>(); reader.ReadToDescendant("group"); do { var id = (int)reader["id"]; var group = new Group(id); if (reader.ReadToDescendant("member")) { do { var member = new Member(reader["name"], group); group.Add(member); } while (reader.ReadToNextSibling("member")); } _list.Add(group); } while (reader.ReadToNextSibling("group")); reader.Read(); } } This works, but I feel like there is a better way that I'm not seeing. I tried using the xsd.exe utility when I started this project. While it would minimize the amount of code for me to write, it did not feel like the ideal solution. It would be the same approach that I'm using now -- I would just get there faster. I'm looking for a better solution. All pages have the page element in common -- isn't there a way to take advantage of that? Would it be possible to have a serializable container class Page that could contain a combination of other objects depending on the file downloaded? Are there any simpler ways to accomplish this?
.NET provides a "xsd.exe" utility on the command line. Run xsd.exe (xmlfilename) on your original xml file and it'll derive a XML schema (xsd) from your XML data file. Run xsd.exe (xsd file name) /C and it'll create a C# class which can be used to deserialize such an XML file into a C# class. Of course, since it only has a single XML file to go on, xsd.exe isn't perfect in its XML schema it derives - but that could be quick'n'easy starting point for you to get started.
How to create an XmlMappingSource during runtime?
(Follow-Up-Question to How to change LINQ O/R-M table name/source during runtime?) I need to change the table source of a LINQ 2 SQL O/R-Mapper table during runtime. To achieve this, I need to create an XmlMappingSource. On command line, I could use SqlMetal to create this mapping file, but I would like to create the mapping file during runtime in memory. The XmlMappingSource is a simple xml file, looking something like this: <?xml version="1.0" encoding="utf-8"?> <Database Name="MyDatabase" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007"> <Table Name="dbo.MyFirstTable" Member="MyFirstTable"> <Type Name="MyFirstTable"> <Column Name="ID" Member="ID" Storage="_ID" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" IsDbGenerated="true" AutoSync="OnInsert" /> <Association Name="WaStaArtArtikel_WaVerPreisanfragen" Member="WaStaArtArtikel" Storage="_WaStaArtArtikel" ThisKey="ArtikelID" OtherKey="ID" IsForeignKey="true" /> </Type> </Table> <Table Name="dbo.MySecondTable" Member="MySecondTable"> <Type Name="MySecondTable"> <Column Name="ID" Member="ID" Storage="_ID" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" IsDbGenerated="true" AutoSync="OnInsert" /> <Column Name="FirstTableID" Member="FirstTableID" Storage="_FirstTableID" DbType="UniqueIdentifier NOT NULL" /> <Association Name="MySecondTable_MyFirstTable" Member="MyFirstTable" Storage="_MyFirstTable" ThisKey="FirstTableID" OtherKey="ID" IsForeignKey="true" /> </Type> </Table> </Database> This should be possible to create using reflection, for example I can get the database name from a data context like this: using System.Data.Linq.Mapping; using System.Xml.Linq; XDocument mapWriter = new XDocument(); DatabaseAttribute[] catx = (DatabaseAttribute[])typeof(WcfInterface.WaDataClassesDataContext).GetCustomAttributes(typeof(DatabaseAttribute), false); XElement xDatabase = new XElement("Database"); xDatabase.Add(new XAttribute("Name", catx[0].Name)); mapWriter.Add(xDatabase); My problem: I can't find good documentation of the mapping, so extracting the necessary information is quite error-prone - maybe someone can point me to good docs of the mapping, or, even better, to a code example how to create the mapping file?
Have you considered using LINQ to Entities, the mapping formats for LINQ to Entities are documented.
Use Damien Guard's Open Source T4 templates. They do everything SQLMetal can do and more, and you'll have the full T4 engine behind you.
I just had same problem, also I had no option to change project as its too late to do this. I needed to update database name in mapping file in my solution. This solution works. My database mapping <?xml version="1.0" encoding="utf-8"?> <Database Name="DatabaseName" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007"> <Table Name="dbo.tblDictionary" Member="TblDictionary"> <Type Name="TblDictionary"> <Column Name="lngRecordID" Member="LngRecordID" Storage="_LngRecordID" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" AutoSync="OnInsert" /> <Column Name="txtWord" Member="TxtWord" Storage="_TxtWord" DbType="VarChar(50) NOT NULL" CanBeNull="false" /> </Type> </Table> </Database> and finally the code: class Program { static void Main(string[] args) { // to get embeded file name you have to add namespace of the application const string embeddedFilename = "ConsoleApplication3.FrostOrangeMappings.xml"; // load file into stream var embeddedStream = GetEmbeddedFile(embeddedFilename); // process stream ProcessStreamToXmlMappingSource(embeddedStream); Console.ReadKey(); } private static void ProcessStreamToXmlMappingSource(Stream stream) { const string newDatabaseName = "pavsDatabaseName"; var mappingFile = new XmlDocument(); mappingFile.Load(stream); stream.Close(); // populate collection of attribues XmlAttributeCollection collection = mappingFile.DocumentElement.Attributes; var attribute = collection["Name"]; if(attribute==null) { throw new Exception("Failed to find Name attribute in xml definition"); } // set new database name definition collection["Name"].Value = newDatabaseName; //display xml to user var stringWriter = new StringWriter(); using (var xmlTextWriter = XmlWriter.Create(stringWriter)) { mappingFile.WriteTo(xmlTextWriter); xmlTextWriter.Flush(); stringWriter.GetStringBuilder(); } Console.WriteLine(stringWriter.ToString()); } /// <summary> /// Loads file into stream /// </summary> /// <param name="fileName"></param> /// <returns></returns> private static Stream GetEmbeddedFile(string fileName) { var assembly = Assembly.GetExecutingAssembly(); var stream = assembly.GetManifestResourceStream(fileName); if (stream == null) throw new Exception("Could not locate embedded resource '" + fileName + "' in assembly"); return stream; }`