I have an xml file like:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Session TimeStamp="2016-12-21T17:01:01.8642453+02:00">
<Message>
<Content>test1</Content>
<ID>1</ID>
<Timestamp>12/21/2016 17:01:01</Timestamp>
<EventType>Debug</EventType>
<Priority>High</Priority>
</Message>
<Message>
<Content>test2</Content>
<ID>2</ID>
<Timestamp>12/21/2016 17:01:01</Timestamp>
<EventType>Exception</EventType>
<Priority>Low</Priority>
</Message>
<Message>
<Content>test3</Content>
<ID>3</ID>
<Timestamp>12/21/2016 17:01:01</Timestamp>
<EventType>Info</EventType>
<Priority>Medium</Priority>
</Message>
<Message>
<Content>test4</Content>
<ID>4</ID>
<Timestamp>12/21/2016 17:01:01</Timestamp>
<EventType>Warn</EventType>
<Priority>None</Priority>
</Message>
</Session>
</Root>
I want to check the value of element Content in every message i have try with this method:
Assert.IsTrue(xDocument.Root.Elements("Session").Last().Elements("Message").First().Element("Content").Value.Contains("test1"));
exception: System.InvalidOperationException: Sequence contains no elements
The method fail, cant find the element value, how can i do it using xdocument?
Are you looking for this since you say
I want to check the value of element Content in every message
xDocument.Root.Elements("Session")
.Elements("Message")
.Elements("Content")
.Select(x => x.Value.Contains("test1"));
It would return which node contains test1 so the result would be true,false,false,false
Edit
as per your comment "i want only to verify if message 1 content contains string "test1" "
xDocument.Root.Elements("Session")
.Elements("Message")
.Elements("Content")
.FirstOrDefault().Value.Contains("test1");
XmlDocument advDoc=new XmlDocument();
advDoc.Load("test.xml");
XmlNodeList _ngroups = advDoc.GetElementsByTagName("Content");
foreach(XmlNode nd in _ngroups)
{
if(nd.InnerText.ToString()=="test1")
Console.WriteLine("true");
}
i want only to verify if message 1 content contains string "test1"
string pathToXmlFile = ""; // point to your xml file ...
using (StreamReader reader = File.OpenText(pathToXmlFile))
{
XDocument doc = XDocument.Load(reader); // load into XDocument
XElement idElement = doc.Root.Element("Session").Elements("Message").Elements("ID").First( item => item.Value == "1"); // since you need the message id = 1
string content = idElement.Parent.Element("Content").Value; // get the parent of this message id which is message element then navigate to its content element.
}
Hope this helps..
Related
<?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());
<?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");
}
I am trying to find a node in my xml file but getting the error ( see title)?
// instantiate XmlDocument and load XML from file
XmlDocument doc = new XmlDocument();
doc.Load(#"C:\temp\test2.xml");
var node = doc.SelectSingleNode("/Offers/Offer/ID=[text()='1']");
var test = node;
xml
<?xml version="1.0" encoding="utf-8"?>
<Offers>
<Offer>
<Model>AAAA</Model>
<ID>1</ID>
<Name>First offer</Name>
</Offer>
<Offer>
<Model>BBBB</Model>
<ID>2</ID>
<Name>Second offer</Name>
</Offer>
</Offers>
Remove the = after ID:
var node = doc.SelectSingleNode("/Offers/Offer/ID=[text()='1']");
becomes:
var node = doc.SelectSingleNode("/Offers/Offer/ID[text()='1']");
I currently have an XML Structure that looks something like this
<Parent>
<Info>
<Info-Data></Info-Data>
<Info-Data2></Info-Data2>
</Info>
<Message>
<Foo></Foo>
<Bar></Bar>
</Message>
<Message>
<Foo/>
<Bar/>
</Message>
</Parent>
What I'm trying to accomplish is split each Message into it's own unique XDocument. I want it to be
<Parent>
<Info />
<Message />
</Parent>
I tried to do the following.
XDocument xDoc = XDocument.Parse(myXMLString);
IEnumerable<XElement> elements = xDoc.Descendants(xDoc.Root.Name.NameSpace + "Message");
foreach(XElement element in elements)
{
XDocument newDoc = XDocument.Parse(element.ToString());
}
Obviously this only gets me everything from Message and below. I tried using Ancestors and AncestorsAndSelf but they always include BOTH Messages. Is there a different call I should be making?
If your format is fixed like this, it's not so bad:
foreach(XElement element in elements)
{
XDocument newDoc = new XDocument
(new XElement(xDoc.Root.Name,
xDoc.Root.Element("Info"),
element));
// ...
}
It's not great, but it's not horrendous. An alternative is to clone the original document, remove all the Message elements, then repeatedly clone the "gutted" version and add one element at a time to the new clone:
XDocument gutted = new XDocument(xDoc);
gutted.Descendants(xDoc.Root.Name.Namespace + "Message").Remove();
foreach(XElement element in elements)
{
XDocument newDoc = new XDocument(gutted);
newDoc.Root.Add(element);
// ...
}
I am trying to remove all the nodes from the XML file. But it's removing the root node open tag also .Using C# anf Linq
Input:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--Log the error count and error message-->
<root>
<ErrData>
<Count>1</Count>
<Timestamp>2011-11-21T11:57:12.3539044-05:00</Timestamp>
</ErrData>
<ErrData>max of 20 ErrData elements</ErrData>
</root>
Expected OP:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--Log the error count and error message-->
<root>
</root>
Actual OP:EDITED
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--Log the error count and error message-->
<root />
Code :
XDocument docs = XDocument.Load(path);
try
{
docs.Descendants("ErrData").Remove();
}
CODE:
Below is the code i am using ,the concept is the error count and timestamp are logged into XML file.Once its reached the threshold value,email will be send by function and remove all the nodes from the xml. Then when the next error comes it will start entering in to the xml file as below,
XDocument doc = null;
XElement el;
if (!System.IO.File.Exists(path))
{
doc = new XDocument(new XDeclaration("1.0", "utf-8", "no"));
el = new XElement("root");
//el = new XElement("root");
XComment comment = new XComment("Log the error count and error message");
doc.Add(comment);
}
else
{
doc = XDocument.Load(path);
}
XElement p1 = new XElement("ErrData");
XElement p1Count = new XElement("Count", eventCount);
XElement p1Windowsatrt = new XElement("Timestamp", windowStart);
p1.Add(p1Count );
p1.Add(p1Windowsatrt );
if (doc.Root != null)
{
el = doc.Root;
el.Add(p1);
}
else
{
el = new XElement("root");
el.Add(p1);
}
try
{
doc.Add(el);//Line throwing the exeception
}
catch (Exception e)
{
}
finally
{
doc.Save(path);
}
Use docs.Root.Nodes().Remove().
<root /> is valid XML for a tag with no content (self-closing tags). If you absolutely need an opening and closing tag you need to put some content in the root node like a comment or text.
The confusion is in your very first sentence: "I am trying to remove all the nodes/elements from the XML file." Which one is it? Do you want to remove all nodes, or all elements?
There are five types of nodes in XML: elements, text, comments, processing instructions, and attributes. If you use "node" and "element" interchangeably, as you are here, you're going to have no end of trouble working with XML.
What you got, <root/>, is the correct output for code that removes all descendant nodes: it's a single element named root with no content.
What you expect,
<root>
</root>
is a single element named root that contains a child text node containing whitespace, probably a newline. The code you wrote removes all descendant nodes, not just descendant element nodes, and so it removed this text node as well.