Having some difficulties deserializing the following xml to an object in the most efficient/cleanest possible way:
<item xsi:type="ns2:Map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<item>
<key xsi:type="xsd:string">mailinglistid</key>
<value xsi:type="xsd:string">1</value>
</item>
<item>
<key xsi:type="xsd:string">uniqueid</key>
<value xsi:type="xsd:string">1a0d0d2195</value>
</item>
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">OSM NL</value>
</item>
</item>
This is a single item along with its properties. As you can see the properties are defined as key/value pairs.
Because they are key value pairs i could not find a way to use the Xml deserialize attributes (as described in this question: How to deserialize xml to object )
Therefore i have created the following alternative:
// Retrieves all the elements from the "return" node in the Mailinglist_allResponse node.
var items = response.ResponseFromServer.Descendants(_engineResponseNamespace + "Mailinglist_allResponse").Descendants("return").Elements();
foreach (var item in items)
{
var values = item.Descendants("item").ToArray();
var list = new EngineMailingList
{
MailingListId =
values.Descendants("key")
.First(v => v.Value == "mailinglistid")
.ElementsAfterSelf("value")
.First()
.Value,
UniqueId =
values.Descendants("key")
.First(v => v.Value == "uniqueid")
.ElementsAfterSelf("value")
.First()
.Value,
Name =
values.Descendants("key")
.First(v => v.Value == "name")
.ElementsAfterSelf("value")
.First()
.Value,
};
result.Add(list);
As you can see this is alot more code than when using the deserialize attributes. Is there any way i could still use these attributes so that my code could be cleaner/ efficient? i am going to have to make alot of these functions otherwise.
I don't think attributes will work because of key/value structure. There is no way for a program to infer from the xml alone what properties an object has. I would make a static extension method helper function to get the values:
public static string GetValue(this XElement element, string key){
return values.Descendants("key")
.First(v => v.Value == key)
.ElementsAfterSelf("value")
.First()
.Value;
}
That way your code could look like this:
MailingListId = values.GetValue("mailinglistid"),
UniqueId = values.GetValue("uniqueid"),
Name = values.GetValue("name")
Related
I have 2 XML Files and I want to get all the XNodes , which are in both files, only based on their same Attribute "id".
This is how an XML File looks like:
<parameters>
<item id="57">
<länge>8</länge>
<wert>0</wert>
</item>
<item id="4">
<länge>8</länge>
<wert>0</wert>
</item>
<item id="60">
<länge>8</länge>
<wert>0</wert>
</item>
</parameters>
Given a second XML File which looks like this:
<parameters>
<item id="57">
<länge>16</länge>
<wert>10</wert>
</item>
<item id="144">
<länge>16</länge>
<wert>10</wert>
</item>
</parameters>
Now I only want the XNode with the ID=57, since it is available in both files. So the output should look like this:
<item id="57">
<länge>8</länge>
<wert>0</wert>
</item>
I already did intersect both files like this:
aDoc = XDocument.Load(file);
bDoc = XDocument.Load(tmpFile);
intersectionOfFiles = aDoc.Descendants("item")
.Cast<XNode>()
.Intersect(bDoc.Descendants("item")
.Cast<XNode>(), new XNodeEqualityComparer());
This only seems to work, when all the descendant Nodes are the same. If some value is different, it won't work. But I need to get this to work on the same Attributes, the values or the descendants doesn't matter.
I also tried to get to the Attributes and intersect them, but this didn't work either:
intersectionOfFiles = tmpDoc
.Descendants(XName.Get("item"))
.Attributes()
.ToList()
.Intersect(fileDoc.Descendants(XName.Get("item")).Attributes()).ToList();
Am I missing something or is this a completely wrong approach?
Thanks in advance.
You should create your own IEqualityComparer that compares XML attributes you want:
public class EqualityComparerItem : IEqualityComparer<XElement>
{
public bool Equals(XElement x, XElement y)
{
return x.Attribute("id").Value == y.Attribute("id").Value;
}
public int GetHashCode(XElement obj)
{
return obj.Attribute("id").Value.GetHashCode();
}
}
which you would then pass to XML parsing code:
var intersectionOfFiles = aDoc.Root
.Elements("item")
.Intersect(
bDoc.Root
.Elements("item"), new EqualityComparerItem());
I also changed some parts of your XML parsing code (XElement instead of XNode since "item" is XML element and "id" is XML attribute).
I currently have the following C# with Linq to XML code working but I'm hoping there is a better way to form the Linq query to then be able to extract the results into a flat structure.
Here are the current results formatted as I would like them to appear:
7990697: result_status = Complete
7990756: result_status = Incomplete
I want to be able to access the results using the syntax result.resultStatusId instead of result.meta.ToList()[0].resultStatusId
var xDoc = XDocument.Parse(#"<fs_response status='success' timestamp='2014-07-10 14:38:39'>
<headings>
<heading for='result_status'>Result Status</heading>
<heading for='1'>First Name</heading>
<heading for='2'>Last Name</heading>
</headings>
<results>
<result id='7990697'>
<metas>
<meta id='result_status'>Complete</meta>
</metas>
<items>
<item id='1' index='3' type='text'>
<value>Bugs</value>
</item>
<item id='2' index='4' type='text'>
<value>Bunny</value>
</item>
</items>
</result>
<result id='7990756'>
<metas>
<meta id='result_status'>Incomplete</meta>
</metas>
<items>
<item id='1' index='3' type='text'>
<value>Yogi</value>
</item>
<item id='2' index='4' type='text'>
<value>Bear</value>
</item>
</items>
</result>
</results>
<total_results>2</total_results>
</fs_response>");
var results =
from x in xDoc.Elements("fs_response").Elements("results").Elements("result")
select new
{
id = x.Attribute("id").Value,
meta = from m in x.Elements("metas").Elements("meta")
where m.Attribute("id").Value == "result_status"
select new
{
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value
}
};
results.Dump();
foreach (var result in results.ToList())
{
Console.WriteLine("{0}: {1} = {2}",
result.id,
result.meta.ToList()[0].resultStatusId,
result.meta.ToList()[0].resultStatus);
//Console.WriteLine("{0}: {1} = {2}",
// result.id,
// result.resultStatusId,
// result.resultStatus);
}
I would change the query to something like this:
var results = xDoc.Element("fs_response")
.Element("results")
.Elements("result")
.Select (e => new
{
id = e.Attribute("id").Value,
meta = e.Element("metas")
.Elements("meta")
.Where (m =>
m.Attribute("id").Value == "result_status")
.Select (m => new
{
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value
})
.Single()
});
Some notes:
Notice the change in a few locations from Elements to Element. This indicates that you know there will only be one result with that name, or if you only want the first with that name. Use Elements when you expect multiple results. In some cases in the example above, I'm making assumptions about your xml schema (such as the fact that there will only be one metas Element per result). This may perform faster as it will not have to continue searching for Elements of a specific name once it has found one.
Also see the use of Single(). This indicates that you are expecting one and only one element with the id attribute equal to result_status. If you expect 0 or 1, you can use SingleOrDefault(), or if you want the first in a potential list of matching elements you can use First() or FirstOrDefault(). Once you add this, you will no longer have to call ToList()[0] in the results:
Console.WriteLine("{0}: {1} = {2}",
result.id,
result.meta.resultStatusId,
result.meta.resultStatus);
var results =
from x in xDoc.Elements("fs_response").Elements("results").Elements("result")
from m in x.Elements("metas").Elements("meta")
where m.Attribute("id").Value == "result_status"
select new
{
id = x.Attribute("id").Value,
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value,
};
foreach (var result in results.ToList())
{
Console.WriteLine("{0}: {1} = {2}", result.id, result.resultStatusId, result.resultStatus);
}
I have a XDocument called currentIndex like that:
<INDEX>
<SUBINDEX>
<!-- Many tag and infos -->
<SUBINDEX>
<ITEM>
<IDITEM>1</IDITEM>
<ITEM>
<ITEM>
<IDITEM>2</IDITEM>
<ITEM>
...
<ITEM>
<IDITEM>n</IDITEM>
<ITEM>
</INDEX>
I would recreate a new XDocument similar to above one:
<INDEX>
<SUBINDEX>
<!-- Many tag and infos -->
<SUBINDEX>
<ITEM>
<IDITEM>2</IDITEM>
<ITEM>
</INDEX>
I want to do this in C#, I have tried starting in this way:
public void ParseItems(XDocument items)
{
IEnumerable<XElement> items = from a in indexGenerale.Descendants(XName.Get("ITEM"))
// where a.Element("IDITEM").Equals("2")
select a;
foreach(var item in items) {
// do something
}
}
Now the problem: If where clause is commented, items contains n elements (one for each ITEM tag), but if I remove that comments items is empty. Why this behaviour. How I need to perform a search?
Use an explicit cast:
from a in indexGenerale.Descendants("ITEM")
where (string)a.Element("IDITEM") == "2"
a.Element("IDITEM") will return an XElement and it will never be equal to "2".Maybe you meant a.Element("IDITEM").Value.Equals("2"), that will also work but explicit cast is safer.It doesn't throw exception if the element wasn't found`,
I have the following XML:
<?xml version="1.0" ?>
<NewDataSet>
<Data>
<ElementDefinition>
<ID>1</ID>
<QUANTITY>0</QUANTITY>
</ElementDefinition>
<ElementDefinition>
<ID>2</ID>
<QUANTITY>1</QUANTITY>
</ElementDefinition>
</Data>
</NewDataSet>
I need to create an array which contains all ElementDefinitions which contain a QUANTITY element with a value other then 0.
I tried:
var f = XDocument.Load(path);
var xe = f.Root.Elements("QUANTITY").Where(x => x.Value != "0").ToArray();
But that doesn't seem to work. With the above XML the array should contain 1 item, but it stays 0.
After that I need to create a string for each ElementDefinition in the array, the string must contain the value of the corresponding ID element.
For that I tried:
foreach (string x in xe)
{
string ID = //not sure what to do here
}
You want something like this:
var ids = f.Root.Descendants("ElementDefinition")
.Where(x => x.Element("QUANTITY").Value != "0")
.Select(x => x.Element("ID").Value);
As you want the ID, it is not very helpful to select all QUANTITY nodes. Instead, select exactly what you specified in your question:
All ElementDefinitions (Descendants("ElementDefinition")), that have a QUANTITY with a Value other than 0 (Where(x => x.Element("QUANTITY").Value != "0"). From the resulting nodes, select the ID (Select(x => x.Element("ID").Value)).
Yo can replace with
var xe = f.Root.Elements("Data/ElementDefinition/QUANTITY").Where(x => x.Value != "0").ToArray();
I have variable XElement content =
"...
<item text="Name-1" id="1" segment="1" secret-id="Find-Me">
<item text="Name-1-1" id="1.1">
<item text="Name-1-1-1" id="1.1.1" />
<item text="Name-1-1-2" id="1.1.2" />
</item>
</item>
..."
I have also an item with id = 1.1.2
I need to find the first parent of this item which has attribute segment="1" and get its secret-id
How to do this with LINQ?
You mean the "closest ancestor"?
string secretId = element.Ancestors()
.Where(x => (string) x.Attribute("segment") == "1")
.Select(x => (string) x.Attribute("secret-id"))
.FirstOrDefault();
The result will be null if either:
There are no ancestors with an attribute segment="1"
The element with that attribute has no secret-id attribute.
Like this:
item.Ancestors().Select(p => p.Attribute("secret-id")).First(a => a != null)