I have a XML file, organized in a way similar to this:
<People>
<Person>
<Name>John</Name>
<SurName>Smith</SurName>
</Person>
<Person>
<Name>Jack</Name>
<SurName>Woodman</SurName>
</Person>
[...]
</People>
<Professions>
<Person>
<SurName>Smith</SurName>
<Profession>Blacksmith</Profession>
</Person>
<Person>
<SurName>Woodman</SurName>
<Profession>Lumberjack</Profession>
</Person>
[...]
</Professions>
Ok, here we go.
I am parsing this using Xml.Linq and I've got sth like this code:
foreach (XElement datatype in RepDoc.Element("People").Descendants("Persons"))
{
Name=datatype.Element("Name").Value;
SurName=datatype.Element("SurName").Value;
}
But, the question is if it is possible to open another foreach, inside another method, starting on the current datatype position without parsing from the beginning again.
As if the method inherit the XElement datatype, so I could start parsing from the same <Person>node.
It sounds stupid, but I can't put the original .xml because it is huge and has millions of information that would just confuse us more.
Thanks :)
Okay, so I believe you're looking for yield. This would often be used like this:
public IEnumerable<Person> GetPeople()
{
foreach (XElement datatype in RepDoc.Element("People").Descendants("Persons"))
{
var p = new Person
{
Name = datatype.Element("Name").Value,
SurName = datatype.Element("SurName").Value
};
yield return p;
}
}
and then you could use that method like this:
foreach (Person p in MyClass.GetPeople())
The benefit here is that you only read as much of the XML file as is necessary because if you break out of the outer-loop, everything stops.
Related
I've an XML file like below:
<Employees>
<Employee Id="ABC001">
<Name>Prasad 1</Name>
<Mobile>9986730630</Mobile>
<Address Type="Perminant">
<City>City1</City>
<Country>India</Country>
</Address>
<Address Type="Temporary">
<City>City2</City>
<Country>India</Country>
</Address>
</Employee>
Now I want get all Address Type's.
I tried like below using XPath and I'm getting exception.
var xPathString = #"//Employee/Address/#Type";
doc.XPathSelectElements(xPathString); // doc is XDocument.Load("xml file Path")
Exception: The XPath expression evaluated to unexpected type
System.Xml.Linq.XAttribute.
Is there any issue with my XPath?
Your XPath is fine (although you might want it to be more selective), but you have to adjust how you evaluate it...
XPathSelectElement(), as its name implies, should only be used to select elements.
XPathEvaluate() is more general and can be used for attributes. You can enumerate over the results, or grab the first:
var type = ((IEnumerable<object>)doc.XPathEvaluate("//Employee/Address/#Type"))
.OfType<XAttribute>()
.Single()
.Value;
Another option would be:
var addresses = doc.XPathSelectElements("//Employee/Address"));
foreach(var address in addresses) {
var addrType = address.Attribute("Type").Value;
}
Expanding on kjhughes's answer, I've tended to create an XElement class extension for this so that I can simply call element.XPathSelectAttribute(), which makes my calling code look a great deal clearner.
public static class XElementExtensions
{
public static XAttribute XPathSelectAttribute(this XElement element, string xPath)
{
return ((IEnumerable<object>)element.XPathEvaluate(xPath)).OfType<XAttribute>().First();
}
}
Using C#, I need to store all the data of the inner text of Email nodes into a list, and all the data of the inner text of Related nodes into a list for each person separately. For now I was only able to store only first "email" node and first "related" node into lists. I get this:
,when I should get this
.
How to get the right answer? This is my xml file:
<? xml version="1.0" encoding="utf-8"?>
<People>
<Person>
<Name>Toni</Name>
<Email>a#g.c</Email>
<Email>b#g.c</Email>
<Email>c#g.c</Email>
<Related>Friend1</Related>
<Related>Friend2</Related>
</Osoba>
<Osoba>
<Name>Deni</Name>
<Email>d#g.c</Email>
<Email>e#g.c</Email>
<Email>f#g.c</Email>
<Related>Friend3</Related>
<Related>Friend4</Related>
</Osoba>
</People>
I am assuming the following xml Instead of the one you posted since it is wrong.
<?xml version="1.0" encoding="utf-8"?>
<People>
<Person>
<Name>Toni</Name>
<Email>a#g.c</Email>
<Email>b#g.c</Email>
<Email>c#g.c</Email>
<Related>Friend1</Related>
<Related>Friend2</Related>
</Person>
<Person>
<Name>Deni</Name>
<Email>d#g.c</Email>
<Email>e#g.c</Email>
<Email>f#g.c</Email>
<Related>Friend3</Related>
<Related>Friend4</Related>
</Person>
</People>
I am using LINQ to XML.(Another way is to use XmlDocument)
String path = "Path of your xml file";
XDocument doc = XDocument.Load(path);
var nodes = doc.Descendants("Person");
foreach (XElement node in nodes)
{
var name = node.Element("Name").Value;
var emails = node.Elements("Email").Select(x => x.Value);
}
I've got an xml-file that looks like this for example:
<?xml version="1.0" encoding="UTF-8"?>
<Adresses>
<Message>
<Header>
<MessageID>96</MessageID>
<Timestamp>22.08.2014 10:25:01</Timestamp>
</Header>
<Body>
<Person SurName="Muster" Prename="Max">
<Adress Street="Street 1"/>
</Person>
<Person SurName="Muster" Prename="Max">
<Adress Street="Street 1"/>
</Person>
<Person SurName="Muster" Prename="Max">
<Adress Street="Street 1"/>
</Person>
</Body>
</Message>
</Adresses>
From this xml I only want the part inside the body-tags. I do the deserialization with the XmlSerializer and annotaions. So I have models that look like this
[XmlRoot("Body")]
public class BodyXml
{
public BodyXml()
{}
[XmlElement("Person")]
public Person[] Persons { get; set; }
}
Now my question is how can I get the XmlSerializer to serialize from the body-tag and not from the adresses-tag? Do I need another annotation somewhere in my models?
thanks and greets
Depending on other constraints, either consider writing a quick and dirty wrapper that would deserialize the whole XML (with BodyXml as it's member), or alternatively select only the relevant part of your xml, e.g.:
var serializer = new XmlSerializer(typeof(BodyXml));
var xDoc = XDocument.Parse(YOUR_XML_STRING);
using (var xmlReader = xDoc.Descendants("Body").Single().CreateReader())
{
var result = serializer.Deserialize(xmlReader);
}
EDIT: without any context I'd go with the latter.
use XmlIgnoreAttribute
You can Add Address property as below
[XmlIgnoreAttribute]
public AddressClass Adress{get;set;}
Here AddressClass may type of your Address property or some other class.
I'm trying to find an easy and slick way to do the following requirement.
I have a XML message with this arrangement:
<persons>
<person>
<firstName>Mike</firstName>
<middleName>K.</middleName>
<lastName>Kelly</lastName>
</person>
<person>
<firstName>Steve</firstName>
<lastName>David</lastName>
</person>
<person>
<firstName>Laura</firstName>
<middleName>X.</middleName>
<lastName>Xavier</lastName>
</person>
</persons>
I want to parse this XML using xPath expressions.
persons/person/firstName
persons/person/middleName
persons/person/lastName
My objective is store firstName, middleName and lastName tag values like this into a list of string objects like this:
firstNameList[0] = "Mike";
firstNameList[1] = "Steve";
firstNameList[2] = "Laura";
middleNameList[0] = "K.";
middleNameList[1] = null;
middleNameList[2] = "X.";
lastNameList[0] = "Kelly";
lastNameList[1] = "David";
lastNameList[2] = "Xavier";
In my C# code, I do this:
XmlNodeList firstNameNodeList = xmlDoc.SelectNodes("persons/person/firstName", nsmgr);
XmlNodeList middleNameNodeList = xmlDoc.SelectNodes("persons/person/middleName", nsmgr);
XmlNodeList lastNameNodeList = xmlDoc.SelectNodes("persons/person/lastName", nsmgr);
The problem with this code is that for middle name, I don't have it for 2nd person in my XML list. So the middleNameNodeList returns 2 values (K. and X.) but I wouldn't know whether the 1st or 2nd or 3rd person's middle name is missing.
I was hoping that SelectNodes() API would provide an iteration or index ID as which repeating element has a given value.
Please suggest me an easiest way to achieve what I needed? Thanks so much for your help, JK
How about this?
foreach (Node person in xmlDoc.SelectNodes("persons/person", nsmgr))
{
firstNameNodeList.Add(person.SelectSingleNode("firstName", nsmgr));
middleNameNodeList.Add(person.SelectSingleNode("middleName", nsmgr));
lastNameNodeList.Add(person.SelectSingleNode("lastName", nsmgr));
}
Intead of getting a list of names, try getting a list of persons, then iterate the list and get their names.
You just have to iterate over persons/person and handle each individually - this would work:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(#"test.xml");
var persons = xmlDoc.SelectNodes("persons/person");
foreach (XmlNode person in persons)
{
string firstName = person.SelectSingleNode("firstName").InnerText;
string middleName = (person.SelectSingleNode("middleName") != null)
? person.SelectSingleNode("middleName").InnerText
: null;
string lastName = person.SelectSingleNode("lastName").InnerText;
}
Try
<persons>
<person>
<firstName>Mike</firstName>
<middleName>K.</middleName>
<lastName>Kelly</lastName>
</person>
<person>
<firstName>Steve</firstName>
<middleName />
<lastName>David</lastName>
</person>
<person>
<firstName>Laura</firstName>
<middleName>X.</middleName>
<lastName>Xavier</lastName>
</person>
</persons>
<person>
<firstName>Steve</firstName>
<middleName />
<lastName>David</lastName>
</person>
this should return "K","","X" for InnnerText
Is there a way to search an XDocument without knowing the namespace? I have a process that logs all SOAP requests and encrypts the sensitive data. I want to find any elements based on name. Something like, give me all elements where the name is CreditCard. I don't care what the namespace is.
My problem seems to be with LINQ and requiring a xml namespace.
I have other processes that retrieve values from XML, but I know the namespace for these other process.
XDocument xDocument = XDocument.Load(#"C:\temp\Packet.xml");
XNamespace xNamespace = "http://CompanyName.AppName.Service.Contracts";
var elements = xDocument.Root
.DescendantsAndSelf()
.Elements()
.Where(d => d.Name == xNamespace + "CreditCardNumber");
I really want to have the ability to search xml without knowing about namespaces, something like this:
XDocument xDocument = XDocument.Load(#"C:\temp\Packet.xml");
var elements = xDocument.Root
.DescendantsAndSelf()
.Elements()
.Where(d => d.Name == "CreditCardNumber")
This will not work because I don't know the namespace beforehand at compile time.
How can this be done?
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Request xmlns="http://CompanyName.AppName.Service.ContractA">
<Person>
<CreditCardNumber>83838</CreditCardNumber>
<FirstName>Tom</FirstName>
<LastName>Jackson</LastName>
</Person>
<Person>
<CreditCardNumber>789875</CreditCardNumber>
<FirstName>Chris</FirstName>
<LastName>Smith</LastName>
</Person>
...
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Request xmlns="http://CompanyName.AppName.Service.ContractsB">
<Transaction>
<CreditCardNumber>83838</CreditCardNumber>
<TransactionID>64588</FirstName>
</Transaction>
...
As Adam precises in the comment, XName are convertible to a string, but that string requires the namespace when there is one. That's why the comparison of .Name to a string fails, or why you can't pass "Person" as a parameter to the XLinq Method to filter on their name.
XName consists of a prefix (the Namespace) and a LocalName. The local name is what you want to query on if you are ignoring namespaces.
Thank you Adam :)
You can't put the Name of the node as a parameter of the .Descendants() method, but you can query that way :
var doc= XElement.Parse(
#"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Request xmlns=""http://CompanyName.AppName.Service.ContractA"">
<Person>
<CreditCardNumber>83838</CreditCardNumber>
<FirstName>Tom</FirstName>
<LastName>Jackson</LastName>
</Person>
<Person>
<CreditCardNumber>789875</CreditCardNumber>
<FirstName>Chris</FirstName>
<LastName>Smith</LastName>
</Person>
</Request>
</s:Body>
</s:Envelope>");
EDIT : bad copy/past from my test :)
var persons = from p in doc.Descendants()
where p.Name.LocalName == "Person"
select p;
foreach (var p in persons)
{
Console.WriteLine(p);
}
That works for me...
You could take the namespace from the root-element:
XDocument xDocument = XDocument.Load(#"C:\temp\Packet.xml");
var ns = xDocument.Root.Name.Namespace;
Now you can get all desired elements easily using the plus-operator:
root.Elements(ns + "CreditCardNumber")
I think I found what I was looking for. You can see in the following code I do the evaluation Element.Name.LocalName == "CreditCardNumber". This seemed to work in my tests. I'm not sure if it's a best practice, but I'm going to use it.
XDocument xDocument = XDocument.Load(#"C:\temp\Packet.xml");
var elements = xDocument.Root.DescendantsAndSelf().Elements().Where(d => d.Name.LocalName == "CreditCardNumber");
Now I have elements where I can encrypt the values.
If anyone has a better solution, please provide it. Thanks.
There's a couple answers with extension methods that have been deleted. Not sure why. Here's my version that works for my needs.
public static class XElementExtensions
{
public static XElement ElementByLocalName(this XElement element, string localName)
{
return element.Descendants().FirstOrDefault(e => e.Name.LocalName == localName && !e.IsEmpty);
}
}
The IsEmpty is to filter out nodes with x:nil="true"
There may be additional subtleties - so use with caution.
If your XML documents always defines the namespace in the same node (Request node in the two examples given), you can determine it by making a query and seeing what namespace the result has:
XDocument xDoc = XDocument.Load("filename.xml");
//Initial query to get namespace:
var reqNodes = from el in xDoc.Root.Descendants()
where el.Name.LocalName == "Request"
select el;
foreach(var reqNode in reqNodes)
{
XNamespace xns = reqNode.Name.Namespace;
//Queries making use of namespace:
var person = from el in reqNode.Elements(xns + "Person")
select el;
}
I a suffering from a major case of "I know that is the solution, but I am disappointed that that is the solution"... I recently wrote a query like the one below (which I will shortly replace, but it has educational value):
var result = xdoc.Descendants("{urn:schemas-microsoft-com:rowset}data")
.FirstOrDefault()?
.Descendants("{#RowsetSchema}row");
If I remove the namespaces from the XML, I can write the same query like this:
var result = xdoc.Descendants("data")
.FirstOrDefault()?
.Descendants("row");
I plan to write my own extension methods that should allow me to leave the namespaces alone and search for nodes like this:
var result = xdoc.Descendants("rs:data")
.FirstOrDefault()?
.Descendants("z:row");
//'rs:' {refers to urn:schemas-microsoft-com:rowset}
//'z:' {refers to xmlns:z=#RowsetSchema}
My comments just below the code point to how I would like to hide the ugliness of the solution in an Extension Methods library. Again, I'm aware of the solutions posted earlier - but I wish the API itself handled this more fluently. (See what I did there?)
Just use the Descendents method:
XDocument doc = XDocument.Load(filename);
String[] creditCards = (from creditCardNode in doc.Root.Descendents("CreditCardNumber")
select creditCardNode.Value).ToArray<string>();