c# not able to find a node within the XMLNode

时间:2018-02-03 07:40:40

标签: c# xml

This is my kml file:

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
  <name>Test.kml</name>
  <Folder>
    <name>Test</name>
    <open>1</open>
    <Placemark>
      <name>Placemark 1</name>
      <LookAt>
        <longitude>-150</longitude>
        <latitude>72</latitude>
        <altitude>0</altitude>
        <heading>-13.26929942603143</heading>
        <tilt>0</tilt>
        <range>33665.16192218825</range>
        <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
      </LookAt>
      <styleUrl>#m_ylw-pushpin</styleUrl>
      <Point>
        <gx:drawOrder>1</gx:drawOrder>
        <coordinates>-110.7484519621821,52.7616508182995,0</coordinates>
      </Point>
    </Placemark>
    <Placemark>
      <name>Polygon</name>
      <styleUrl>#msn_ylw-pushpin551</styleUrl>
      <Polygon>
        <tessellate>1</tessellate>
        <outerBoundaryIs>
          <LinearRing>
          <coordinates>
            -114.1205573145593,51.36318071429854,0 -114.1205787952745,51.36318006995027,0 -114.1205971712767,51.36317242116965,0 -114.1206026671322,51.36316989077702,0 -114.1206102089206,51.36316966453516,0 -114.1206306254288,51.36316432159048,0 -114.1206380046647,51.36316173522451,0 -114.1206530868876,51.36316128267593,0 -114.1206700591908,51.36316077215199,0 -114.1207186777935,51.36315339774478,0 -114.1207317114146,51.36315064137735,0 -114.1206014395037,51.36248218454789,0 -114.120595868448,51.36248353455266,0 -114.1205319409001,51.36248782272818,0 -114.1204591504232,51.36250065739123,0 -114.1203422144068,51.3624758047018,0 -114.1205573145593,51.36318071429854,0
          </coordinates>
          </LinearRing>
        </outerBoundaryIs>
      </Polygon>
    </Placemark>
  </Folder>
  <Placemark>
    <name>Placemark 1</name>
    <LookAt>
      <longitude>-150</longitude>
      <latitude>72</latitude>
      <altitude>0</altitude>
      <heading>-13.26929942603143</heading>
      <tilt>0</tilt>
      <range>33665.16192218825</range>
      <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
    </LookAt>
    <styleUrl>#m_ylw-pushpin</styleUrl>
    <Point>
      <gx:drawOrder>1</gx:drawOrder>
      <coordinates>-110.7484519621821,52.7616508182995,0</coordinates>
    </Point>
  </Placemark>
  <Placemark>
    <name>Polygon</name>
    <styleUrl>#msn_ylw-pushpin551</styleUrl>
    <Polygon>
      <tessellate>1</tessellate>
      <outerBoundaryIs>
        <LinearRing>
          <coordinates>
            -114.1205573145593,51.36318071429854,0 -114.1205787952745,51.36318006995027,0 -114.1205971712767,51.36317242116965,0 -114.1206026671322,51.36316989077702,0 -114.1206102089206,51.36316966453516,0 -114.1206306254288,51.36316432159048,0 -114.1206380046647,51.36316173522451,0 -114.1206530868876,51.36316128267593,0 -114.1206700591908,51.36316077215199,0 -114.1207186777935,51.36315339774478,0 -114.1207317114146,51.36315064137735,0 -114.1206014395037,51.36248218454789,0 -114.120595868448,51.36248353455266,0 -114.1205319409001,51.36248782272818,0 -114.1204591504232,51.36250065739123,0 -114.1203422144068,51.3624758047018,0 -114.1205573145593,51.36318071429854,0
          </coordinates>
        </LinearRing>
      </outerBoundaryIs>
    </Polygon>
  </Placemark>
 </Document>
</kml>

I want to find the name and coordinates and check if the coordinates are duplicate then delete them. Following is code I have written so far:

    XmlDocument xmldoc = new XmlDocument();
    XmlReaderSettings settings = new XmlReaderSettings { NameTable = new NameTable() };
    XmlNamespaceManager xmlns = new XmlNamespaceManager(settings.NameTable);
    xmlns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
    XmlParserContext context = new XmlParserContext(null, xmlns, "", XmlSpace.Default);
    XmlReader reader = XmlReader.Create(fileName, settings, context);
    xmldoc.Load(reader);

    // Setup default namespace manager for searching through nodes
    XmlNamespaceManager manager = new XmlNamespaceManager(xmldoc.NameTable);
    string defaultns = xmldoc.DocumentElement.GetNamespaceOfPrefix("");
    manager.AddNamespace("ns", defaultns);

    var values = new HashSet<string>();

    // get a list of all <Placemark> nodes
    XmlNodeList listOfPlacemark = xmldoc.SelectNodes("//ns:Placemark", manager);

    int totalRecordsRemoved = 0;

    // iterate over the <Placemark> nodes
    foreach (XmlNode singlePlaceMark in listOfPlacemark)
    {
        StringBuilder sb = new StringBuilder();
        // Get the name subnode
        XmlNode nameNode = singlePlaceMark.SelectSingleNode("ns:name", manager);

        if (nameNode != null)
        {
            // get a coordinate nodes
            //XmlNodeList coordinatesNode = singlePlaceMark.SelectNodes("//coordinates", manager);

            XmlNode coordinatesNode = singlePlaceMark.SelectSingleNode("ns:coordinates", manager);

            sb.Append(coordinatesNode.InnerXml.ToString());

            if (sb.ToString() != "")
            {
                if (values.Contains(sb.ToString()))
                {                                    
                    singlePlaceMark.RemoveAll();                                    
                }
                else
                {
                    values.Add(sb.ToString());
                }
            }
        }
    }

I am not able to find the coordinate node, how do I get the values in <coordinates> node irrespective of the hierarchy within the placemark node? Is there a simple way to do this with Linq or any other approach?

EDIT:

Two reasons for which I am not using XDocument

1. The kml saved after all the processing become of large size because all the nodes get kml as prefix and that is because of "xmlns:kml="http://www.opengis.net/kml/2.2"" tag. I don't know how to remove this tag with XDocument so I used XmlDocument which when saved shows xml tags without the prefix. 2. My kml file does not have "http://www.w3.org/2001/XMLSchema-instance" namespace so when I use XDocument it gives me error - 'xsi' is an undeclared prefix. But when I use XmlDocument I don't get this error.

Edit 2:

Can you please suggest how can I get the coordinate node at this line XmlNode coordinatesNode = singlePlaceMark.SelectSingleNode("ns:coordinates", manager);?

2 个答案:

答案 0 :(得分:3)

A kml file can be huge and cause out of memory exception. I recommend using a XmlReader. See code below :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XmlReader reader = XmlReader.Create(FILENAME);
            reader.MoveToContent();
            while (!reader.EOF)
            {
                if (reader.Name != "Placemark")
                {
                    reader.ReadToFollowing("Placemark");
                }
                if (!reader.EOF)
                {
                    XElement placemark = (XElement)XElement.ReadFrom(reader);
                    Location location = new Location();
                    Location.locations.Add(location);
                    XElement name = placemark.Elements().Where(x => x.Name.LocalName == "name").FirstOrDefault();
                    location.name = (string)name;
                    XElement longitude = placemark.Descendants().Where(x => x.Name.LocalName == "longitude").FirstOrDefault();
                    location.longitude = (int?)longitude;
                    XElement latitude = placemark.Descendants().Where(x => x.Name.LocalName == "latitude").FirstOrDefault();
                    location.latitude = (int?)latitude;
                    XElement altitude = placemark.Descendants().Where(x => x.Name.LocalName == "altitude").FirstOrDefault();
                    location.alt = (int?)altitude;
                    XElement tilt = placemark.Descendants().Where(x => x.Name.LocalName == "tilt").FirstOrDefault();
                    location.tilt = (int?)tilt;
                    XElement heading = placemark.Descendants().Where(x => x.Name.LocalName == "heading").FirstOrDefault();
                    location.heading = (double?)heading;
                    XElement range = placemark.Descendants().Where(x => x.Name.LocalName == "range").FirstOrDefault();
                    location.range = (double?)range;
                    XElement coordinates = placemark.Descendants().Where(x => x.Name.LocalName == "coordinates").FirstOrDefault();
                    if (coordinates != null)
                    {
                        List<string> coordinatesArray = ((string)coordinates).Split(new char[] { ' ', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                        foreach(string coordinate in coordinatesArray)
                        {
                            string[] coordinateArray = coordinate.Split(new char[] { ',' });
                            Coordinate newCoordinate = new Coordinate();
                            if (location.coordinates == null) location.coordinates = new List<Coordinate>();
                            location.coordinates.Add(newCoordinate);

                            newCoordinate.longitude = double.Parse(coordinateArray[0]);
                            newCoordinate.latitude = double.Parse(coordinateArray[1]);
                            newCoordinate.alt = double.Parse(coordinateArray[2]);

                        }

                    }
                }
            }
        }
    }
    public class Location
    {
        public static List<Location> locations = new List<Location>();
        public string name { get; set; }
        public int? longitude { get; set; }
        public int? latitude { get; set; }
        public int? alt { get; set; }
        public int? tilt { get; set; }
        public double? heading { get; set; }
        public double? range { get; set; }
        public List<Coordinate> coordinates { get; set; }
    }
    public class Coordinate
    {
        public double longitude { get; set; }
        public double latitude { get; set; }
        public double alt { get; set; }
    }
}

答案 1 :(得分:1)

If you're using XDocument and some LINQ magic the following code removes any other Placemark elements for those instances where the coordinates are duplicate.

var xdoc = XDocument.Parse(xml); // XDocument.Load(stream or file);

var kml = (XNamespace) "http://www.opengis.net/kml/2.2";

var coordinates = xdoc
   .Descendants(kml + "Placemark")              // find Placemark elements
   .Where(pm => pm.Element(kml+"name") != null) // which have a name element
   .Descendants(kml + "coordinates")            // find its coordinates 
   .GroupBy(c => c.Value)                       // group by its value
   .Where(c => c.Count() > 1)                   // if we found more then one value
   .Select(c=> c);                              // project the item for removal

int totalRecordsRemoved = 0;
// loop over each key    
foreach(var coord in coordinates)
{
  // loop over the double coordinates, skipping the first item
  foreach(var item in coord.Skip(1))
  {
     totalRecordsRemoved++;
     // remove the Placemark node, 
     // assuming there ALWAYS will be a Placemark up the hierachy
     // if not First will bark
     item.Ancestors(kml + "Placemark").First().Remove();
  }
}

xdoc will now contain your cleaned XML which you can Save or process in any other way you like.

XDocument might be hungry for memory so test and verify if its memory consumption fits in your requirements.

If you need to read a broken XML file with XDocument.Load, use an XMLReader with its own ParserContext and namespacemanager like so:

NameTable nt = new NameTable();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt);
// this adds the missing namespace
nsmgr.AddNamespace("xsi", "https://www.w3.org/2001/XMLSchema-instance");

// Create the XmlParserContext.
XmlParserContext context = new XmlParserContext(null, nsmgr, null, XmlSpace.None);

// Create the reader. 
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
XmlReader reader = XmlReader.Create(fileOrStream, settings, context);

var xdoc = XDocument.Load(reader);