I'm struggling to read in a "GPX" file to a WPF (c#) project. Sample GPX is provided below. I've tried a number of different options with the same result.
Document is loading ok, but I'm unable to break it down to access the Nodes Directly.
Any help would be greatly appreciated.
Thanks.
private void Simple_Click(object sender, RoutedEventArgs e)
{
XmlDocument xml = new XmlDocument();
xml.Load(#"C:\Users\Jonathon\Desktop\GPX_Data.gpx");
XmlNodeList nodes = xml.SelectNodes("trkpt"); // have tried: double '/' to get nodes at any level (XPath syntax)
//XmlNodeList nodes = xml.SelectNodes("/gpx/trk/trkseg/trkpt");
int count = 0;
foreach (XmlNode xn in nodes)
{
count++;
}
}
}
Sample GPX File
<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="Endomondo.com"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1
http://www.topografix.com/GPX/1/1/gpx.xsd
http://www.garmin.com/xmlschemas/GpxExtensions/v3
http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd
http://www.garmin.com/xmlschemas/TrackPointExtension/v1
http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd"
xmlns="http://www.topografix.com/GPX/1/1"
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<metadata>
<author>
<name>Jonathon Ralfe</name>
<email id="jonathon" domain="ralfe.net"/>
</author>
<link href="http://www.endomondo.com">
<text>Endomondo</text>
</link>
<time>2015-01-27T18:31:26Z</time>
</metadata>
<trk>
<src>http://www.endomondo.com/</src>
<link href="https://www.endomondo.com/workouts/463986953/2256850">
<text>endomondo</text>
</link>
<type>SKIING_DOWNHILL</type>
<trkseg>
<trkpt lat="45.576892" lon="6.894079">
<time>2015-01-26T09:49:57Z</time>
</trkpt>
<trkpt lat="45.576892" lon="6.894079">
<ele>1595.0</ele>
<time>2015-01-26T09:49:59Z</time>
</trkpt>
<trkpt lat="45.577109" lon="6.893946">
<ele>1581.0</ele>
<time>2015-01-26T09:51:46Z</time>
</trkpt>
<trkpt lat="45.5772" lon="6.894084">
<ele>1575.0</ele>
<time>2015-01-26T09:52:02Z</time>
</trkpt>
<trkpt lat="45.577247" lon="6.894212">
<ele>1577.0</ele>
<time>2015-01-26T09:52:05Z</time>
</trkpt>
<trkpt lat="45.577317" lon="6.89452">
<ele>1589.0</ele>
<time>2015-01-26T09:52:11Z</time>
</trkpt>
That's because your xml contains namespaces, so you have to set namespace when querying data.
Consider this approach:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
nsmgr.AddNamespace("x", "http://www.topografix.com/GPX/1/1");
XmlNodeList nodes = xml.SelectNodes("//x:trkpt", nsmgr);
Here we're creating NamespaceManager, setting namespace according to your data xmlns="http://www.topografix.com/GPX/1/1" attribute and using this namespace in XPath.
By XPath selection:
foreach(XElement aElement in xml.XPathSelectElements("/trk/trkseg").Elements())
{
foreach(XNode aXNode in aElement.Nodes())
{
//Access subnodes of trkpt
}
}
Related
In the XML below, I'm trying to retrieve all the trkpt items so that I can perform a task with the lat / lon attribute values. I thought LINQ to XML would be easiest approach but I can't get any results to be returned in the foreach loop. What am I doing wrong?
This code is in the Main() method of a console app:
var filename = #"C:\temp\sample_output.gpx";
var xDoc = XDocument.Load(filename);
XNamespace xmlns = "http://www.topografix.com/GPX/1/1";
XNamespace gpxtpx = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1";
XNamespace wptx1 = "http://www.garmin.com/xmlschemas/WaypointExtension/v1";
XNamespace gpxx = "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
XNamespace gpxtrkx = "http://www.garmin.com/xmlschemas/TrackStatsExtension/v1";
foreach (var item in xDoc.Descendants(gpxtpx + "trkpt"))
{
Console.WriteLine(item.Element(gpxtpx + "trkpt").Value);
}
Console.Read();
XML data I'm trying to process with the above code. Lat / Lon values modified.
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1"
xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
xmlns:gpxtrkx="http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
xmlns:wptx1="http://www.garmin.com/xmlschemas/WaypointExtension/v1"
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
creator="Oregon 600" version="1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1
http://www.topografix.com/GPX/1/1/gpx.xsd
http://www.garmin.com/xmlschemas/GpxExtensions/v3
http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd
http://www.garmin.com/xmlschemas/TrackStatsExtension/v1
http://www8.garmin.com/xmlschemas/TrackStatsExtension.xsd
http://www.garmin.com/xmlschemas/WaypointExtension/v1
http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd
http://www.garmin.com/xmlschemas/TrackPointExtension/v1
http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd">
<metadata>
<link href="http://www.garmin.com">
<text>Garmin International</text>
</link>
<time>2016-04-18T01:19:07Z</time>
</metadata>
<trk>
<name>2016-04-17 20:19:01</name>
<extensions>
<gpxx:TrackExtension>
<gpxx:DisplayColor>Magenta</gpxx:DisplayColor>
</gpxx:TrackExtension>
</extensions>
<trkseg>
<trkpt lat="44.123" lon="-89.123">
<ele>343.61</ele>
<time>2016-04-17T22:53:34Z</time>
</trkpt>
<trkpt lat="44.123" lon="-89.123">
<ele>343.58</ele>
<time>2016-04-17T22:53:35Z</time>
</trkpt>
<trkpt lat="44.123" lon="-89.123">
<ele>343.56</ele>
<time>2016-04-17T22:53:36Z</time>
</trkpt>
<trkpt lat="44.123" lon="-89.123">
<ele>343.55</ele>
<time>2016-04-17T22:53:37Z</time>
</trkpt>
<trkpt lat="44.123" lon="-89.123">
<ele>343.62</ele>
<time>2016-04-17T22:53:38Z</time>
</trkpt>
</trkseg>
</trk>
</gpx>
You're using the wrong namespace. The default namespace of the elements (including trkpt) is http://www.topografix.com/GPX/1/1, due to this:
xmlns="http://www.topografix.com/GPX/1/1"
I would strongly recommend that you change your variable name to gpxNs to indicate its value, then use it:
XNamespace gpxNs = "http://www.topografix.com/GPX/1/1";
...
foreach (var item in xDoc.Descendants(gpxNs + "trkpt"))
You can do it with XML Linq
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)
{
XDocument doc = XDocument.Load(FILENAME);
XElement trkseg = doc.Descendants().Where(x => x.Name.LocalName == "trkseg").FirstOrDefault();
XNamespace ns = trkseg.Name.Namespace;
var l_l = trkseg.Elements(ns + "trkpt").Select(x => new
{
lat = x.Attribute("lat").Value,
lon = x.Attribute("lon").Value
}).ToList();
}
}
}
I'm trying to edit an element in the following xml, but I'm not sure how to deal with the cdata code... the element I want to edit is FileID
<Import>
<MethodParameters>
<messageXml><![CDATA[
<Message>
<ImportRequest Entity="Users">
<File Name="XXXXXX" Type="Xml" FileID="TestM-00" TotalPartialFiles="1" PartialSequence="1" Md5="">
<![CDATA[
<ImportUserList>
<ImportUser UserName="TestM01" FirstName="TestM01" MiddleName="TestM01" LastName="TestM01" Active="true" Email="TestM01#gmail.com" ExternalId="TestM01"/>
<ImportUser UserName="TestM02" FirstName="TestM02" MiddleName="TestM02" LastName="TestM02" Active="true" Email="TestM02#gmail.com" ExternalId="TestM02"/>
</ImportUserList>
]]]]><![CDATA[>
</File >
</ImportRequest>
</Message>
]]></messageXml>
</MethodParameters>
</Import>
I have something like this in C#, but I'm not sure how to update the Users.xml file, because I know that AdminNode is in the second document
var doc = new XmlDocument();
doc.Load("E:\\Users.xml");
XmlNode node = doc.SelectSingleNode("/Import/MethodParameters/messageXml").FirstChild;
string newID = Guid.NewGuid().ToString();
XmlNodeList xmlRootNode = doc.GetElementsByTagName("messageXml");
String Sxml = xmlRootNode.Item(0).InnerText;
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(Sxml);
XmlNodeList AdminNode = xdoc.GetElementsByTagName("Message");
AdminNode.Item(0).SelectSingleNode("//ImportRequest/File/#FileID").Value = newID;
doc.Save("E:\\Users.xml");
The following code should do the trick:
string xml = #"<Import>
<MethodParameters>
<messageXml><![CDATA[
<Message>
<ImportRequest Entity=""Users"">
<File Name=""XXXXXX"" Type=""Xml"" FileID=""TestM-00"" TotalPartialFiles=""1"" PartialSequence=""1"" Md5="""">
<![CDATA[
<ImportUserList>
<ImportUser UserName=""TestM01"" FirstName=""TestM01"" MiddleName=""TestM01"" LastName=""TestM01"" Active=""true"" Email=""TestM01#gmail.com"" ExternalId=""TestM01""/>
<ImportUser UserName=""TestM02"" FirstName=""TestM02"" MiddleName=""TestM02"" LastName=""TestM02"" Active=""true"" Email=""TestM02#gmail.com"" ExternalId=""TestM02""/>
</ImportUserList>
]]]]><![CDATA[>
</File>
</ImportRequest>
</Message>
]]></messageXml>
</MethodParameters>
</Import>";
var newID = Guid.NewGuid().ToString();
var xDoc = XDocument.Parse(xml);
var messageNode = xDoc.XPathSelectElement(".//messageXml");
var innerDoc = XDocument.Parse(messageNode.Value);
innerDoc.XPathSelectElement(".//ImportRequest/File").Attribute("FileID").Value = newID;
messageNode.ReplaceAll(new XCData(innerDoc.ToString()));
It's pretty dirty now, but at least it produces the expected output:
<Import>
<MethodParameters>
<messageXml><![CDATA[<Message>
<ImportRequest Entity="Users">
<File Name="XXXXXX" Type="Xml" FileID="609bead3-3295-4d68-a07f-1ee47db756fd" TotalPartialFiles="1" PartialSequence="1" Md5="">
<![CDATA[
<ImportUserList>
<ImportUser UserName="TestM01" FirstName="TestM01" MiddleName="TestM01" LastName="TestM01" Active="true" Email="TestM01#gmail.com" ExternalId="TestM01"/>
<ImportUser UserName="TestM02" FirstName="TestM02" MiddleName="TestM02" LastName="TestM02" Active="true" Email="TestM02#gmail.com" ExternalId="TestM02"/>
</ImportUserList>
]]]]><![CDATA[>
</File>
</ImportRequest>
</Message>]]></messageXml>
</MethodParameters>
</Import>
This question already has an answer here:
Error: "The node to be inserted is from a different document context"
(1 answer)
Closed 8 years ago.
I am using XML as discuss forum in ASP.NET. I am trying to add functionality for updating comments. My XML structure is this:
<forum>
<author id="1">
<comment id="0" idUser="19">
<name>....</name>
<date>....</date>
<message>...</message>
</comment>
<comment id="1" idUser="4">
....
</comment>
</author>
<author id="2">
....
</author>
</forum>
And my code is:
protected void btnEditComment_Click(object sender, EventArgs e)
{
if (messageTxb.Text!="" && nameTxb.Text!="")
{
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/App_Data/forum.xml"));
XmlNode newComment = (XmlNode)Session["Comment"];
XmlNode oldComment = doc.SelectSingleNode(string.Format("//author[#id={0}]/comment[#id={1}]",Request.QueryString["id"],newComment.Attributes["id"].Value));
newComment.ChildNodes[0].InnerText = nameTxb.Text.Trim();
newComment.ChildNodes[1].InnerText = string.Format("{0:D}", DateTime.Now);
newComment.ChildNodes[2].InnerText = messageTxb.Text.Trim();
oldComment.ParentNode.ReplaceChild(newComment, oldComment);
doc.Save(Server.MapPath("~/App_Data/forum.xml"));
Repeater1.DataBind();
Response.Redirect(string.Format("~/user/Autor.aspx?id={0}", Request.QueryString["id"]));
}
}
I get error
'The node to be inserted is from a different document context.'
It raises when I am trying to use the replaceChild method.
try to replace this row:
oldComment.ParentNode.ReplaceChild(doc.ImportNode(newComment, true), oldComment);
I have seen a couple of examples on here where Xpath is used in conjunction with XmlDocument to get a specific attribute from an XmlDocument Node.... Example
Console.WriteLine(xmlDocument.SelectSingleNode("//dataTemplateSpecification/templates/template/elements/element/#name").Value.ToString());
For some reason I am getting a "Object reference not set to an instance of an object." exception. Whenever I run across that particular line of code. I have a little test app that I have set up to test out different things before I put them into my main project...
Here is the code for that...
namespace ReadXml
{
class Program
{
static void Main(string[] args)
{
//string fulXmlPath = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/templateExample.xml");
XDocument xDocument = XDocument.Load("C:\\Users\\derekww\\Documents\\XML Documents\\templateExample.xml");
XElement elem = xDocument.Element("dataTemplateSpecification"); ;
XmlDocument xmlDocument = new XmlDocument();
StreamReader file = new StreamReader("C:\\Users\\derekww\\Documents\\XML Documents\\templateExample.xml");
xmlDocument.Load(file);
//XmlDocument theDoc = new XmlDocument();
//using (var xmlReader = xDocument.CreateReader())
//{
// xmlDocument.Load(xmlReader);
//}
//Console.WriteLine(elem.ToString());
XmlNode xNode = xmlDocument.SelectSingleNode("//dataTemplateSpecification/templates/template/elements/element");
Console.WriteLine("WORK PLEASE!!!! {0}", xNode.Value.ToString());
//Console.WriteLine(xmlDocument.SelectSingleNode("//dataTemplateSpecification/templates/template/elements/element/#name").Value.ToString());
//Console.WriteLine("This better Work>>>> {0}", xmlDocument.Attributes["/dataTemplateSpecification/templates/template/elements/element/#name"].Value);
Console.ReadLine();
//Console.WriteLine("This better Work>>>> {0}", xmlDocument.SelectSingleNode("//dataTemplateSpecification/templates/template/elements/element/#name").Value);
//foreach (String AttVal in xmlDocument.SelectSingleNode("//dataTemplateSpecification/templates/template/elements/element/#name").Value)
{
//Console.WriteLine("This better Work>>>> {0}", AttVal);
}
}
}
}
Here is part of the XML that I used...
<?xml version="1.0" encoding="utf-8"?>
<dataTemplateSpecification id="id1" name="name1" xmlns="http://EADIS.upmc.com /DataTemplateSpecification.xsd">
<description xmlns="">
<html>text</html>
</description>
<templates xmlns="">
<template>
<elements>
<element id="element0" name="PatientId" display="Patient ID" dataType="String" value="0101010111111" visable="false" readOnly="true">
<validation>
<rules>
<rule id="0" test="#element0.value == ''">
<fail>
<html><b>Patient ID is null, value must be present</b></html>
</fail>
</rule>
</rules>
</validation>
</element>
</elements>
</template>
<templates>
I just showed you the part that you need to understand the xml structure. I assure you that it is well formed. I think I asked this question before but somehow or the other it didn't get posted (maybe I forgot, who knows). Any help with this would be greatly appreciated. If I come up with a reason for why it isn't working I will be sure to let you guys know.
Thank You.
Why can't you use this XPath:
xmlDocument.SelectSingleNode("//templates/template/elements/element/#name").Value
You need to specify the namespace of the XML file in your code.
See here for more info: How to select xml root node when root node has attribute?
I'm reading in a Kml file, changing the placemarks' names, and saving it again.
var KmlFile = XDocument.Load("C:\\Inetpub\\wwwroot\\GeotagService\\Kml\\Photographs.kml");
XNamespace KmlNamespace = "http://www.opengis.net/kml/2.2";
// find the Placemarks in the Photos folder
IEnumerable<XElement> Placemarks = KmlFile.Element(KmlNamespace + "kml").Element(KmlNamespace + "Document").Element(KmlNamespace + "Folder").Elements(KmlNamespace + "Placemark");
foreach (XElement p in Placemarks){
p.Element(KmlNamespace + "name").Value = "testing";
}
KmlFile.Save("C:\\Inetpub\\wwwroot\\GeotagService\\Kml\\Photographs.kml");
When I save it however, every element is prefixed with <kml:, like this:
<kml:Folder>
<kml:name>Photos</kml:name>
<kml:open>1</kml:open>
<kml:Placemark>
<kml:name>testing</kml:name>
<kml:LookAt>
<kml:longitude>-10.02717694938161</kml:longitude>
<kml:latitude>53.48672543547379</kml:latitude>
<kml:altitude>0</kml:altitude>
</kml:LookAt>
<kml:styleUrl>#msn_ylw-pushpin1</kml:styleUrl>
<kml:Point>
<kml:coordinates>-10.02867619360582,53.48651240326751,0</kml:coordinates>
</kml:Point>
</kml:Placemark>...
Tomalak's comment on this question about blank xmlns gives me a clue that it might be inconsistencies between the namespaces of the document and the elements, but I can't see how I'm doing that. Anyone know?
EDIT: Original document (in part):
<?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>Connemara.net Photographs</name>
<open>1</open>
<Style id="sh_ylw-pushpin0">
<IconStyle>
<scale>1.3</scale>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>
</Icon>
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels"/>
</IconStyle>
</Style>
<Folder>
<name>Photos</name>
<open>1</open>
<Placemark>
<name>Id:579</name>
<LookAt>
<longitude>-10.02717694938161</longitude>
<latitude>53.48672543547379</latitude>
<altitude>0</altitude>
<range>213.2931913230747</range>
<tilt>75.17546328115256</tilt>
<heading>69.89736514305363</heading>
<altitudeMode>relativeToGround</altitudeMode>
<gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
</LookAt>
<styleUrl>#msn_ylw-pushpin1</styleUrl>
<Point>
<coordinates>-10.02867619360582,53.48651240326751,0</coordinates>
</Point>
</Placemark>
...
</Folder>
</Document>
</kml>
Got it - the problem is that the original document starts like this:
<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">
LINQ to XML sees that there's a specific alias for the KML namespace, so uses that. If you remove that attribute, it works fine:
var explicitNs = KmlFile.Root.Attribute(XNamespace.Xmlns + "kml");
if (explicitNs != null)
{
explicitNs.Remove();
}
You need to use a namespace manager. Here's an example using XPath to select the necessary tags and then update their value:
using System;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
class Program
{
static void Main(string[] args)
{
var kml = XDocument.Load("test.kml");
var ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("kml", "http://www.opengis.net/kml/2.2");
var names = kml.XPathSelectElements("//kml:Placemark/kml:name", ns);
foreach (var name in names)
{
name.Value = "testing";
}
kml.Save("test.kml");
}
}