Concatenate XML Node data - c#

I have XML look like this
<BoxResult>
<DocumentType>BCN</DocumentType>
<DocumentID>BCN_20131113_1197005001#854#11XEZPADAHANDELC</DocumentID>
<DocumentVersion>1</DocumentVersion>
<ebXMLMessageId>CENTRAL_MATCHING</ebXMLMessageId>
<State>FAILED</State>
<Timestamp>2013-11-13T13:02:57</Timestamp>
<Reason>
<ReasonCode>efet:IDNotFound</ReasonCode>
<ReasonText>Unknown Sender</ReasonText>
</Reason>
<Reason>
<ReasonCode>efet:IDNotFound</ReasonCode>
<ReasonText>Unknown Receiver</ReasonText>
</Reason>
</BoxResult>
In my C# code , i need to parse through the XML and concatenate the Reason Text Data.
Basically , i need the output as Unknown Sender ; Unknown Receiver
I tried the following code but i am not getting the desired output
XmlNodeList ReasonNodeList = xmlDoc.SelectNodes(/BoxResult/Reason);
foreach (XmlNode xmln in ReasonNodeList)
{
ReasonText = ReasonText + ";" + xmlDoc.SelectSingleNode(/BoxResult/Reason/ReasonText).InnerXml.ToString();
}
if (ReasonText != " ")
{
ReasonText = ReasonText.Substring(1);
}
The output i am getting from this code is Unknown Sender ; Unknown Sender
It is not displaying Unknown Receiver
Please advise and your help will be useful

You are always using the same node to retrieve the data. The xmlDoc is always called (i.e. the first <Reason> node), instead of each targeted node.
XmlNodeList ReasonNodeList = xmlDoc.SelectNodes("/BoxResult/Reason/ReasonText"); //change here
foreach (XmlNode xmln in ReasonNodeList)
{
ReasonText = ReasonText + ";" + xmln.InnerXml.ToString(); //change here
}
if (ReasonText != " ")
{
ReasonText = ReasonText.Substring(1);
}

You're iterating through <Reason> nodes and each time selecting the first /BoxResult/Reason/ReasonText node in document (note you're not using your xmln variable anywhere).
By the way, here's a shorter version (replaces your whole code block):
ReasonText += String.Join(";",
xmlDoc.SelectNodes("/BoxResult/Reason/ReasonText")
.Cast<XmlNode>()
.Select(n => n.InnerText));

Related

C# System.Xml.Linq.XElement.Value removed nested tags

I have XElement like
<text>The temperature is raised <b>75</b> to <b>80</b> Celsius</text>
I need to read the value of this XElement and
element.value returns
"The temperature is raised 75 to 80 Celcius"
could someone please tell how can I get XElement value
"The temperature is raised <b>75</b> to <b>80</b> Celsius"
it seems iterating through elements using
var nodes = element.Nodes();
foreach (var node in nodes)
{
}
does not resolve the issue
I think you can simply use ToString() method to get the string representation of an XElement.
XElement element = new XElement("text", "The temperature is raised ", new XElement("b", "75"), " to ", new XElement("b", "80"), " Celsius");
string elementString = element.ToString();
Console.WriteLine(elementString);
Updated: Sorry, now only I understand your problem, try this.
string elementValue = "";
foreach (var node in element.Nodes())
{
elementValue += node.ToString();
}
Console.WriteLine(elementValue);
Fiddle : https://dotnetfiddle.net/ZsoG9F

How can I search values of consecutive XML nodes with C#?

I want to select nodes from an XML that have consecutive child nodes with values matching with the respective words from my search term.
Here is a sample XML:
<book name="Nature">
<page number="4">
<line ln="10">
<word wn="1">a</word>
<word wn="2">white</word>
<word wn="3">bobcat</word>
<word wn="3">said</word>
</line>
<line ln="11">
<word wn="1">Hi</word>
<word wn="2">there,</word>
<word wn="3">Bob.</word>
</line>
</page>
My search term is Hi Bob. I want find all the nodes from the above XML that contain two consecutive words with values %Hi% and %Bob%. Please note that I want to perform a partial and case-insensitive match for each word in my search term.
It should return the following output for the above XML:
ln="10" wn="2" wn="3"
Please note that line (ln=10) is selected because it contains two consecutive words (in the correct order) that match with the search term. white=%Hi% bobcat=%Bob%
However, the next line (ln=11) is not selected because the matching nodes are not consecutive.
Please note that all the words from the search term should be matched in order for it to be considered a match.
Thank you!
[Edit]
I tried the following solution and it yields the expected results. Is there a better or more efficient way of achieving this? The program has to search 100,000 XML files per day and each of them would be 300 KB to 50 MB.
XDocument xDoc = XDocument.Load(#"C:\dummy.xml");
var xLines = xDoc
.Descendants("page")
.Descendants("line");
foreach (var xLine in xLines)
{
var xFirstWords = xLine
.Descendants("word")
.Where(item => item.Value.ToUpper().Contains("HI"));
foreach (var xFirstWord in xFirstWords)
{
var xNextWord = xFirstWord.NodesAfterSelf().OfType<XElement>().First();
if(xNextWord.Value.ToUpper().Contains("BOB"))
{
MessageBox.Show(xLine.FirstAttribute.Value + " " + xFirstWord.FirstAttribute.Value + " " + xNextWord.FirstAttribute.Value);
}
}
}
I could improvise my code. Please let me know if you have a better solution.
XDocument xDoc = XDocument.Load(#"C:\dummy.xml");
var xLines = xDoc
.Descendants("page")
.Descendants("line");
foreach (var xLine in xLines)
{
var xFirstWords = xLine
.Descendants("word")
.Where(item => item.Value.ToUpper().Contains("HI"))
.Where(item => item.ElementsAfterSelf("word").First().Value.ToUpper().Contains("BOB"));
foreach (var xFirstWord in xFirstWords)
{
var xNextWord = xFirstWord.ElementsAfterSelf("word").First();
MessageBox.Show(xLine.FirstAttribute.Value + " " + xFirstWord.FirstAttribute.Value + " " + xNextWord.FirstAttribute.Value);
}
}
I have no idea whether the performance of this code will be better or worse, but I'm pretty sure it will be different, so it might be worth trying. Reconstitute the text of the line and then use a regular expression to match.
Regex re = new Regex(#"^.*Hi\s+\S+\s+Bob$*", RegexOptions.IgnoreCase);
XDocument xDoc = XDocument.Load(#"C:\Users\user\Documents\temp.xml");
foreach (XElement xLine in xDoc.Root.Descendants("line")) {
string text = string.Join(" ", xLine.Elements("word").Select(x => x.Value));
if (re.IsMatch(text)) {
Console.WriteLine(text);
}
}
Things that come to mind performance-wise:
.Nodes will be faster than .Descendants, as it only gets the direct children.
Use IndexOf with OrdinalIgnoreCase instead of ToUpper.Contains.
In the foreach instead of NodesAfterSelf, you can just hold the previous node.
var xLines = xDoc.Descendants("line");
foreach (var xLine in xLines)
{
XNode prevWord = null;
foreach (var word in xLine.Nodes("word"))
{
if(prevWord == null && word.Value.IndexOf("HI", StringComparison.OrdinalIgnoreCase))
{
prevWord == word;
}
else if(prevWord != null && word.Value.IndexOf("BOB"), StringComparison.OrdinalIgnoreCase))
{
MessageBox.Show(xLine.FirstAttribute.Value + " " + prevWord.FirstAttribute.Value + " " + word.FirstAttribute.Value);
}
}
}

XPath to access the label/value nodes in the xml

I am trying to access the label / value nodes in the below xml . I have to match the label and get the Value.
<OneStopCenterResult>
<OneStopCenterInfoResults>
<OneStopCenterInfo>
<LabelValues>
<Label>Name of Center</Label>
<Value>Arlington Employment Center</Value>
</LabelValues>
<LabelValues>
<Label>ADDRESS_1</Label>
<Value>2100 Washington Blvd</Value>
</LabelValues>
</OneStopCenterInfo>
</OneStopCenterInfoResults>
</OneStopCenterResult>
C#
XmlDocument xmlDocument = HelperClass.GetXMLDocument(AJCDetUri);
ltrAJCDetail.Text = GetHTMLTableString(xmlDocument);
C# function call
I am using the below function to retrieve the label/value based on the label value.
private string GetHTMLTableString(XmlDocument xmlResults)
{
//Sort the table based on EmpCount
string outPutString = string.Empty;
XmlNodeList empDetail=null;
try
{
//the below code is not working
empDetail = xmlResults.SelectNodes("/OneStopCenterResult/OneStopCenterInfoResults/OneStopCenterInfo/LabelValues[Label] ='Name of Center'");
//foreach (XmlNode node in empDetail)
//{
// Response.Write(" for loop " + node.SelectSingleNode("/Label").Value);
//}
}
catch (Exception ex)
{
Response.Write(" Error " +ex.ToString());
Response.End();
}
}
Thanks in advance
You can use XPath directly, though that results in a somewhat complicated expression along the lines of //LabelValues/Value[preceding-sibling::Label[1]="Name of Center"]/text() that looks for the text() of a Value element whose first preceding-sibling::Label element contains Name of Center. Test case via xpquery:
% cat label.xml
<foo>
<LabelValues>
<Label>Name of Center</Label>
<Value>v</Value>
</LabelValues>
</foo>
% xpquery '//LabelValues/Value[preceding-sibling::Label[1]="Name of Center"]/text()' label.xml
v
%
Hmm probably implementation dependent; yours gets the element, not the text:
% xpquery '//LabelValues/Value[preceding-sibling::Label="Name of Center"]' label.xml
<Value>v</Value>

XElement.Element returning null for newly created element

I am using XElement to create an XMLDocument which is used in a hierarchical WPF treeview. If I create a new element with :
x_element = new XElement("node",
new XElement("tree_id", strData[0]),
new XElement("sys_id", dpl.DepSysId),
new XElement("part_id", strData[8]),
new XElement("make", strData[6]),
new XElement("model", strData[5]),
new XElement("level", strData[2]));
I then need to add attributes to "node" so I tried:
XElement temp_el = x_element.Element("node"); // This is returning null
temp_el.SetAttributeValue("title", strData[7] + " " + strData[6] + " " + strData[5]);
temp_el.SetAttributeValue("canEdit", "False");
temp_el.SetAttributeValue("status", nStatus.ToString());
temp_el.SetAttributeValue("qty", strData[13]);
temp_el.SetAttributeValue("part", strData[8]);
In the above code temp_el is null, but I can see in the debugger that x_element contains the following :
<node>
<tree_id>82</tree_id>
<sys_id>82</sys_id>
<part_id>169</part_id>
<make>ST Panel</make>
<model>Logical Pure 16 tube Collector</model>
<level>0</level>
</node>
To work around this I have used the following:
foreach (XElement temp_el in x_element.DescendantsAndSelf())
{
if (temp_el.Name == "node")
{
temp_el.SetAttributeValue("title", strData[7] + " " + strData[6] + " " + strData[5]);
temp_el.SetAttributeValue("canEdit", "False");
temp_el.SetAttributeValue("status", nStatus.ToString());
temp_el.SetAttributeValue("qty", strData[13]);
temp_el.SetAttributeValue("part", strData[8]);
break;
}
}
Whilst the above works I am just curious as to why I am getting null returned. Is my workaround the best way of doing this?
Regards.
You defined your XElement like this:
x_element = new XElement("node", /* child nodes */);
Where "node" is the name of the XElement you are creating, and the following parameters are its children.
By using x_element.Node("node"), you are trying to get the child node named "node", and there isn't such a child node.
x_element itself is the node named "node".
DescendantsAndSelf worked because it includes x_element (hence "AndSelf"), but you don't need this either because you already have the node.
So you can change your second code snippet to this:
x_element.SetAttributeValue("title", strData[7] + " " + strData[6] + " " + strData[5]);
x_element.SetAttributeValue("canEdit", "False");
// etc.
(BTW, you can also add the Attributes in the constructor)
Because with your first temp_el,
XElement temp_el = x_element.Element("node");
You used to get nodes which is not treated to be an Element of x_element.
It was treated as its root. However, with the second one,
x_element.DescendantsAndSelf()`
You used this XElement Method which treat node itself as an element.
XContainer.Elements Method - Returns a collection of the child elements of this element or document, in document order.
XElement.DescendantsAndSelf Method - Returns a collection of elements that contain this element, and all descendant elements of this element, in document order.
To solve the issue I used Descendants(). Here is my code snippet
public void UpdateEnquiry([FromBody]XElement UpdatePurchaseOrder)
{
var obj = XElement.Parse(UpdatePurchaseOrder.ToString());
var ii = (from v in obj.Descendants() select new { v.Value }).ToList() ;
}

Reading multiple child nodes of xml file

I have created an Xml file with example contents as follows:
<?xml version="1.0" encoding="utf-8" ?>
<Periods>
<PeriodGroup name="HER">
<Period>
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Iron Age</PeriodName>
<StartDate>-800</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
</Period>
</PeriodGroup>
<PeriodGroup name="CAFG">
<Period>
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
</Period>
<Period>
<PeriodName>Anglo-Saxon</PeriodName>
<StartDate>410</StartDate>
<EndDate>800</EndDate>
</Period>
</PeriodGroup>
</Periods>
I need to be able to read the Period node children within a selected PeriodGroup. I guess the PeriodName could be an attribute of Period if that is more sensible.
I have looked at loads of examples but none seem to be quite right and there seems to be dozens of different methods, some using XmlReader, some XmlTextReader and some not using either. As this is my first time reading an Xml file, I thought I'd ask if anyone could give me a pointer. I've got something working just to try things out, but it feels clunky. I'm using VS2010 and c#. Also, I see a lot of people are using LINQ-Xml, so I'd appreciate the pros and cons of using this method.
string PG = "HER";
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("./Xml/XmlFile.xml"));
string text = string.Empty;
XmlNodeList xnl = doc.SelectNodes("/Periods/PeriodGroup");
foreach (XmlNode node in xnl)
{
text = node.Attributes["name"].InnerText;
if (text == PG)
{
XmlNodeList xnl2 = doc.SelectNodes("/Periods/PeriodGroup/Period");
foreach (XmlNode node2 in xnl2)
{
text = text + "<br>" + node2["PeriodName"].InnerText;
text = text + "<br>" + node2["StartDate"].InnerText;
text = text + "<br>" + node2["EndDate"].InnerText;
}
}
Response.Write(text);
}
You could use an XPath approach like so:
XmlNodeList xnl = doc.SelectNodes(string.Format("/Periods/PeriodGroup[#name='{0}']/Period", PG));
Though prefer LINQ to XML for it's readability.
This will return Period node children based on the PeriodGroup name attribute supplied, e.g. HER:
XDocument xml = XDocument.Load(HttpContext.Current.Server.MapPath(FileLoc));
var nodes = (from n in xml.Descendants("Periods")
where n.Element("PeriodGroup").Attribute("name").Value == "HER"
select n.Element("PeriodGroup").Descendants().Elements()).ToList();
Results:
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
<PeriodName>Iron Age</PeriodName>
<StartDate>-800</StartDate>
<EndDate>43</EndDate>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
The query is pretty straightforward
from n in xml.Descendants("Periods")
Will return a collection of the descendant elements for the element Periods.
We then use where to filter this collection of nodes based on attribute value:
where n.Element("PeriodGroup").Attribute("name").Value == "HER"
Will then filter down the collection to PeriodGroup elements that have a name attribute with a value of HER
Finally, we select the PeriodGroup element and get it's descendant nodes
select n.Element("PeriodGroup").Descendants().Elements()
EDIT (See comments)
Since the result of this expression is just a query, we use .ToList() to enumerate the collection and return an object containing the values you need. You could also create anonymous types to store the element values for example:
var nodes = (from n in xml.Descendants("Period").
Where(r => r.Parent.Attribute("name").Value == "HER")
select new
{
PeriodName = (string)n.Element("PeriodName").Value,
StartDate = (string)n.Element("StartDate").Value,
EndDate = (string)n.Element("EndDate").Value
}).ToList();
//Crude demonstration of how you can reference each specific element in the result
//I would recommend using a stringbuilder here..
foreach (var n in nodes)
{
text += "<br>" + n.PeriodName;
text += "<br>" + n.StartDate;
text += "<br>" + n.EndDate;
}
This is what the nodes object will look like after the query has run:
Since the XmlDocument.SelectNodes method actually accepts an XPath expression, you're free to go like this:
XmlNodeList xnl = doc.SelectNodes("/Periods/PeriodGroup[#name='" + PG + "']/Period");
foreach (XmlNode node in xnl) {
// Every node here is a <Period> child of the relevant <PeriodGroup>.
}
You can learn more on XPath at w3schools.
go thru this
public static void XMLNodeCheck(XmlNode xmlNode)
{
if (xmlNode.HasChildNodes)
{
foreach (XmlNode node in xmlNode)
{
if (node.HasChildNodes)
{
Console.WriteLine(node.Name);
if (node.Attributes.Count!=0)
{
foreach (XmlAttribute att in node.Attributes)
{
Console.WriteLine("----------" + att.Name + "----------" + att.Value);
}
}
XMLNodeCheck(node);//recursive function
}
else
{
if (!node.Equals(XmlNodeType.Element))
{
Console.WriteLine(node.InnerText);
}
}
}
}
}

Categories

Resources