I want to create a kml file in c#. Now I have two problems:
What is the synatx to add the kml element in the xml file in order to have the line below in my kml file?
<kml xmlns="http://www.opengis.net/kml/2.2">
I have an array of points that i would like to form a linestring. How am I suppose to fill the coordinates element in xml for the kml file?.The following is my code so far.
CODE:
public void MakeKmlFile(string line)
{
CoordinateCollection coordinates = new CoordinateCollection();
char[] delimiterLine = { '|' };
char[] delimiterPoint = { ',' };
string[] route = line.Split(delimiterLine);
foreach (string point in route)
{
string[] route_point = line.Split(delimiterPoint);
double lat = double.Parse(route_point[1]);
double lon = double.Parse(route_point[0]);
coordinates.Add(new Vector(lat, lon));
}
XmlTextWriter writer = new XmlTextWriter("route.xml", System.Text.Encoding.UTF8);
writer.Formatting = Formatting.Indented;
writer.WriteStartElement("Document");
writer.WriteStartElement("Folder");
writer.WriteStartElement("name");
writer.WriteString("route");
writer.WriteEndElement();
writer.WriteStartElement("Placemark");
writer.WriteStartElement("Style");
writer.WriteStartElement("LineStyle");
writer.WriteStartElement("color");
writer.WriteString("ff0000ff");
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteStartElement("PolyStyle");
writer.WriteStartElement("fill");
writer.WriteString("2");
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteStartElement("LineString");
writer.WriteStartElement("coordinates");
This is the result i got:
<?xml version="1.0" encoding="utf-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<Name>Points.kml</Name>
<Placemark />
<Placemark />
<Placemark />
<Placemark />
<Placemark />
</Document>
You can create the KML document just like a normal XML document
XmlDocument xDoc = new XmlDocument();
XmlDeclaration xDec = xDoc.CreateXmlDeclaration("1.0", "utf-8", null);
XmlElement rootNode = xDoc.CreateElement("kml");
rootNode.SetAttribute("xmlns", #"http://www.opengis.net/kml/2.2");
xDoc.InsertBefore(xDec, xDoc.DocumentElement);
xDoc.AppendChild(rootNode);
XmlElement docNode = xDoc.CreateElement("Document");
rootNode.AppendChild(docNode);
XmlElement nameNodeMain = xDoc.CreateElement("Name");
XmlText nameTextMain = xDoc.CreateTextNode("Points.kml");
docNode.AppendChild(nameNodeMain);
nameNodeMain.AppendChild(nameTextMain);
That sets up the basic structure for your document then all you need to do is add each placemark (This is best done through a loop)
char[] delimiterLine = { '|' };
char[] delimiterPoint = { ',' };
string[] places = line.Split(delimiterLine);
for (int i = 0; i < places.length; i++)
{
string[] placeMark = places[i].split(delimiterPoint);
XmlElement placeNode = xDoc.CreateElement("Placemark");
docNode.AppendChild(placeNode);
XmlElement nameNode = xDoc.CreateElement("Name");
XmlText nameText = xDoc.CreateTextNode(placeMark[0]);
placeNode.AppendChild(nameNode);
nameNode.AppendChild(nameText);
XmlElement descNode = xDoc.CreateElement("Description");
XmlText descText = xDoc.CreateTextNode(placeMark[1]);
placeNode.AppendChild(descNode);
descNode.AppendChild(descText);
XmlElement pointNode = xDoc.CreateElement("Point");
placeNode.AppendChild(pointNode);
XmlElement coordNode = xDoc.CreateElement("coordinates");
XmlText coordText = xDoc.CreateTextNode(string.Format("{0},{1}", placeMark[2], placeMark[3]));
pointNode.AppendChild(coordNode);
coordNode.AppendChild(coordText);
}
return xDoc;
I haven't worked with LineStrings in KML before but I suspect the code to do that would be along the lines of the following:
XmlDocument xDoc = new XmlDocument();
XmlDeclaration xDec = xDoc.CreateXmlDeclaration("1.0", "utf-8", null);
XmlElement rootNode = xDoc.CreateElement("kml");
rootNode.SetAttribute("xmlns", #"http://www.opengis.net/kml/2.2");
xDoc.InsertBefore(xDec, xDoc.DocumentElement);
xDoc.AppendChild(rootNode);
XmlElement docNode = xDoc.CreateElement("Document");
rootNode.AppendChild(docNode);
XmlElement nameNodeMain = xDoc.CreateElement("Name");
XmlText nameTextMain = xDoc.CreateTextNode("Points.kml");
docNode.AppendChild(nameNodeMain);
nameNodeMain.AppendChild(nameTextMain);
XmlElement placeNode = xDoc.CreateElement("Placemark");
docNode.AppendChild(placeNode);
XmlElement nameNode = xDoc.CreateElement("Name");
XmlText nameText = xDoc.CreateTextNode("Test line");
placeNode.AppendChild(nameNode);
nameNode.AppendChild(nameText);
XmlElement lineStringNode = xDoc.CreateElement("LineString");
placeNode.AppendChild(lineStringNode);
XmlElement coordNode = xDoc.CreateElement("coordinates");
char[] delimiterLine = { '|' };
char[] delimiterPoint = { ',' };
string[] places = line.Split(delimiterLine);
for (int i = 0; i < places.length; i++)
{
string[] placeMark = places[i].split(delimiterPoint);
XmlText coordText = xDoc.CreateTextNode(string.Format("{0},{1}", placeMark[0], placeMark[1]));
pointNode.AppendChild(coordNode);
}
coordNode.AppendChild(coordText);
xDoc.Save("./KML/");
It basically involves moving my previous code around and creating a single XmlElement for each of the main elements required in a KML file and then iterating through the coordinates after splitting them in the line string.
Most direct solution is to tokenize the route (line string) and append coordinates in a string buffer then output as a value. No need to create the CoordinateCollection and separate Vector objects.
NOTE: In order to be valid KML you must output the longitude value first then latitude separated by comma (,) with no whitespace between the lon-lat values and whitespace must separate each lon-lat pair optionally with an altitude value.
Here's C# solution using the System.Xml.XmlTextWriter class:
XmlTextWriter writer = new XmlTextWriter(...);
writer.WriteStartElement("kml", "http://www.opengis.net/kml/2.2");
...
writer.WriteStartElement("LineString");
StringBuilder sb = new StringBuilder();
foreach (string point in route)
{
string[] route_point = point.Split(delimiterPoint);
if (route_point.Length >= 2)
{
double lon = double.Parse(route_point[0]);
double lat = double.Parse(route_point[1]);
sb.Append(' ').Append(lon).Append(',').Append(lat);
// coordinates.Add(new Vector(lat, lon));
}
}
writer.WriteStartElement("coordinates");
writer.WriteValue(sb.ToString());
writer.WriteEndElement(); // end coordinates
writer.WriteEndElement(); // end LineString
writer.WriteEndElement(); // end Placemark
...
writer.Close();
Related
How do we iterate an XmlDocument using an xpath?
I'm attempting to return a list of nodes by xpath:
public static List<string> Filter(string xpath, string input, string ns, string nsUrl)
{
var bytes = Encoding.UTF8.GetBytes(input); //i believe this unescapes the string
var stream = new MemoryStream(bytes);
var doc = new XmlDocument();
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace(ns, nsUrl);
var links = new List<string>();
var nodes = doc.SelectNodes(xpath, namespaceManager);
using (var reader = new XmlTextReader(stream))
{
reader.Namespaces = false;
doc.Load(reader);
}
foreach (XmlNode node in nodes)
{
if (IsNullOrWhiteSpace(node.InnerText))
{
continue;
}
links.Add(node.InnerText);
}
return links;
}
however, the count is always 0 !
I'm using this xpath. notice how i am using only 1 namespace:
/ns0:Visit/ns0:DocumentInterface/ns0:Documents/ns0:Document/ns0:BinaryData
The header of the file looks like this:
<ns0:Visit xmlns:ns0="http://NameSpace.ExternalSchemas.Patient"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
I'm certain that I am using the right xpath because I tested it against my payload:
I'm calling the function this way:
var links = Filter(xpath, xml, "ns0", "http://NameSpace.ExternalSchemas.Patient");
How do we iterate an XmlDocument using an xpath? Perhaps the XmlDocument should be an XDocument instead?
this is the code
XmlDocument xml = new XmlDocument();
XmlElement root = xml.CreateElement("customers");
xml.AppendChild(root);
foreach (var cust in customerlist)
{
XmlElement child = xml.CreateElement("customer");
child.SetAttribute("CustomerId", cust.CustomerId.ToString());
child.SetAttribute("CustomerName", cust.CustomerName);
child.SetAttribute("PhoneNumber", cust.PhoneNumber);
child.SetAttribute("Email", cust.Email);
root.AppendChild(child);
}
string s = xml.OuterXml;
I want my string to have next lines added to it instead of a single xml document
My string is coming as continuous
< x >xxxxx< /x > < x >xxxxx< /x >
You can use the XmlTextWriter class to format the XML as a string like this:
StringWriter string_writer = new StringWriter();
XmlTextWriter xml_text_writer = new XmlTextWriter(string_writer);
xml_text_writer.Formatting = Formatting.Indented;
xml.WriteTo(xml_text_writer); // xml is your XmlDocument
string formattedXml = string_writer.ToString();
I have a C# function like this in my XSLT stylesheet:
<xsl:stylesheet ...
xmlns:utils="urn:local">
<msxsl:script language="CSharp" implements-prefix="utils">
<![CDATA[
public XmlDocument dateSplit(string str)
{
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement(string.Empty, "root", string.Empty);
Regex rgx = new Regex("(?:(\\d{1,2})\\.(\\d{1,2})\\.)?(\\d{4})?");
Match match = rgx.Match(str);
XmlElement yearElem = doc.CreateElement(string.Empty, "year", string.Empty);
XmlElement monthElem = doc.CreateElement(string.Empty, "month", string.Empty);
XmlElement dayElem = doc.CreateElement(string.Empty, "day", string.Empty);
if (match.Success) {
string dayVal = match.Groups[1].Value;
string monthVal = match.Groups[2].Value;
string yearVal = match.Groups[3].Value;
if (dayVal != "" && monthVal != "" && yearVal != "") {
XmlText dayText = doc.CreateTextNode(dayVal.PadLeft(2, '0'));
XmlText monthText = doc.CreateTextNode(monthVal.PadLeft(2, '0'));
XmlText yearText = doc.CreateTextNode(yearVal);
dayElem.AppendChild(dayText);
monthElem.AppendChild(monthText);
yearElem.AppendChild(yearText);
} else if (yearVal != "") {
XmlText yearText = doc.CreateTextNode(yearVal);
yearElem.AppendChild(yearText);
}
}
root.AppendChild(yearElem);
root.AppendChild(monthElem);
root.AppendChild(dayElem);
doc.AppendChild(root);
return doc;
}
]]>
</msxsl:script>
It turns "1960" into <year>1960</year>, "4.7.2016" into <year>2016</year><month>07</month><day>04</day> etc.
In order to add the elements year, month and day flat into my output XML...
<someOtherStuff>...</someOtherStuff>
<year>2016</year>
<month>07</month>
<day>04</day>
<moreStuff>...</moreStuff>
... I have to use the function like this:
<xsl:copy-of select="utils:dateSplit(myInput)/root/*"/>
I can't avoid the auxiliary <root> element in the dateSplit() function, because XmlDocument must be well-formed (only a single element at the top level). It's not possible to append multiple elements to the root.
Is there an alternative, something like a ResultTreeFragment, that does not ensure well-formedness in order to avoid the artificial and temporary <root> element?
If you create an XmlDocumentFragment with CreateDocumentFragement then you can add your elements to that fragment and return it instead of the XmlDocument:
<msxsl:script language="CSharp" implements-prefix="utils">
<![CDATA[
public XmlDocumentFragment dateSplit(string str)
{
XmlDocument doc = new XmlDocument();
XmlDocumentFragment docFrag = doc.CreateDocumentFragment();
// ...
docFrag.AppendChild(yearElem);
docFrag.AppendChild(monthElem);
docFrag.AppendChild(dayElem);
return docFrag;
And then use it like this:
<xsl:copy-of select="utils:dateSplit(myInput)"/>
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 9 years ago.
i want to parse kml in my c# app.
XmlDocument doc = new XmlDocument();
doc.Load(fileKml);
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("x", "http://www.opengis.net/kml/2.2");
XmlNode nodeKmlns = doc.SelectSingleNode("/x:kml", ns); string sKmlns = nodeKmlns.InnerText;
XmlNode nodeName = doc.SelectSingleNode("GroundOverlay/name"); string sName = nodeName.InnerText;
XmlNode nodehref = doc.SelectSingleNode("GroundOverlay/Icon/href"); string shref = nodehref.InnerText;
XmlNode north = doc.SelectSingleNode("GroundOverlay/LatLonBox/north"); string snorth = north.InnerText; double yn = Convert.ToDouble(snorth);
XmlNode south = doc.SelectSingleNode("GroundOverlay/LatLonBox/south"); string ssouth = south.InnerText; double ys = Convert.ToDouble(ssouth);
XmlNode east = doc.SelectSingleNode("GroundOverlay/LatLonBox/east"); string seast = east.InnerText; double xe = Convert.ToDouble(seast);
XmlNode west = doc.SelectSingleNode("GroundOverlay/LatLonBox/west"); string swest = west.InnerText; double xw = Convert.ToDouble(swest);
and here is my .kml
<?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">
<GroundOverlay>
<name>osm_bandung</name>
<Icon>
<href>files/osm_bandung.png</href>
<viewBoundScale>0.75</viewBoundScale>
</Icon>
<LatLonBox>
<north>-6.928631334672425</north>
<south>-6.956054957857409</south>
<east>107.6467976125619</east>
<west>107.6030622981136</west>
</LatLonBox>
</GroundOverlay>
</kml>
i use addNameSpace but still error
when running, the code error in line
XmlNode nodeName = doc.SelectSingleNode("GroundOverlay/name"); string sName = nodeName.InnerText;
the error is NullReferenceException Object reference not set to an instance of an object.
how to fix that?
I suggest you to use LINQ to XML:
var xdoc = XDocument.Load("data.xml");
XNamespace ns = xdoc.Root.GetDefaultNamespace();
var overlay = xdoc.Root.Element(ns + "GroundOverlay");
var icon = overlay.Element(ns + "Icon");
var box = overlay.Element(ns + "LatLonBox");
var groundOverlay = new
{
Name = (string)overlay.Element(ns + "name"),
Icon = new
{
Href = (string)icon.Element(ns + "href"),
ViewBoundScale = (double)icon.Element(ns + "viewBoundScale")
},
LatLonBox = new
{
North = (double)box.Element(ns + "north"),
South = (double)box.Element(ns + "south"),
East = (double)box.Element(ns + "east"),
West = (double)box.Element(ns + "west")
}
};
Then you can simply use
groundOverlay.LatLonBox.East // 107.6467976125619
Consider also creating custom classes instead of using anonymous types here
Since you specify at least one namespace in your document, all the nodes belong to that namespace. Therefore, you need to always specify the namespace when accessing a node, just like you do when accessing the nodeKmlns node:
XmlDocument doc = new XmlDocument();
doc.Load(fileKml);
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("x", "http://www.opengis.net/kml/2.2");
XmlNode nodeKmlns = doc.SelectSingleNode("/x:kml", ns);
string sKmlns = nodeKmlns.InnerText;
XmlNode nodeName = doc.SelectSingleNode("GroundOverlay/name", ns);
string sName = nodeName.InnerText;
XmlNode nodehref = doc.SelectSingleNode("GroundOverlay/Icon/href", ns);
string shref = nodehref.InnerText;
XmlNode north = doc.SelectSingleNode("GroundOverlay/LatLonBox/north", ns);
string snorth = north.InnerText; double yn = Convert.ToDouble(snorth);
XmlNode south = doc.SelectSingleNode("GroundOverlay/LatLonBox/south", ns);
string ssouth = south.InnerText; double ys = Convert.ToDouble(ssouth);
XmlNode east = doc.SelectSingleNode("GroundOverlay/LatLonBox/east", ns);
string seast = east.InnerText; double xe = Convert.ToDouble(seast);
XmlNode west = doc.SelectSingleNode("GroundOverlay/LatLonBox/west", ns);
string swest = west.InnerText; double xw = Convert.ToDouble(swest);
I want to read an XML file and match tag </contrib-group> and write a string after this tag
string Final = File.ReadAllText(Npath);
string Oxml = path + "\\" + Oword + ".abs.xml";
if (File.Exists(Oxml))
{
StreamReader xml = new StreamReader(Oxml,Encoding.UTF8);
string xmltag = xml.ReadToEnd();
//File.OpenWrite(Oxml);
xml.Close();
StreamWriter write = new StreamWriter(Oxml, true, Encoding.UTF8);
Match tag = Regex.Match(xmltag, #"</contrib-group>");
if (tag.Success == true)
{
write.WriteLine(Environment.NewLine);
write.Write(Final);
}
}
So I need to write the string Final to the XML file called Oxml after the matched XML tag </contrib-group>
If you are willing to save the new content as a valid XML file which you can work with, you should use the XML classes for that approach, this should look like this (untested):
XmlDocument doc = new XmlDocument();
doc.Load("YourFile.xml");
XmlElement root = doc.DocumentElement;
XmlNodeList elemList = root.GetElementsByTagName("contrib-group");
for (int i=0; i < elemList.Count; i++)
{
XmlNode xnode = elemList[i];
XmlNode xnodeParent = xnode.ParentNode;
XMLNode newNode = doc.CreateNode(XmlNodeType.Element, "NodeName", "");
newNode.InnerText = "ContentInsideTheNode";
xnodeParent.InsertAfter(newNode, xnode);
}
doc.Save("YourFile.xml");
If you only need to replace the string for other purposes than saving it where having a valid XML is not an issue you can just handle it as a string and use the String.Replace (String, String) Method
string searchedTag = #"</contrib-group>";
string tagAndNewContent = #"</contrib-group>" + newContent;
string fileContentString = File.ReadAllText("YourFile.xml");
string ouput = fileContentString.Replace(searchedTag, tagAndNewContent);