views:

2455

answers:

5

I'm a LINQ to XML newbie, and a KML newbie as well; so bear with me.

My goal is to extract individual Placemarks from a KML file. My KML begins thusly:

<?xml version="1.0" encoding="utf-8"?>
<Document xmlns="http://earth.google.com/kml/2.0"&gt;
  <name>Concessions</name>
  <visibility>1</visibility>
  <Folder>
    <visibility>1</visibility>
    <Placemark>
      <name>IN920211</name>
      <Style>
        <PolyStyle>
          <color>80000000</color>
        </PolyStyle>
      </Style>
      <Polygon>
        <altitudeMode>relativeToGround</altitudeMode>
        <outerBoundaryIs>
          <LinearRing>
            <coordinates>11.728374,1.976421,0 11.732967,1.965322,0 11.737225,1.953161,0 11.635858,1.940812,0 11.658102,1.976874,0 11.728374,1.976421,0 </coordinates>
          </LinearRing>
        </outerBoundaryIs>
      </Polygon>
    </Placemark>
    <Placemark>
    ...

This is as far as I've gotten:

    Dim Kml As XDocument = XDocument.Load(Server.MapPath("../kmlimport/ga.kml"))
    Dim Placemarks = From Placemark In Kml.Descendants("Placemark") _
         Select Name = Placemark.Element("Name").Value

So far no good - Kml.Descendants("Placemark") gives me an empty enumeration. The document is loaded properly - because KML.Descendants contains every node. For what it's worth these queries come up empty as well:

Dim foo = Kml.Descendants("Document") 
Dim foo = Kml.Descendants("Folder")

Can someone point me in the right direction? Bonus points for links to good Linq to XML tutorials - the ones I've found online stop at very simple scenarios.

A: 

You may need to add a namespace to the XElement name

Dim ns as string = "http://earth.google.com/kml/2.0" dim foo = Kml.Descendants(ns + "Document")

ignore any syntax errors, I work in c#

You'll find there can be a difference in the XElement.Name vs XElement.Name.LocalName/

I usually foreach through all the XElements in the doc to as a first step to make sure I'm using the right naming.

C# Here is an excerpt of my usage, looks like I forgot the {}

private string GpNamespace = "{http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions}";

var results = admldoc.Descendants(GpNamespace + "presentationTable").Descendants().Select( p => new dcPolicyPresentation(p));

Bruce Murdock
This gives me Run-time exception thrown : System.Xml.XmlException - The ':' character, hexadecimal value 0x3A, cannot be included in a name.
Herb Caudill
Feel free to answer in c#.
Herb Caudill
Thanks - this works. I've found a more concise approach which I'll post below.
Herb Caudill
A: 

Neither of the above fixes did the job; see my comments for details. I believe both spoon16 and Bruce Murdock are on the right track, since the namespace is definitely the issue.

After further Googling I came across some code on this page that suggested a workaround: just strip the xmlns attribute from the original XML.

    ' Read raw XML
    Dim RawXml As String = ReadFile("../kmlimport/ga.kml")
    ' HACK: Linq to XML choking on the namespace, just get rid of it
    RawXml = RawXml.Replace("xmlns=""http://earth.google.com/kml/2.0""", "")
    ' Parse XML
    Dim Kml As XDocument = XDocument.Parse(RawXml)
    ' Loop through placemarks
    Dim Placemarks = From Placemark In Kml.<Document>.<Folder>.Descendants("Placemark")
    For Each Placemark As XElement In Placemarks
        Dim Name As String = Placemark.<name>.Value
        ...
    Next

If anyone can post working code that works with the namespace instead of nuking it, I'll gladly give them the answer.

Herb Caudill
+1  A: 

Answer

Again, thanks to spoon16 and Bruce Murdock for pointing me in the right direction. The code that spoon16 posted works, but forces you to concatenate the namespace with every single element name, which isn't as clean as I'd like.

I've done a bit more searching and I've figured out how this is supposed to be done - this is super concise, and I love the new <...> bracket syntax for referring to XML elements.

Imports <xmlns:g='http://earth.google.com/kml/2.0'&gt;
Imports System.Xml.Linq

 ...

    Dim Kml As XDocument = XDocument.Load(Server.MapPath("../kmlimport/ga.kml"))
    For Each Placemark As XElement In Kml.<g:Document>.<g:Folder>.<g:Placemark>
        Dim Name As String = Placemark.<g:name>.Value
    Next

Note the :g following the xmlns in the first line. This gives you a shortcut to refer to this namespace elsewhere.

For more about the XNamespace class, see the MSDN documentation.

Herb Caudill
+1  A: 

Scott Hanselman has a concise solution for those looking for a C# based solution.

XLINQ to XML support in VB9

Also, using XNamespace comes in handy, rather than just appending a string. This is a bit more formal.

// This code should get all Placemarks from a KML file            
            var xdoc = XDocument.Parse(kmlContent);
            XNamespace ns = XNamespace.Get("http://earth.google.com/kml/2.0");
            var ele = xdoc.Element(ns + "kml").Element(ns + "Document").Elements(ns + "Placemark");
Matthew Ruston
+2  A: 

This works for me in C#:

    XDocument doc = XDocument.Load(@"TheFile.kml");

    var q = doc.Descendants().Where(x => x.Name.LocalName == "Placemark");
Jacob