I am attempting to read a GML file in C#. I would like to store all the returned data in one object. So far I have been able to return all the data but in 3 separate objects:
XDocument doc = XDocument.Load(fileName);
XNamespace gml = "http://www.opengis.net/gml";
// Points
var points = doc.Descendants(gml + "Point")
.Select(e => new
{
POSLIST = (string)e.Element(gml + "pos")
});
// LineString
var lineStrings = doc.Descendants(gml + "LineString")
.Select(e => new
{
POSLIST = (string)e.Element(gml + "posList")
});
// Polygon
var polygons = doc.Descendants(gml + "LinearRing")
.Select(e => new
{
POSLIST = (string)e.Element(gml + "posList")
});
I would like to instead of creating 3 separate objects, I would like to create one object as follows:
var all = doc.Descendants(gml + "Point")
doc.Descendants(gml + "LineString")
doc.Descendants(gml + "LinearRing")....
But need some help. Thanks before hand.
Sample Data:
<gml:Point>
<gml:pos>1 2 3</gml:pos>
</gml:Point>
<gml:LineString>
<gml:posList>1 2 3</gml:posList>
</gml:LineString>
<gml:LinearRing>
<gml:posList>1 2 3</gml:posList>
</gml:LinearRing>
You can use Concat:
XDocument doc = XDocument.Load(fileName);
XNamespace gml = "http://www.opengis.net/gml";
var all = doc.Descendants(gml + "Point")
.Concat(doc.Descendants(gml + "LineString"))
.Concat(doc.Descendants(gml + "LinearRing"));
For getting the values as the inner elements you can do something like this:
XDocument doc = XDocument.Load("data.xml");
XNamespace gml = "http://www.opengis.net/gml";
var all = doc.Descendants(gml + "Point")
.Select(e => new { Type = e.Name, Value = (string)e.Element(gml + "pos") })
.Concat(doc.Descendants(gml + "LineString")
.Select(e => new { Type = e.Name, Value = (string)e.Element(gml + "posList") }))
.Concat(doc.Descendants(gml + "LinearRing")
.Select(e => new { Type = e.Name, Value = (string)e.Element(gml + "posList") }));
Related
I am trying to parse an xml document with Linq and Lambda expression, but need help.
The Node from within which I want to get data is "DiskDriveInfo" ,
I'm also not sure as to how to proceed with the next node "ResultCode i:nil="true" "
My Code:
var xml = XDocument.Parse(InXML);
var r = from x in xml.Elements("DiskDriveInfo")
select new
{
ResultCode = x.Element("ResultCode").Value,
ResultCodeDescription =
x.Element("ResultCodeDescription").Value,
AirbagDetails = x.Element("AirbagDetails").Value,
..
..
WheelBase = x.Element("WheelBase").Value
};
and the input is :
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Body>
<GetConvergedDataRequestResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://autoinsight.trn.co.za/types">
<ConvergedData xmlns:d4p1="http://schemas.datacontract.orgB2B.BusinessModels" i:type="ConvergedResults">
<AccidentHistory i:nil="true" />
<AlertInfo i:nil="true" />
<CloneInfo i:nil="true" />
<DiskDriveInfo>
<ResultCode i:nil="true" />
<ResultCodeDescription i:nil="true" />
<AirbagDetails>DRIVER, PASSENGER</AirbagDetails>
...
...
<WheelBase>2460</WheelBase>
</DiskDriveInfo>
Thx
There are two problems here:
Your elements are in the namespace "http://autoinsight.trn.co.za/types" but you're looking for them without specifying a namespace
You're using xml.Elements which will only look for root elements; to look for any descendants, you should use Descendants.
So you probably want:
XNamespace ns = "http://autoinsight.trn.co.za/types";
var xml = XDocument.Parse(InXML);
var r = from x in xml.Descendants(ns + "DiskDriveInfo")
select new
{
ResultCode = x.Element(ns + "ResultCode").Value,
ResultCodeDescription = x.Element(ns + "ResultCodeDescription").Value,
AirbagDetails = x.Element(ns + "AirbagDetails").Value,
..
..
WheelBase = x.Element(ns + "WheelBase").Value
};
As a side note, I probably wouldn't use a query expression for this - I'd just call Select directly:
var r = xml
.Descendants(ns + "DiskDriveInfo")
.Select(x => new
{
ResultCode = x.Element(ns + "ResultCode").Value,
ResultCodeDescription = x.Element(ns + "ResultCodeDescription").Value,
AirbagDetails = x.Element(ns + "AirbagDetails").Value,
..
..
WheelBase = x.Element(ns + "WheelBase").Value
});
If you need an element with i:nil="true" to return null instead of an empty string, I'd add an extension method for XElement:
private static XNamespace SchemaNamespace = "http://www.w3.org/2001/XMLSchema-instance";
public static string ValueOrNull(this XElement element)
{
XAttribute nil = element.Attribute(SchemaNamespace + "nil");
return (string) nil == "true" ? null : element.Value;
}
Then call it like this:
XNamespace ns = "http://autoinsight.trn.co.za/types";
var xml = XDocument.Parse(InXML);
var r = from x in xml.Descendants(ns + "DiskDriveInfo")
select new
{
ResultCode = x.Element(ns + "ResultCode").ValueOrNull(),
ResultCodeDescription = x.Element(ns + "ResultCodeDescription").ValueOrNull(),
AirbagDetails = x.Element(ns + "AirbagDetails").ValueOrNull(),
..
..
WheelBase = x.Element(ns + "WheelBase").ValueOrNull()
};
You can write from below code just you need to create class according to you xml file and below is function to convert directly xml to class object
public T DeserializeData(string dataXML)
{
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(dataXML);
XmlNodeReader xNodeReader = new XmlNodeReader(xDoc.DocumentElement);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
var modelData = xmlSerializer.Deserialize(xNodeReader);
T deserializedModel = (T)modelData ;
return deserializedModel;
}
I am removing the duplicated entries in one XML file, and the code is removing them and summing the values to have only one entry of each invoice.
But when it reaches the end it removes 3 lines the following ones:
<NumberOfEntries>11972</NumberOfEntries>
<TotalDebit>0</TotalDebit>
<TotalCredit>34422.86</TotalCredit>
So instead of having the final file like:
...
<SourceDocuments>
<SalesInvoices>
<NumberOfEntries>11972</NumberOfEntries>
<TotalDebit>0</TotalDebit>
<TotalCredit>34422.86</TotalCredit>
<Invoice>
<InvoiceNo>FS 006120180101/19959</InvoiceNo>
...
It appears like:
...
<SourceDocuments>
<SalesInvoices>
<Invoice>
<InvoiceNo>FS 006120180101/19959</InvoiceNo>
...
My code is the following one:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
//Ficheiro
const string FILENAME = "ccc.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XNamespace ns = doc.Root.Name.Namespace;
List<XElement> originalInvoices = doc.Descendants(ns + "Invoice").ToList();
var groups = originalInvoices.GroupBy(x => (string)x.Element(ns + "Hash")).ToList();
var finalInvoices = groups.Select(x => new
{
unit = x.Descendants(ns + "UnitPrice").Sum(z => (decimal)z),
credit = x.Descendants(ns + "CreditAmount").Sum(z => (decimal)z),
tax = x.Descendants(ns + "TaxPayable").Sum(z => (decimal)z),
net = x.Descendants(ns + "NetTotal").Sum(z => (decimal)z),
gross = x.Descendants(ns + "GrossTotal").Sum(z => (decimal)z),
first = x.First()
}).ToList();
foreach (var finalInvoice in finalInvoices)
{
finalInvoice.first.Element(ns + "Line").SetElementValue(ns + "UnitPrice", finalInvoice.unit);
finalInvoice.first.Element(ns + "Line").SetElementValue(ns + "CreditAmount", finalInvoice.credit);
finalInvoice.first.Element(ns + "DocumentTotals").SetElementValue(ns + "TaxPayable", finalInvoice.tax);
finalInvoice.first.Element(ns + "DocumentTotals").SetElementValue(ns + "NetTotal", finalInvoice.net);
finalInvoice.first.Element(ns + "DocumentTotals").SetElementValue(ns + "GrossTotal", finalInvoice.gross);
}
doc.Descendants(ns + "SalesInvoices").FirstOrDefault().ReplaceWith(new XElement(ns + "SalesInvoices", finalInvoices.Select(x => x.first)));
doc.Descendants(ns + "SalesInvoices").
Console.WriteLine(doc);
doc.Save("Root.xml");
Console.ReadKey();
}
}
}
And you can see a sample of my XML file here: Pastebin Link
Can someone help me with this, how can I make it to not remove those 3 lines?
The problem is probably on the last line where it writes the file, but I'm not sure.
doc.Descendants(ns + "SalesInvoices").FirstOrDefault().ReplaceWith(new XElement(ns + "SalesInvoices", finalInvoices.Select(x => x.first)));
Small Update on the question:
Well I really think that the problem is on the line above because if I change for example SalesInvoices with TotalCredit which is the last line of the ones that are disappearing the file still wrong but instead of:
...
<SourceDocuments>
<SalesInvoices>
<Invoice>
<InvoiceNo>FS 006120180101/19959</InvoiceNo>
...
I'm getting:
...
<SourceDocuments>
<SalesInvoices>
<NumberOfEntries>11972</NumberOfEntries>
<TotalDebit>0</TotalDebit>
<TotalCredit>
<Invoice>
<InvoiceNo>FS 006120180101/19959</InvoiceNo>
...
there is a missing 34422.86</TotalCredit> before tag <Invoice>
and it's adding the </TotalCredit> after the first closed element </Invoice> as you can test here: Link to test the code
Easy to fix. Just add the missing 3 elements :
foreach (var finalInvoice in finalInvoices)
{
finalInvoice.first.Element(ns + "Line").SetElementValue(ns + "UnitPrice", finalInvoice.unit);
finalInvoice.first.Element(ns + "Line").SetElementValue(ns + "CreditAmount", finalInvoice.credit);
finalInvoice.first.Element(ns + "DocumentTotals").SetElementValue(ns + "TaxPayable", finalInvoice.tax);
finalInvoice.first.Element(ns + "DocumentTotals").SetElementValue(ns + "NetTotal", finalInvoice.net);
finalInvoice.first.Element(ns + "DocumentTotals").SetElementValue(ns + "GrossTotal", finalInvoice.gross);
}
XElement salesInvoices = doc.Descendants(ns + "SalesInvoices").FirstOrDefault();
XElement numberOfEntries = salesInvoices.Element(ns + "NumberOfEntries");
XElement totalDebit = salesInvoices.Element(ns + "TotalDebit");
XElement totalCredit = salesInvoices.Element(ns + "TotalCredit");
salesInvoices.ReplaceWith(new XElement(ns + "SalesInvoices", new object[] {
numberOfEntries,
totalDebit,
totalCredit,
finalInvoices.Select(x => x.first)
}));
I have a word Document in Xml format with multiple entries like so :
<aml:annotation aml:id="0" w:type="Word.Bookmark.Start" w:name="CustomerName"/>
I want to retrieve a collection of these but cannot see how to get past
foreach (XElement ann in doc.Root.Descendants(aml + "annotation"))
In other words I can get all annotations, but cannot see how to filter to just retrieve bookmarks. The namespaces aml and w are declared like this
XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
XNamespace aml = "http://schemas.microsoft.com/aml/2001/core";
Could someone give me a push ?
I resolved the issue as follows
XNamespace w = doc.Root.GetNamespaceOfPrefix("w");
XNamespace aml = doc.Root.GetNamespaceOfPrefix("aml");
foreach (string bookm in doc.Descendants(aml + "annotation")
.Where(e => e.Attributes(w + "type")
.Any(a => a.Value == "Word.Bookmark.Start"))
.Select(b => b.Attribute(w + "name").Value))
{
...
}
var names = from a in doc.Root.Descendants(aml + "annotation"))
where (string)a.Attribute(w + "type") == "Word.Bookmark.Start"
select (string)a.Attribute(w + "name");
Lambda syntax:
doc.Root.Descendants(aml + "annotation")
.Where(a => (string)a.Attribute(w + "type") == "Word.Bookmark.Start")
.Select(a => (string)a.Attribute(w + "name"))
This solution is not for XML but maybe helps you.
System.Collections.Generic.IEnumerable<BookmarkStart> BookMarks = wordDoc.MainDocumentPart.RootElement.Descendants<BookmarkStart>();
foreach (BookmarkStart current in BookMarks)
{
//Do some...
}
I am having trouble with the OrderByDescending. It does not sort it correctly.
I have an XML file that looks like this:
<player id="3">
<name>David, Backham</name>
<goals>155</goals>
</player>
I am trying to display 3 players with the highest amount of goals.
XDocument doc = XDocument.Load("players.xml");
/// .OrderByDescending(r => r.Attribute("goals"))
var players = from r in doc.Descendants("player").OrderByDescending(r => r.Value)
select new
{
Name = r.Element("name").Value + " ",
Goal = r.Element("goals").Value + " ",
};
foreach (var r in players)
{
Console.WriteLine(r.Name + r.Goal);
}
Perhaps like this:
var players =
(from r in doc.Descendants("player")
orderby int.Parse(r.Element("goals").Value) descending
select new
{
Name = r.Element("name").Value + " ",
Goal = r.Element("goals").Value + " ",
})
.Take(3);
Or in fluent syntax:
var players = doc.Descendants("player")
.OrderByDescending(r => int.Parse(r.Element("goals").Value))
.Select(r => new
{
Name = r.Element("name").Value + " ",
Goal = r.Element("goals").Value + " ",
})
.Take(3);
Note that you'll need to parse the string value to an integer to get the correct sort order.
To filter the results, you can just do this:
var players =
(from r in doc.Descendants("player")
where r.Element("name").Value.StartsWith("David")
orderby int.Parse(r.Element("goals").Value) descending
select new
{
Name = r.Element("name").Value + " ",
Goal = r.Element("goals").Value + " ",
})
.Take(3);
Or in fluent syntax:
var players = doc.Descendants("player")
.Where(r => r.Element("name").Value.StartsWith("David"))
.OrderByDescending(r => int.Parse(r.Element("goals").Value))
.Select(r => new
{
Name = r.Element("name").Value + " ",
Goal = r.Element("goals").Value + " ",
})
.Take(3);
I'm trying to get a List of Styles in the following xml file using xdoc and LINQ.
<?xml version="1.0" encoding="UTF-8"?>
<kml>
<Document>
<Style id="style62">
<IconStyle>
<Icon>
<href>http://maps.gstatic.com/mapfiles/ms2/micons/yellow-dot.png</href>
</Icon>
</IconStyle>
</Style>
</Document>
</kml>
I cannot get my syntax right in order to get the ID="style62" AND also the value within href in the same LINQ select, can anyone help?
var styles = xdoc.Descendants(ns + "Style")
.Select(s => new
{
//HELP!?!
//E.G
//
//id = s.something (style62)
//href = s.something (url)
}).ToList();
if you are talking about a kml file like here https://developers.google.com/kml/documentation/KML_Samples.kml
then below code should work. The problem here is that every "Style" does not contain "href" tag.
var xDoc = XDocument.Parse(xml);
XNamespace ns = "http://www.opengis.net/kml/2.2";
var items = xDoc.Descendants(ns + "Style")
.Select(d =>
{
var h = d.Descendants(ns + "href").FirstOrDefault();
return new
{
Id = d.Attribute("id").Value,
Href = h == null ? null : h.Value
};
})
.ToList();
With a simple extension method, you can simplify the query
XNamespace ns = "http://www.opengis.net/kml/2.2";
var items = xDoc.Descendants(ns + "Style")
.Select(d => new
{
Id = d.Attribute("id").Value,
HRef = d.Descendants(ns + "href").FirstOrDefault()
.IfNotNull(h=>h.Value)
})
.ToList();
public static class S_O_Extensions
{
public static S IfNotNull<T, S>(this T obj,Func<T,S> selector)
{
if (obj == null) return default(S);
return selector(obj);
}
}
Something like this should work:
xdoc.Descendants(ns + "Style")
.Select(s => new
{
id = s.Attribute("id").Value,
href = s.Element("IconStyle")
.Element("Icon")
.Element("href")
.Value
});
Run this through LinqPad:
XDocument doc = XDocument.Parse("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<kml>" +
"<Document>" +
"<Style id=\"style62\">" +
"<IconStyle>" +
"<Icon>" +
"<href>http://maps.gstatic.com/mapfiles/ms2/micons/yellow-dot.png</href>" +
"</Icon>" +
"</IconStyle>" +
"</Style>" +
"</Document>" +
"</kml>");
var styles = from document in doc.Root.Elements("Document")
from style in document.Elements("Style")
where style.Attribute("id").Value == "style62"
select new
{
StyleElement = style,
Href = style.Element("IconStyle").Element("Icon").Element("href").Value
};
styles.Dump();
you can use linq like
var items = doc.Descendants("field")
.Where(node => (string)node.Attribute("name") == "Name")
.Select(node => node.Value.ToString())
.ToList();