Extracting XML inner node elements in C# - c#

I have an XML document that looks like this:
<root>
<key>
<id>v1</id>
<val>v2</val>
<iv>v3</iv>
</key>
</root>
How do I extract the v2 values and v3 values of a key node using its v1 value in C#?

Use Linq.
var myXml = XDocument.Parse("<root>
<key>
<id>v1</id>
<val>v2</val>
<iv>v3</iv>
</key>
</root>").Root.Elements("key")
.FirstOrDefault(x=> x.Element("id").Value == value);
if (myXml != null)
{
var myObject = new
{
id = myXml.Element("id").Value,
val = myXml.Element("val").Value,
iv = myXml.Element("iv").Value
});
}
Of course, you need to check for missing elements, etc, if required.

Use xpath:
/root/key[id='v1']/val
/root/key[id='v1']/iv
so something like
myXmlDoc.SelectSingleNode("/root/key[id='v1']/val").Value
myXmlDoc.SelectSingleNode("/root/key[id='v1']/iv").Value

I like using LINQ to XML for processing XML:
var xml = XElement.Parse(#"<root>
<key>
<id>v1</id>
<val>v2</val>
<iv>v3</iv>
</key>
</root>");
var key = xml.Elements("key").First(x => x.Element("id").Value == "v1");
Console.WriteLine("val: " + key.Element("val").Value);
Console.WriteLine(" iv: " + key.Element("iv").Value);
I have ignored all error checking for brevity.
For example First() would throw an exception if the element is not found. You might want to use FirstOrDefault() and check for null if you are expecting that or handle edge cases a bit more gracefully.
Same goes for Element() calls. They might return null so calling .Value could result in a System.NullReferenceException. To avoid clutter I usually use extension methods to do these checks:
static class XElementUtilities
{
public static string GetValue(this XElement xml, string name)
{
var element = xml.Element(name);
return element == null ? null : element.Value;
}
public static bool ValueEqual(this XElement xml, string name, string value)
{
var element = xml.Element(name);
return element != null && value != null && element.Value == value;
}
}

Related

How to read and update value of nodes in an XElement

I have an xml document (actually a config file) loaded into an XDocument object, that contains an element like this:
<ScheduledTasks>
<add key="RelativePath" value="..\Scheduler\Tasks"/>
<add key="SearchPauseInSeconds" value="10"/>
<add key="MatrixAccount" value="95755UE93ZEb3fRZUSZ753K9FRS3O9DaDrJxtdiiZnm"/>
<add key="MatrixPassword" value="95755UE93ZEb3fRZUSZ753K9FRS3O9DaDgKrn2e71"/>
</ScheduledTasks>
How can I best retrieve (and update) the value of RelativePath, SeachPauseInseconds etc? They aren't XElements.
TIA.
var attribute =
xDocument.Root.Elements()
.Single(element => element.Attribute("key").Value == "RelativePath")
.Attribute("value");
string oldValue = attribute.Value; // to retrieve
attribute.Value = newValue; // to update
They are attributes. Use XElement.Attribute("attributeName") to get them.
var items = (from i in scheduledTasksElement.Elements("add")
select new
{
KeyAttribute = i.Attribute("key"),
Key = (string)i.Attribute("key"),
ValueAttribute = i.Attribute("value"),
Value = (string)i.Attribute("value")
}).ToList();
As you can see, you can easily cast XAttribute to other types like you can do with XElement.
You can also update the value:
items[0].KeyAttribute.Value = "newValue";
You can, for example, create an extension method that will do it
public static void FindAndReplace(this XDocument doc, string key, string newValue)
{
var elem = doc.Descendants("add")
.FirstOrDefault(d => d.Attribute("key").Value == key);
if (elem != null)
elem.Attribute("value").Value = newValue;
}
and use it like
doc.FindAndReplace("RelativePath", "..\Tasks");

Parsing XML in Compact Framework 3.5

I have a following XML that I need to parse, but I am having some problems. First, the amount of tags that I have inside Class tag is not known, and they not distinct (so I can't specify them by their name).
XML example:
<Class value="1B2">
<FirstName>Bob</FirstName>
<FirstName>Jim</FirstName>
<FirstName>Alice</FirstName>
<FirstName>Jessica</FirstName>
//(More similar lines, number is not known)
</Class>
<Class value="2C4">
<FirstName>Bob</FirstName>
<FirstName>Jim</FirstName>
<FirstName>Alice</FirstName>
<FirstName>Jessica</FirstName>
//(More similar lines, number is not known)
</Class>
Now this is my code so far to parse it:
Define xmlReader and XElement
XmlReader xmlReader = XmlReader.Create(modFunctions.InFName);
XElement xElem = new XElement("FirstName");
Then I am making connection to SQL Server CE database and this is my main loop that reads xmlfile:
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element &&
(xmlReader.LocalName == "Class" || xmlReader.LocalName == "FirstName") &&
xmlReader.IsStartElement() == true)
{
// Find Class tag
if (xmlReader.LocalName == "Class")
{
xElem = (XElement)XNode.ReadFrom(xmlReader);
// Get 1B2 value
HRName = xElem.FirstAttribute.Value;
// Tried to read each node in xElement to get FirstName values.. this didn't work
for ( (XNode e in (XNode)xElem)
{
string newString = ((string)xElem.Element("FirstName"));
}
}
// Also tried this before, but it is skips it since FirstName tags are inside Class tag.
if (xmlReader.LocalName == "FirstName")
{
xElem = (XElement)XNode.ReadFrom(xmlReader);
record.SetValue(0, xElem.Value);
record.SetValue(1, HRName);
rs.Insert(record);
}
}
}
And in my table (to where I am trying to write this) consists of two columns (FirstName and Class)
As mentioned in the comments, Linq to Xml is your best bet: http://msdn.microsoft.com/en-us/library/system.xml.linq(v=vs.90).aspx
Your parse would look something like:
string xml = "<root><Class value='1B2'><FirstName>Bob</FirstName><FirstName>Jim</FirstName><FirstName>Alice</FirstName><FirstName>Jessica</FirstName></Class>" +
"<Class value='2C4'><FirstName>Bob</FirstName><FirstName>Jim</FirstName><FirstName>Alice</FirstName><FirstName>Jessica</FirstName></Class></root>";
XDocument doc = XDocument.Parse(xml);
foreach(var node in doc.Descendants("Class"))
{
var cls = node.Attribute("value").Value;
string[] firstNames = node.Descendants("FirstName").Select(o => o.Value).ToArray();
}

How to write xpath for this XDocument?

<tags>
<data mode="add" name="ttt" oldindex="-1" index="-1" oldnumber="1" number="1" type="VAR_INT" value="72" />
<data mode="delete" name="test3d" oldindex="-1" index="-1" oldnumber="1" number="1" type="VAR_INT" value="72" />
</tags>
I want to check whether "mode" is present in xml or not
xdDiffData.XPathSelectElement("//tags[#mode='add']") != null && xdDiffData.XPathSelectElement("//tags[#mode='delete']") != null
This always gives false..how to do this... ?
If you want to make sure that mode attribute is present in every data element, then you should better iterate all the data elements to look for the mode attribute this way:
XDocument doc = XDocument.Load("XmlFile.xml");
var nodes = doc.Descendants("data");
foreach (var node in nodes)
{
var attrMode = node.Attribute("mode");
if (attrMode == null)
{
// mode attribute is not available for this data element
}
}
Using Linq:
if (nodes.Where(c => c.Attribute("mode") == null).Count() == 0)
{
var result = nodes.All(e =>
e.Attribute("mode").Value.Equals("add") ||
e.Attribute("mode").Value.Equals("delete"));
}
else
{
// 'mode' attribute is missing for one or more 'data' element(s)
}
If result equals to true, then it means all the data elements have mode attribute either set to value "add" or "delete".
You are missing the 'data' element. Try
xdDiffData.XPathSelectElement("//tags/data[#mode='add']") != null && xdDiffData.XPathSelectElement("//tags/data[#mode='delete']") != null
xdDiffData.XPathSelectElement("/tags/data[#mode='add']") != null
I want to check whether "mode" is present in xml or not
Use:
//#mode
if this XPath expression selects a node, this means that an attribute named mode is present in the XML document.
or you could use:
boolean(//#mode)
and this produces a boolean value -- true() or false()

Parsing xml. return one object not collection

I have a xml file:
<Result>Ok</Result>
<Error></Error>
<Remark></Remark>
<Data>
<Movies>
<Movie ID='2'>
<Name><![CDATA[TestName]]></Name>
<Duration Duration='170'>2h 50min</Duration>
<Properties>
<Property Name='11'><![CDATA[1111110]]></Property>
</Properties>
<Rental from_date='' to_date=''>
<SessionCount></SessionCount>
<PU_NUMBER></PU_NUMBER>
</Rental>
</Movie>
</Movies>
</Data>
</XML>
Code for pasring xml file:
var results = from element in XDocument.Parse(queryResponse).Descendants("Movie")
select new BaseEvent
{
OID = (int)element.Attribute("ID"),
Subject = (string)element.Element("Name"),
Duration = (int)element.Element("Duration").Attribute("Duration")
};
The problem in that Descedants retruns IEumerable<BaseEvent> but I want that will be BaseEvent. How can I do this?
Just use First(), Last(), Single(), FirstOrDefault() etc.
Personally I'd do that initially, rather than doing it all in a query:
var element = XDocument.Parse(queryResponse)
.Descendants("Movie")
.FirstOrDefault();
if (element == null)
{
// Handle the case of no movies
}
else
{
var baseEvent = new BaseEvent
{
OID = (int) element.Attribute("ID"),
Subject = (string) element.Element("Name"),
Duration = (int) element.Element("Duration")
.Attribute("Duration")
};
// Use baseEvent
}
Add a .First() to get the first element:
from element in XDocument.Parse(queryResponse).Descendants("Movie")
select new BaseEvent
{
OID = (int)element.Attribute("ID"),
Subject = (string)element.Element("Name"),
Duration = (int)element.Element("Duration").Attribute("Duration")
}.First();
You can alternatively use FirstOrDefault() (in case there are no such nodes), Last() or Single().

c# linq to xml query help

Lets assume a xml file named data.xml with following content:
<root>
<record>
<id>1</id>
<name>test 1</name>
<resume>this is the resume</resume>
<specs>these are the specs</specs>
</record>
<record>
<id>2</id>
<name>test 2</name>
<resume>this is the resume 2</resume>
</record>
<record>
<id>3</id>
<name>test 3</name>
<specs>these are the specs 3</specs>
</record>
</root>
I need to search all records where any of these fields (id, name, resume or specs) contains a given value. I've created this code
XDocument DOC = XDocument.Load("data.xml");
IEnumerable<ProductRecord> results = from obj in DOC.Descendants("record")
where
obj.Element("id").Value.Contains(valueToSearch) ||
obj.Element("name").Value.Contains(valueToSearch) ||
obj.Element("resume").Value.Contains(valueToSearch) ||
obj.Element("specs").Value.Contains(valueToSearch)
select new ProductRecord {
ID = obj.Element("id").Value,
Name = obj.Element("name").Value,
Resume = obj.Element("resume").Value,
Specs = obj.Element("specs").Value
};
This code throws an error of NullReference since not all records have all fields.
How can i test if current record has a given element before i define a condition to apply? Ex. Record[#ID=3] has no resume.
Thanks in advance
You can write an extension method which is like bellow:
public static class XMLExtension
{
public static string GetValue(this XElement input)
{
if (input != null)
return input.Value;
return null;
}
public static bool XMLContains(this string input, string value)
{
if (string.IsNullOrEmpty(input))
return false;
return input.Contains(value);
}
}
and use it as below:
IEnumerable<ProductRecord> results = from obj in DOC.Descendants("record")
where
obj.Element("id").GetValue().XMLContains(valueToSearch) || ...
You are getting a NullReferenceException because you are trying to access the value of some nodes that don't exist for each record like specs. You need to check whether obj.Element("specs") != null before calling .Value on it.
As an alternative you could use XPath:
var doc = XDocument.Load("test.xml");
var records = doc.XPathSelectElements("//record[contains(id, '2') or contains(name, 'test') or contains(resume, 'res') or contains(specs, 'spe')]");
First I'm amazed that it's not crashing because you're not using the Namespace. Maybe c#4.0 has bypassed this?
Anyway try
obj.Descendants("id").Any() ? root.Element("id").Value : null
That is:
select new ProductRecord {
ID = obj.Descendants("id").Any() ? root.Element("id").Value : null,
Name = obj.Descendants("name").Any() ? root.Element("name").Value : null,
Resume = obj.Descendants("resume").Any() ? root.Element("resume").Value : null
Specs = obj.Descendants("specs").Any() ? root.Element("specs").Value : null
};

Categories