How to read this part of the web XML file? - c#

I am working on a XML reader which shows the result in the labels.
I want to read the node called "Opmerking" which is standing in "Opmerkingen"
A example:
<VertrekkendeTrein>
<RitNummer>4085</RitNummer>
<VertrekTijd>2014-06-13T22:00:00+0200</VertrekTijd>
<EindBestemming>Rotterdam Centraal</EindBestemming>
<TreinSoort>Sprinter</TreinSoort>
<RouteTekst>A'dam Sloterdijk, Amsterdam C., Duivendrecht</RouteTekst>
<Vervoerder>NS</Vervoerder>
<VertrekSpoor wijziging="false">4</VertrekSpoor>
<Opmerkingen>
<Opmerking> Rijdt vandaag niet</Opmerking>
</Opmerkingen>
</VertrekkendeTrein>
"Opmerkingen" is not always there, it is always changing. The code i use now:
XmlNodeList nodeList = xmlDoc.SelectNodes("ActueleVertrekTijden/VertrekkendeTrein/*");
and:
foreach (XmlNode nodelist2 in nodeList)
{
if (i < 17) //4
{
switch (nodelist2.Name)
{
case "VertrekTijd": string kuttijd4 = (nodelist2.InnerText);
var res4 = Regex.Match(kuttijd4, #"\d{1,2}:\d{1,2}").Value;
lblv4.Text = Convert.ToString(res4); break;
case "TreinSoort": lblts4.Text = (nodelist2.InnerText); break;
case "RouteTekst": lblvia4.Text = (nodelist2.InnerText); break;
case "VertrekSpoor": lbls4.Text = (nodelist2.InnerText); i++; break;
}
}
}
How can i read the part "Opmerking" and set it in a case?
I tried it a few times, but it failed.
i also tried:
case "Opmerking": var texeliseeneiland1 = (nodelist2.InnerText); if (texeliseeneiland1 == null) { } else { lblop1.Text = texeliseeneiland1; lblop1.Font = new Font(lblop1.Font.FontFamily, 17); lblop1.Visible = true; picop1.Visible = true; }; break;
Anyone who knows the answer?

Just extend your logic with check whether current node has child nodes and if so, read them and process:
if (nodelist2.HasChildNodes)
{
for (int i=0; i<nodelist2.ChildNodes.Count; i++)
{
var childNode = root.ChildNodes[i];
//do whatever you need to display the contents of the child node.
}
}
Also I have to recommend to consider LinqToXML or at least refactor the code you shared. With LinqToXML is might be as easy as this:
var temp = from remarkNode in nodelist2.Descendants("Opmerking")
select remarkNode.Value;

Somehow load the xml content in an XDocument object and loop through it.
Example: read it from a file
var doc = XDocument.Load("C:/test.xml");
foreach (var xe in doc.Descendants("Opmerking"))
{
var value = xe.Value;
//Do your job with value
}

Related

Retrieving XML value in C# from arbitary key path

I've got a project where I'm currently implementing support for reading values from an XML file via an arbitrary/user-defined path within the document's keys.
For example, if the document looks like this:
<information>
<machine>
<foo></foo>
<name>
test machine
</name>
<bar>spam</bar>
</machine>
</information>
then the user might want to retrieve the value from the name key in information/machine.
Is there a way using XDocument/XPath that I can look up the values the user wants without knowing/coding in the schema for the document?
My initial thought was working through the document with a form of recursive function utilizing XElement items, but I feel like there ought to be a simpler/cleaner solution that doesn't require me rolling my own lookup code.
I also tried something along these lines
var doc = XDocument.Load("C:\Path\to\XML\file.xml");
// Split the parent keys string
XElement elem = doc.Root.XPathSelectElement("path/to/key");
if (elem != null && elem.Attribute("wantedKeyName") != null)
replace = elem.Attribute("wantedKeyName").Value;
but elem is always null. I'm assuming there's a problem with the way I'm defining my path or utilizing XPathSelectElement, but I haven't worked it out yet.
static XmlNode SearchNode(XmlNodeList nodeList, string nodeName)
{
for (int i = 0; i < nodeList.Count; i++)
{
if (nodeList[i].Name == nodeName)
{
return nodeList[i];
}
if (nodeList[i].HasChildNodes)
{
XmlNode node = SearchNode(nodeList[i].ChildNodes, nodeName);
if (node != null)
{
return node;
}
}
}
return null;
}
static XmlNodeList SearchNodeByPath(XmlNodeList nodeList, string xPath)
{
for (int i = 0; i < nodeList.Count; i++)
{
var nodes = nodeList[i].SelectNodes(xPath);
if (nodes != null && nodes.Count > 0)
{
return nodes;
}
if (nodeList[i].HasChildNodes)
{
XmlNodeList innerNodes = SearchNodeByPath(nodeList[i].ChildNodes, xPath);
if (innerNodes != null && innerNodes.Count > 0)
{
return innerNodes;
}
}
}
return null;
}
this is using of methods :
var node = SearchNode(doc.ChildNodes, "compiler");
var node1 = SearchNodeByPath(doc.ChildNodes, "compilers/compiler");
I turns out my solution using XPathSelectElement was the correct approach, I just had to prepend the path/to/key string with //.
The following code I ended up using does that and strips off any whitespace around the outside of the value (in case the value is on a separate line than the opening tag.
// xml is a struct with the path to the parent node (path/to/key)
// and the key name to look up
// Split the parent keys string
XElement elem = doc.Root.XPathSelectElement("//" + xml.KeyPath);
if (elem != null && elem.Element(xml.Key) != null)
replace = elem.Element(xml.Key).Value.Trim();

How to iterate XML by using XDocument in .Net

I have a big XML file where I am taking small snippet by using ReadFrom() and then I will get xmlsnippet which contains leaf, sas, kir tags at different positions (sometimes leaf at top compare to kir or viceversa).
Now the thing is I am using three foreach loop to get these values which is bad logic and it will take time when this snippet also big.
Is there anyway I can use one foreach loop and then three if loop inside foreach to get values?
arr is a custom arraylist
var xdoc = new XDocument(xmlsnippet);
string xml = RemoveAllNamespaces(xdoc.ToString());
foreach (XElement element in XDocument.Parse(xml).Descendants("leaf"))
{
arr.Add(new Test("leaf", element.Value, 2));
break;
}
foreach (XElement element in XDocument.Parse(xml).Descendants("sas"))
{
arr.Add(new Test("sas", element.Value, 2));
break;
}
foreach (XElement element in XDocument.Parse(xml).Descendants("kir"))
{
if (element.Value == "0")
arr.Add(new Test("kir", "90", 2));
break;
}
You only need to Parse that xmlsnippet once (assuming it fits in memory) and then use XNamespace to qualify the right XElement. No need to call RemoveAllnamespaces which I guess does what its name implies and probably does so in an awful way.
I used the following XML snippet as example input, notice the namespaces a, b and c:
var xmlsnippet = #"<root xmlns:a=""https://a.example.com""
xmlns:b=""https://b.example.com""
xmlns:c=""https://c.example.com"">
<child>
<a:leaf>42</a:leaf>
<a:leaf>43</a:leaf>
<a:leaf>44</a:leaf>
<somenode>
<b:sas>4242</b:sas>
<b:sas>4343</b:sas>
</somenode>
<other>
<c:kir>80292</c:kir>
<c:kir>0</c:kir>
</other>
</child>
</root>";
And then use Linq to either return an instance if your Test class or null if no element can be found. That Test class instance is then added to the arraylist.
var arr = new ArrayList();
var xdoc = XDocument.Parse(xmlsnippet);
// add namespaces
var nsa = (XNamespace) "https://a.example.com";
var nsb = (XNamespace) "https://b.example.com";
var nsc = (XNamespace) "https://c.example.com";
var leaf = xdoc.Descendants(nsa + "leaf").
Select(elem => new Test("leaf", elem.Value, 2)).FirstOrDefault();
if (leaf != null) {
arr.Add(leaf);
}
var sas = xdoc.Descendants(nsb + "sas").
Select(elem => new Test("sas", elem.Value, 2)).FirstOrDefault();
if (sas != null) {
arr.Add(sas);
}
var kir = xdoc.
Descendants(nsc + "kir").
Where(ele => ele.Value == "0").
Select(elem => new Test("kir", "90", 2)).
FirstOrDefault();
if (kir != null) {
arr.Add(kir);
}
I expect this to be the most efficient way to find those nodes if you want to stick with using XDocument. If the xml is really huge you might consider using an XMLReader but that probably only helps if memory is a problem.
If you want to do it one LINQ Query you can do this:
var q = xdoc
.Descendants()
.Where(elem => elem.Name.LocalName == "leaf" ||
elem.Name.LocalName == "sas" ||
elem.Name.LocalName == "kir" && elem.Value == "0" )
.GroupBy(k=> k.Name.LocalName)
.Select(k=>
new Test(
k.Key,
k.Key != "kir"? k.FirstOrDefault().Value: "90",
2)
);
arr.AddRange(q.ToList());
That query goes looking for all elements named leaf, sas or kir, groups them on the elementname and then takes the first element in each group. Notice the extra handling in case the elementname is kir. Both the where clause and the projection in Select need to deal with that. You might want to performance test this as I'm not sure how efficient this will be.
For completeness here is an XmlReader version:
var state = FoundElement.NONE;
using(var xe = XmlReader.Create(new StringReader(xmlsnippet)))
while (xe.Read())
{
// if we have not yet found an specific element
if (((state & FoundElement.Leaf) != FoundElement.Leaf) &&
xe.LocalName == "leaf")
{
// add it ... do not change the order of those arguments
arr.Add(new Test(xe.LocalName, xe.ReadElementContentAsString(), 2));
// keep track what we already handled.
state = state | FoundElement.Leaf;
}
if (((state & FoundElement.Sas) != FoundElement.Sas) &&
xe.LocalName == "sas")
{
arr.Add(new Test(xe.LocalName, xe.ReadElementContentAsString(), 2));
state = state | FoundElement.Sas;
}
if (((state & FoundElement.Kir) != FoundElement.Kir) &&
xe.LocalName == "kir")
{
var localName = xe.LocalName; // we need this ...
var cnt = xe.ReadElementContentAsString(); // ... because this moves the reader
if (cnt == "0") {
arr.Add(new Test(localName, "90", 2));
state = state | FoundElement.Kir;
}
}
}
And here is the enum with the different states.
[Flags]
enum FoundElement
{
NONE =0,
Leaf = 1,
Sas = 2,
Kir = 4
}

Getting text element XML Node based on element name C#

I want to create a function that will take an XML file and split it into its individual elements. Then depending on the element name I want to store the text from that element in an array. My function so far is:
public string[] read(string fileLoc)
{
//Create variables
r = XmlReader.Create(fileLoc, settingsR);
string[] content = new string[3];
while (r.Read())
{
if (r.NodeType == XmlNodeType.Element)
{
switch (r.Name)
{
case "Title":
content[0] = r.Name;
break;
case "Description":
content[1] = r.Name;
break;
default:
content[2] = r.Name;
break;
}
}
}
return content;
}
So far I can load the array with the name of the element, but not the text. Thanks

looping through same multiple nodes in an xml and merging numbers that are strings

I am having an issue where i am looping through multiple of the same nodes in an xml bill. As i was looping through the values and merging them accordingly I didnt realize that the datatype was a string. I need some help merging/summing up numbers that are strings. the loop contains all of the values included in the node but i am only showing the portion i need help with.
private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(string requestSA, string xmlBill, XmlNodeList detailPageNodes)
{
var saBillDetail = new ServiceAddressBillDetail();
saBillDetail.UsageServiceName = requestSA;
foreach (XmlNode detailPageNode in detailPageNodes)
{
if (totalSvcUseXMLNodes.Count > 0 && totalSvcUseXMLNodes[0].HasChildNodes)
{
var totalSvcNode = totalSvcUseXMLNodes[0].SelectSingleNode("IRBILGP_SA_TOTAL_KWH.SERVICE_ACCOUNT_STATEMENT");
if (totalSvcNode == null)
{
totalSvcNode = totalSvcUseXMLNodes[0].SelectSingleNode("IRBILGU_US_KWH_USAGE.USAGE");
}
saBillDetail.TotalServiceUsage = totalSvcNode.InnerText;
}
}
}
This is how i had it and i figured the datatype was a string. this way just concatenates the values together.
private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(string requestSA, string xmlBill, XmlNodeList detailPageNodes)
{
var saBillDetail = new ServiceAddressBillDetail();
saBillDetail.UsageServiceName = requestSA;
foreach (XmlNode detailPageNode in detailPageNodes)
{
if (totalSvcUseXMLNodes.Count > 0 && totalSvcUseXMLNodes[0].HasChildNodes)
{
var totalSvcNode = totalSvcUseXMLNodes[0].SelectSingleNode("IRBILGP_SA_TOTAL_KWH.SERVICE_ACCOUNT_STATEMENT");
if (totalSvcNode == null)
{
totalSvcNode = totalSvcUseXMLNodes[0].SelectSingleNode("IRBILGU_US_KWH_USAGE.USAGE");
}
saBillDetail.TotalServiceUsage += totalSvcNode.InnerText;
}
}
}
then i tried something like this where i am using int.Parse and adding the result back to result. But i am getting an error with the second result in the result = result + int.Parse(totalSvcNode.InnerText); saying it is unassigned variable
private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(string requestSA, string xmlBill, XmlNodeList detailPageNodes)
{
var saBillDetail = new ServiceAddressBillDetail();
saBillDetail.UsageServiceName = requestSA;
foreach (XmlNode detailPageNode in detailPageNodes)
{
if (totalSvcUseXMLNodes.Count > 0 && totalSvcUseXMLNodes[0].HasChildNodes)
{
var totalSvcNode = totalSvcUseXMLNodes[0].SelectSingleNode("IRBILGP_SA_TOTAL_KWH.SERVICE_ACCOUNT_STATEMENT");
if (totalSvcNode == null)
{
totalSvcNode = totalSvcUseXMLNodes[0].SelectSingleNode("IRBILGU_US_KWH_USAGE.USAGE");
}
int result;
//saBillDetail.TotalServiceUsage += totalSvcNode.InnerText;
result = result + int.Parse(totalSvcNode.InnerText);
saBillDetail.TotalServiceUsage = result.ToString();
}
}
}
I am stuck with the logic since its not two different TotalServiceUsage i am adding up together. Its the same TotalServiceUsage added up for every node found. I am a pretty new programmer and any help would be appreciated.
In your last example int result needs to be initialized outside the for loop:
int result =0;
If TotalServiceUsage is an int you can just use:
saBillDetail.TotalServiceUsage += int.Parse(totalSvcNode.InnerText);

changing a node type to #text whilst keeping the innernodes with the HtmlAgilityPack

I'm using the HtmlAgilityPack to parse an XML file that I'm converting to HTML. Some of the nodes will be converted to an HTML equivalent. The others that are unnecessary I need to remove while maintaining the contents. I tried converting it to a #text node with no luck. Here's my code:
private HtmlNode ConvertElementsPerDatabase(HtmlNode parentNode, bool transformChildNodes)
{
var listTagsToReplace = XmlTagMapping.SelectAll(string.Empty); // Custom Dataobject
var node = parentNode;
if (node != null)
{
var bNodeFound = false;
if (node.Name.Equals("xref"))
{
bNodeFound = true;
node = NodeXref(node);
}
if (node.Name.Equals("graphic"))
{
bNodeFound = true;
node = NodeGraphic(node);
}
if (node.Name.Equals("ext-link"))
{
bNodeFound = true;
node = NodeExtLink(node);
}
foreach (var infoTagToReplace in listTagsToReplace)
{
if (node.Name.Equals(infoTagToReplace.XmlTag))
{
bNodeFound = true;
node.Name = infoTagToReplace.HtmlTag;
if (!string.IsNullOrEmpty(infoTagToReplace.CssClass))
node.Attributes.Add("class", infoTagToReplace.CssClass);
if (node.HasAttributes)
{
var listTagAttributeToReplace = XmlTagAttributeMapping.SelectAll_TagId(infoTagToReplace.Id); // Custom Dataobject
for (int i = 0; i < node.Attributes.Count; i++ )
{
var bDeleteAttribute = true;
foreach (var infoTagAttributeToReplace in listTagAttributeToReplace)
{
if (infoTagAttributeToReplace.XmlName.Equals(node.Attributes[i].Name))
{
node.Attributes[i].Name = infoTagAttributeToReplace.HtmlName;
bDeleteAttribute = false;
}
}
if (bDeleteAttribute)
node.Attributes.Remove(node.Attributes[i].Name);
}
}
}
}
if (transformChildNodes)
for (int i = 0; i < parentNode.ChildNodes.Count; i++)
parentNode.ChildNodes[i] = ConvertElementsPerDatabase(parentNode.ChildNodes[i], true);
if (!bNodeFound)
{
// Replace with #text
}
}
return parentNode;
}
At the end I need to do the node replacement (where you see the "Replace with #text" comment) if the node is not found. I've been ripping my hair (what's left of it) out all day and it's probably something silly. I'm unable to get the help to compile and there is no online version. Help Stackoverflow! You're my only hope. ;-)
I would think you could just do this:
return new HtmlNode(HtmlNodeType.Text, parentNode.OwnerDocument, 0);
This of course adds the node to the head of the document, but I assume you have some sort of code in place to handle where in the document the node should be added.
Regarding the documentation comment, the current (as of this writing) download of the Html Agility Pack documentation contains a CHM file which doesn't require compilation in order to view.

Categories