Retrieving a Deeply Nested Value in an XML File - c#

I'm trying to read a property in an XML file using C# and LINQ XML and I can't retrieve a value that is deeply nested in the tree. The value I'm trying to get is the contents of <Value> near <DisplayName>Add Your Comments</DisplayName>. Every <OrderProduct id=???> may have its own comments.
I can read other properties in the XML file using LINQ, but I'm confused how to go about reading something so deeply nested.
Thanks.
<?xml version="1.0" encoding="utf-16"?>
<OrderXml>
<Order>
<OrderProducts>
<OrderProduct id="1">
.
.
.
</OrderProduct>
<OrderProduct id="2">
<PropertyValues>
<PropertyValue>
<Property id="10786">
<DisplayName>Base</DisplayName>
</Property>
<Value />
</PropertyValue>
<PropertyValue>
<Property id="10846">
<DisplayName>Add Your Comments</DisplayName>
</Property>
<Value>this is a comment</Value>
</PropertyValue>
</PropertyValues>
</OrderProduct>
</OrderProducts>
</Order>
</OrderXml>
This is the code I have so far. I can retrieve the "Add Your Comments" part, but I'm stuck in how to get the part that follows it.
string productOrderID = "";
string productName = "";
XElement xelement;
xelement = XElement.Load (#"D:\Order.xml");
IEnumerable<XElement> Products = xelement.Descendants ("OrderProduct");
foreach (var order in Products)
{
productOrderID = order.Attribute ("id").Value;
productName = order.Element ("Product").Element ("Name").Value;
Console.WriteLine ("productOrderID: {0}", productOrderID);
Console.WriteLine ("productName: {0}", productName);
Console.WriteLine ("");
IEnumerable<XElement> PropertyValues = xelement.Descendants ("PropertyValues").Elements ("PropertyValue");
foreach (var propValue in PropertyValues.Elements ("Property").Elements ("DisplayName"))
{
Console.WriteLine ("Property ID: {0}", propValue.Value);
if (propValue.Value == "Add Your Comments")
{
Console.WriteLine ("---");
}
}
}

You can use Descendants to search for nodes in document no matter where they are:
string name = "Add Your Comments";
var value = xdoc
.Descendants("PropertyValue")
.Where(pv => (string)pv.Element("Property").Element("DisplayName") == name)
.Select(pv => (string)pv.Element("Value"))
.FirstOrDefault();
Output:
this is a comment

Related

I am having an xml inside xml and want to test a condition is met inside the inner xml. C# solution required

<?xml version="1.0"?>
<TextType IsKey="false" Name="XMLReport"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Providers
xmlns="Reporting"/>
<Sales
xmlns="Reporting"/>
<Value
xmlns="Reporting">
<?xml version="1.0" encoding="utf-8"?>
<TestReport>
<StudyUid>
<![CDATA[123]]>
</StudyUid>
<Modality>
<![CDATA[XYZ]]>
</Modality>
<StudyDate format="DICOM">123456</StudyDate>
<StudyTime format="DICOM">6789</StudyTime>
<AccessionNumber>
<![CDATA[123]]>
</AccessionNumber>
<StudyDescription>
<![CDATA[abc def]]>
</StudyDescription>
<OperatorName format="xyz">
<![CDATA[abc]]>
</OperatorName>
<PhysicianReadingStudy format="xyz">
<![CDATA[^^^^]]>
</PhysicianReadingStudy>
<InstitutionName>
<![CDATA[xyz]]>
</InstitutionName>
<HospitalName>
<![CDATA[Hospital Name]]>
</HospitalName>
<ReportSet>
<MyReport ID="1">
<ReportStatus>
<![CDATA[Done]]>
</ReportStatus>
</MyReport>
<MyReport ID="2">
<ReportStatus>
<![CDATA[Done]]>
</ReportStatus>
</MyReport>
<MyReport ID="3">
<ReportStatus>
<![CDATA[Initial]]>
</ReportStatus>
</MyReport>
</ReportSet>
<ReportImageSet />
<FetusSet />
</TestReport>
</Value>
<WhoSetMe xmlns="Reporting">NotSpecified
</WhoSetMe>
</TextType>
I want to parse the xml above in C# and check whether "ReportStatus" is "Done" for all the ReportStatus under MyReport/ReportSet. One more twist here is the xml contains one more xml starts at "Value" tag as in above example.It may contatin many ReportStatus tag under ReportSet tag. Can someone please help me?
// Can you try this? I tried to do it with LINQ to XML.
// I assume you have multiple <TestReport /> elements in <Value /> tag
// and var xelement is your xml variable
// First we get all TestReport elemnts
IEnumerable<XElement> allReports =
from el in xelement.Elements("TextType/Value/TestReport")
select el;
// From allReports we get all MyReport elemnts
IEnumerable<XElement> allMyReports =
from el in allReports.Elements("ReportSet/MyReport")
select el;
// From allReports we also get all MyReport elemnts with element ReportStatus value equals "Done"
IEnumerable<XElement> allDoneMyReports =
from el in allMyReports
where (string)el.Element("ReportStatus") == "Done"
select el;
// Now we compare allMyReport with allDoneMyReports
if (allMyReports.Count() == allDoneMyReports.Count())
{
//DO Somehing
}
Your XML document is invalid. You need to fix it before trying to parse it. The issue is that a document can only have one top-level element; you have 2 <TextType> and <Providers>.
Most of your elements are the namespace Reporting. You need to use it when referencing the element.
XNamespace ns = "Reporting";
var value = doc.Element("Value" + ns);
Update
Just use the namespace for each element
XNamespace ns = "Reporting";
var value = xelement.Elements("Value" + ns);
Another Update
The XML document is considered invalid because it has multiple XML declarations; there is no way to disable this. I suggest you pre-process the document to remove the extra declarations. Here's an example (https://dotnetfiddle.net/UnuAF6)
var xml = "<?xml version='1.0'?><a> <?xml version='1.0'?><b id='b' /></a>";
var doc = XDocument.Parse(xml.Replace(" <?xml version='1.0'?", " "));
var bs = doc.Descendants("b");
Console.WriteLine("{0} 'b' elements", bs.Count());

Search through XML and grab another Node

<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Message>
<MessageID>1</MessageID>
<Product>
<SKU>33333-01</SKU>
</Product>
</Message>
</Envelope>
I've tried googling but whether I'm just not providing the correct search criteria I don't know.
I want to be able to search the XML file based on the MessageID and then grab the SKU.
I then want to search another XML file based on the SKU and remove that message completely.
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Message>
<MessageID>1</MessageID>
<Inventory>
<SKU>33333-01</SKU>
<Quantity>1</Quantity>
</Inventory>
</Message>
<Message>
<MessageID>2</MessageID>
<Inventory>
<SKU>22222-01</SKU>
<Quantity>1</Quantity>
</Inventory>
</Message>
</Envelope>
Meaning the XML above becomes:
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Message>
<MessageID>2</MessageID>
<Inventory>
<SKU>22222-01</SKU>
<Quantity>1</Quantity>
</Inventory>
</Message>
</Envelope>
To confirm I cannot confirm that the MessageID will be the same over different XML files.
Thanks in advance for any help.
My questions:
How do I search through XML files?
How do I then grab another Nodes details
Can I remove a complete from an XML file based on a search?
You can use XmlDocument to load your XML document. Then, you can use XPath for searching any nodes.
XmlDocument document = new XmlDocument();
document.Load("C:\fileOnTheDisk.xml");
// or
document.LoadXml("<a>someXmlString</a>");
// Returns single element or null if not found
var singleNode = document.SelectSingleNode("Envelope/Message[MessageID = '1']");
// Returns a NodeList
var nodesList = document.SelectNodes("Envelope/Message[MessageID = '1']");
Read more about XPath at w3schools.com.
Here is a good XPath Tester.
For example, you can use the following XPath to find nodes in your document by ID:
XmlDocument document = new XmlDocument();
document.Load("C:\doc.xml");
var node = document.SelectSingleNode("Envelope/Message[MessageID = '1']");
var sku = node.SelectSingleNode("Inventory/SKU").InnerText;
Console.WriteLine("{0} node has SKU = {1}", 1, sku);
Or you can output all SKUs:
foreach (XmlNode node in document.SelectNodes("Envelope/Message"))
{
Console.WriteLine("{0} node has SKU = {1}",
node.SelectSingleNode("MessageID").InnerText,
node.SelectSingleNode("Inventory/SKU").InnerText);
}
It will produce:
1 node has SKU = 33333-01
2 node has SKU = 22222-01
Note that there are possible NullReferenceExceptions if nodes are not present.
You can simply remove it using RemoveChild() method of its parent.
XmlDocument document = new XmlDocument();
document.Load("C:\doc.xml");
var node = document.SelectSingleNode("Envelope/Message[MessageID = '1']");
node.ParentNode.RemoveChild(node);
document.Save("C:\docNew.xml"); // will be without Message 1
You can use Linq to XML to do this:
var doc= XDocument.Load("input.xml");//path of your xml file in which you want to search based on message id.
var searchNode= doc.Descendants("MessageID").FirstOrDefault(d => d.Value == "1");// It will search message node where its value is 1 and get first of it
if(searchNode!=null)
{
var SKU=searchNode.Parent.Descendants("SKU").FirstOrDefault();
if(SKU!=null)
{
var searchDoc=XDocument.Load("search.xml");//path of xml file where you want to search based on SKU value.
var nodes =searchDoc.Descendants("SKU").Where(d=>d.Value==SKU.Value).Select(d=>d.Parent.Parent).ToList();
nodes.ForEach(node=>node.Remove());
searchDoc.Save("output.xml");//path of output file
}
}
I'd recommend you did this using LINQ to XML - it's much nicer to work with than the old XmlDocument API.
For all the examples, you can parse your XML string xml to an XDocument like so:
var doc = XDocument.Parse(xml);
1. How do I search through XML files?
You can get the SKU for a specific message ID by querying your document:
var sku = (string)doc.Descendants("Message")
.Where(e => (int)e.Element("MessageID") == 1)
.SelectMany(e => e.Descendants("SKU"))
.Single();
2. How do I then grab another Nodes details?
You can get the Message element with a specified SKU using a another query:
var message = doc.Descendants("SKU")
.Where(sku => (string)sku == "33333-01")
.SelectMany(e => e.Ancestors("Message"))
.Single();
3. Can I remove a complete element from an XML file based on a search?
Using your result from step 2, you can simple call Remove:
message.Remove();
Alternatively, you can combine the query from step 2 and simply execute a command to remove any messages that have a specific SKU:
doc.Descendants("SKU")
.Where(sku => (string)sku == "33333-01")
.SelectMany(e => e.Ancestors("Message"))
.Remove();
I tried to answer all your questions:
using System.Xml.XPath;
using System.Xml.Linq;
XDocument xdoc1 = XDocument.Load("xml1.xml");
XDocument xdoc2 = XDocument.Load("xml2.xml");
string sku = String.Empty;
string searchedID = "2";
//1.searching through an xml file based on path
foreach (XElement message in xdoc1.XPathSelectElements("Envelope/Message"))
{
if (message.Element("MessageID").Value.Equals(searchedID))
{
//2.grabbing another node's details
sku = message.XPathSelectElement("Inventory/SKU").Value;
}
}
foreach (XElement message in xdoc2.XPathSelectElements("Envelope/Message"))
{
if (message.XPathSelectElement("Inventory/SKU") != null && message.XPathSelectElement("Inventory/SKU").Value.Equals(sku))
{
//removing a node
message.Remove();
}
}
xdoc2.Save("xml2_del.xml");
}

How to get value of some object in XML string in C#?

I have this XML String :
<RESPONSE>
<SINGLE>
<KEY name="sitename">
<VALUE>Stackoverflow</VALUE>
</KEY>
<KEY name="username">
<VALUE>this value</VALUE>
</KEY>
</SINGLE>
</RESPONSE>
How to get value from Key that name "username" ? I want to get value of "this value" in my code. I try deserialized and any other code but it doesnt work. Please help me, thanks :D
Edit:
I tried using this code :
XDocument doc = XDocument.Load( "myXML.xml" );
var keys = doc.Descendants( "KEY" );
foreach ( var VALUE in keys )
{
Console.WriteLine( VALUE.Value );
}
But how did I get the Value only from KEY that named "Username" ? Thanks :))
You can probably use an xpath to do this. The following is an example XPath that will provide a node with name matching "sitename":
//KEY[#name="sitename"]
You can modify this slightly to find all nodes with a "name" attribute or to just find specific names. For more examples of how to use XPath see the MSDN site for XPath. The following is a snippet of C# code that shows you how to use this XPath (again, you can generalize for whatever XPath you need):
const string example_xml = "<RESPONSE> <SINGLE> <KEY name=\"sitename\"> <VALUE>Stackoverflow</VALUE> </KEY> <KEY name=\"username\"> <VALUE>this value</VALUE> </KEY> </SINGLE> </RESPONSE>";
// load
XmlDocument doc = new XmlDocument();
doc.LoadXml(example_xml);
// Query single or multiple nodes using the XPath, do what you want with this node!
var desiredNode = doc.SelectSingleNode("//KEY[#name=\"sitename\"]");
Best of luck!
For completeness here is a System.Xml.Linq version, with the foreachs and where's being System.Linq for good measure. This basically the questioner's attempt, with a where to filter according to attribute.
const string example_xml = "<RESPONSE> <SINGLE> <KEY name=\"sitename\"> <VALUE>Stackoverflow</VALUE> </KEY> <KEY name=\"username\"> <VALUE>this value</VALUE> </KEY> </SINGLE> </RESPONSE>";
XDocument doc = XDocument.Parse(example_xml);
var keys = doc.Descendants("KEY");
var userKeys = keys.Where(item => item.Attribute("name").Value == "username").ToList();
userKeys.ForEach(item => Console.WriteLine(item.Value));
Lets consider your xml document as XYZ.xml, then you may try below code if you are using C#, below is the example only
XmlDocument Doc = new XmlDocument();
Doc.Load(Server.MapPath(".../xyz.xml"));
XmlNodeList itemList = Doc.DocumentElement.SelectNodes("KEY");
foreach (XmlNode currNode in itemList)
{
string name = string.Empty;
XmlNode item = currNode.SelectSingleNode("KEY");
if(currNode["name"].InnerText == "username")//if you are aware of key name, use this condition
{
name = item.Attributes["name"].Value.ToString(); // or currNode["name"].InnerText;
}
}

Check if Xml Element exists before/during a foreach loop

I started learning C# this month and I'm creating a program to parse XML file and get some data. The .xml file is:
<Info>
<Symbols>
<Symbol>
<Name>Name</Name>
<Type>INT</Type>
</Symbol>
<Symbol>
<Name>Name</Name>
<Type>INT</Type>
<Properties>
<Property>
<Name>TAG</Name>
</Property>
</Properties>
</Symbol>
</Symbols>
</Info>
My code below, gets values from elements "Name" and "Type", from "Symbol". But I need to check if the element "Properties" exists in each "Symbol", because, as you can see, there will be some (like the first "Symbol") without the "Properties" element.
If it exists, I will get the value from , in this case: "TAG".
Is there a simple way to make the foreach try to get it only if it exists?!
var symbols = from symbol in RepDoc.Element("Info").Element("Symbols").Descendants("Symbol")
select new
{
VarName = symbol.Element("Name").Value,
VarType = symbol.Element("Type").Value,
};
foreach (var symbol in symbols)
{
Console.WriteLine("" symbol.VarName + "\t" + symbol.VarType);
}
Thank you in advance ^^
var res = XDocument.Load(fname)
.Descendants("Symbol")
.Select(x => new
{
Name = (string)x.Element("Name"),
Type = (string)x.Element("Type"),
Props = x.Descendants("Property")
.Select(p => (string)p.Element("Name"))
.ToList()
})
.ToList();
Props will have zero or more elements depending on the Properties tag.

Filter XDocument more efficiently

I would like to filter with high performance XML elements from an XML document.
Take for instance this XML file with contacts:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="asistentes.xslt"?>
<contactlist evento="Cena Navidad 2010" empresa="company">
<contact type="1" id="1">
<name>Name1</name>
<email>xxxx#zzzz.es</email>
<confirmado>SI</confirmado>
</contact>
<contact type="1" id="2">
<name>Name2</name>
<email>xxxxxxxxx#zzzze.es</email>
<confirmado>Sin confirmar</confirmado>
</contact>
</contaclist>
My current code to filter from this XML document:
using System;
using System.Xml.Linq;
class Test
{
static void Main()
{
string xml = #" the xml above";
XDocument doc = XDocument.Parse(xml);
foreach (XElement element in doc.Descendants("contact")) {
Console.WriteLine(element);
var id = element.Attribute("id").Value;
var valor = element.Descendants("confirmado").ToList()[0].Value;
var email = element.Descendants("email").ToList()[0].Value;
var name = element.Descendants("name").ToList()[0].Value;
if (valor.ToString() == "SI") { }
}
}
}
What would be the best way to optimize this code to filter on <confirmado> element content?
var doc = XDocument.Parse(xml);
var query = from contact in doc.Root.Elements("contact")
let confirmado = (string)contact.Element("confirmado")
where confirmado == "SI"
select new
{
Id = (int)contact.Attribute("id"),
Name = (string)contact.Element("name"),
Email = (string)contact.Element("email"),
Valor = confirmado
};
foreach (var contact in query)
{
...
}
Points of interest:
doc.Root.Elements("contact") selects only the <contact> elements in the document root, instead of searching the whole document for <contact> elements.
The XElement.Element method returns the first child element with the given name. No need to convert the child elements to a list and take the first element.
The XElement and XAttribute classes provide a wide selection of convenient conversion operators.
You could use LINQ:
foreach (XElement element in doc.Descendants("contact").Where(c => c.Element("confirmado").Value == "SI"))

Categories