C# XML child node of similar sibling - c#

I'm trying to modify an XML document that we didn't create initially. Snippit from XML is below:
<DEALS>
<DEAL>
<LOANS>
<LOAN LoanRoleType="SubjectLoan">
<BUYDOWN>
<BUYDOWN_RULE>
<BuydownInformation>0</BuydownInformation>
</BUYDOWN_RULE>
</BUYDOWN>
</LOAN>
<LOAN LoanRoleType="SubjectLoan">
<LOAN_IDENTIFIERS>
<LOAN_IDENTIFIER>
...
</LOAN_IDENTIFIER>
<LOAN_IDENTIFIER>
<SellerLoanIdentifier>1234567890</SellerLoanIdentifier>
</LOAN_IDENTIFIER>
</LOAN_IDENTIFIERS>
</LOAN>
</LOANS>
</DEAL>
<DEAL>
...Same format as above...
</DEAL>
</DEALS>
The first LOAN element of each DEAL will never contain LOAN_IDENTIFIERS. I need to get the SellerLoanIdentifier's InnerText, and then put it into <BuydownInformation> of the first LOAN element. I've tried nested loops and can't seem to get it to differentiate between the two LOAN elements (the second loop isn't even seeing the LOAN elements). I'm thinking it also might have to do with the fact that they both carry the exact same attribute but can't find anything online up to this point to help.
XmlDocument xmlExport = new XmlDocument();
xmlExport.Load(fileDestination);
string loanNumber = "";
XmlNodeList loan_XMLDeals = xmlExport.GetElementsByTagName("DEAL");
Logger.WriteDebug("Found " + loan_XMLDeals.Count + " Deals");
foreach (XmlNode loan_XMLDeal in loan_XMLDeals)
{
XmlNodeList loan_XMLLoans = loan_XMLDeal.SelectNodes("LOAN");
Logger.WriteDebug("Found " + loan_XMLLoans.Count + " Loan categories");
foreach (XmlNode loan_XMLCategory in loan_XMLLoans)
{
if(loan_XMLCategory.SelectSingleNode("SellerLoanIdentifier") != null)
{
loanNumber = loan_XMLCategory.SelectSingleNode("SellerLoanIdentifier").ToString();
Logger.WriteDebug("Got loan number " + loanNumber);
}
}
}

This becomes a lot easier with linq to xml. This means ditching the (old) XmlDocument and replacing it with the friendlier XDocument.
Instead of searching the whole document for the target, you need to start from the context of where you found the SellerLoanIdentifier. You can walk back up to the LOAN element, find its previous sibling, then search that for the BuydownInformation. Because this was all scoped within a single LOANS entry, you can be sure you're targeting the right element.
So...
var doc = XDocument.Load(fileDestination);
//we're going to select a sequence of items that contain 2 values...
//the element we want to change and the value we want to store in it
var changes= doc.Root
.Elements("DEAL")
.Descendants("SellerLoanIdentifier")
//from each SellerLoanIdentifier in DEAL elements
.Select(e => new{
//the node we want to change
//in this case we get the parent LOAN
//element, take the last of the elements
//that precede it in the document
//(e.g. the previous sibling which
//contains the target node)
//and find in it a descendant of type
//BuydownInformation
nodeToChange = e.Ancestors("LOAN")
.Single()
.ElementsBeforeSelf()
.Last()
.Descendants("BuydownInformation")
.Single(),
//the string value of the current element
val = (string)e
});
//then apply the changes back to the document
foreach(var change in changes)
{
change.nodeToChange.Value = change.val;
}
var newXmlString = doc.ToString();
There are assumptions here about the shape of your data that may not hold true, but it should be relatively easy to modify.

Try this simple XML Linq code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<XElement> loans = doc.Descendants("LOANS").ToList();
foreach (XElement loan in loans)
{
string sellerLoanIdentifier = (string)loan.Descendants("SellerLoanIdentifier").FirstOrDefault();
XElement buydownInformation = loan.Descendants("BuydownInformation").FirstOrDefault();
buydownInformation.Value = sellerLoanIdentifier;
}
}
}
}

Related

Selecting the value of XML elements

I don't have much experience with XML files but I'm trying to append a tutorial I found online to suite my needs and I'm not getting the results I would expect.
https://support.microsoft.com/en-us/help/307548/how-to-read-xml-from-a-file-by-using-visual-c
I've searched around but everything I've found doesn't make sense to me.
My XML looks like this for the most part:
<US>
<!-- Kentucky Start -->
<State>Kentucky KY
<City>Newport
<Street>Pavilion Parkway<Number>130<PostalCode>41071</PostalCode></Number></Street>
</City>
<City>Corbin
<Street>Highway 90<Number>7351<PostalCode>40701</PostalCode></Number></Street>
</City>
</State>
</US>
I'm trying to populate a listbox with the value of each state but my code either returns white space or just the text within the XML tag
e.g.
State..
State..
repeated for each element.
while (reader.Read()) {
switch (reader.NodeType) {
case XmlNodeType.Element: // The node is an element.
// Skip over root element
if (reader.Name.Equals("US")) {
reader.MoveToNextAttribute();
}
else {
if(reader.Name.Equals("State")) {
lbState.Items.Add(reader.Name);
lbState.Items.Add(reader.Value);
}
}
break;
reader.Name returns "State"
reader.Value returns "Whitespace"
I don't understand why reader.Value does not return Kentucky KY...
I've seen other examples that use string builder, is this a bad approach?
Use reader.ReadString() instead of reader.Value
Try xml linq :
sing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var results = doc.Descendants("State").Select(x => new {
cities = x.Elements("City").Select(y => new {
state = (string)x,
city = (string)y,
streets = y.Elements("Street").Select(z => (string)z).ToList()
}).ToList()
}).SelectMany(x => x.cities).ToList();
}
}
}
You can use XmDocument (see: https://msdn.microsoft.com/en-us/library/system.xml.xmldocument(v=vs.110).aspx) and then use an xpath expression to get the right elements from your document:
Also it is better to encapsulate the name of the state (that is, if you own the xml document) like this:
<name>Kentucy KY</name>
So you can do the following:
var items = new List<string>();
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml("yourxml");
var xmlNodes = xmlDocument.SelectNodes("//State/Name");
foreach (XmlNode node in xmlNodes)
{
items.Add(xmlNode.Value);
}

XmlElement InnerText property

I'm delving into the world of XmlDocument building and thought I'd try to re-build (at least, in part) the Desktop tree given by Microsoft's program UISpy.
So far I am able to grab a child of the desktop and write that to a XML document, and then grab each child of that and write those to an XML document.
So far the code looks like this...
using System.Windows.Automation;
using System.Xml;
namespace MyTestApplication
{
internal class TestXmlStuff
{
public static void Main(string[] args)
{
XmlDocument xDocument = new XmlDocument();
AutomationElement rootElement = AutomationElement.RootElement;
TreeWalker treeWalker = TreeWalker.ContentViewWalker;
XmlNode rootXmlElement = xDocument.AppendChild(xDocument.CreateElement("Desktop"));
AutomationElement autoElement = rootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "GitHub"));
string name = autoElement.Current.Name;
while (autoElement != null)
{
string lct = autoElement.Current.LocalizedControlType.Replace(" ", "");
lct = (lct.Equals("") ? "Cusotm" : lct);
XmlElement temp = (XmlElement)rootXmlElement.AppendChild(xDocument.CreateElement(lct));
//temp.InnerText = lct;
string outerXML = temp.OuterXml;
rootXmlElement = temp;
autoElement = treeWalker.GetNextSibling(autoElement);
}
}
}
}
...and the resulting XML file...
Now, when I add a line to change the InnerText Property of each XML element, like temp.InnerText = lct I get an oddly formated XML file.
What I expected from this was that each InnerText would be on the same line as the start and end tags of the XML element, but instead all but the last element's InnerText is located on a new line.
So my question is, why is that? Is there something else I could be doing with my XML elements to have their InnerText appear on the same line?
As I said in a comment, XML isn't a display format, so it gets formatted however IE chooses to do so.
To get closer to what you were expecting, you might want to consider using an attribute rather than innertext:
XmlElement temp = (XmlElement)rootXmlElement.AppendChild(xDocument.CreateElement(lct));
var attr = xDocument.CreateAttribute("type");
attr.Value = lct;
temp.Attributes.Append(attr);
IE displays the attributes within the opening element, which may be good enough for your purposes.
From the XML perspective, what you're currently creating is called Mixed Content - you have an element that contains both text and other elements. From a hierarchical perspective, those text nodes and other elements occupy the same position within the hierarchy - so I'd assume that this is why IE is displaying them as "equals" - both nested under their parent element and at the same indentation level.

How add,edit and delete node by line in xml

I have string XML. I loaded to XmlDocument. How can I add, edit and delete by simplest method by line, because I know only line which I should edit. It's better work wih XML like with string, or better work with XmlDocuments?
using System;
using System.Xml;
namespace testXMl
{
class Program
{
static void Main(string[] args)
{
string xml="<?xml version=\"1.0\"?>\r\n<application>\r\n<features>\r\n<test key=\"some_key\">\r\n</features>\r\n</application>";
XmlDocument xm = new XmlDocument();
xm.LoadXml(xml);
//Edit third line
//xm[3].EditName(featuresNew);
//xml->"<?xml version=\"1.0\"?>\r\n<application>\r\n<featuresNew>\r\n<test key=\"some_key\">\r\n</featuresNew>\r\n</application>"
//Add fourth line the Node
//xm[4].AddNode("FeatureNext");
//xml->"<?xml version=\"1.0\"?>\r\n<application>\r\n<FeatureNext>\r\n<FeatureNext>\r\n</features2>\r\n<test key=\"some_key\">\r\n</features>\r\n</application>"
//Delete sixth line
//xm[6].DeleteNode;
//xml->"<?xml version=\"1.0\"?>\r\n<application>\r\n<FeatureNext>\r\n<FeatureNext>\r\n</features2>\r\n</features>\r\n</application>"
}
}
}
Thanks, in advance.
You should always work with XDocument/XmlDocument objects. A key knowledge is the XPath query language.
This a quick XML crash course. Run with debugger and inspect the XML variable as you move on.
var xml = new XmlDocument();
xml.LoadXml(#"<?xml version='1.0'?>
<application>
<features>
<test key='some_key' />
</features>
</application>");
// Select an element to work with; I prefer to work with XmlElement instead of XmlNode
var test = (XmlElement) xml.SelectSingleNode("//test");
test.InnerText = "another";
test.SetAttribute("sample", "value");
var attr = test.GetAttribute("xyz"); // Works, even if that attribute doesn't exists
// Create a new element: you'll need to point where you should add a child element
var newElement = xml.CreateElement("newElement");
xml.SelectSingleNode("/application/features").AppendChild(newElement);
// You can also select elements by its position;
// in this example, take the second element inside "features" regardless its name
var delete = xml.SelectSingleNode("/application/features/*[2]");
// Trick part: if you found the element, navigate to its parent and remove the child
if (delete != null)
delete.ParentNode.RemoveChild(delete);

Updating a specific XML node

I am new to XML files and how to manage them. This is for a web app I am writing (aspx).
At the present time I am able to find the first instance of a node and add an item to it with the following code:
xmlClone.Element("PCs").Element("PC").Element("pc_hwStatus").AddAfterSelf(new XElement("user_name", txt_v0_nombre.Text));
What I really want is to add ("user_name", txt_v0_nombre.Text) to a node in particular, not the first one. The content of my XML file is:
<PCs>
<PC>
<pc_name>esc01</pc_name>
<pc_ip>10.10.10.10</pc_ip>
<pc_hwStatus>Working</pc_hwStatus>
</PC>
<PC>
<pc_name>esc02</pc_name>
<pc_ip>10.10.10.11</pc_ip>
<pc_hwStatus>Under Maintenance</pc_hwStatus>
</PC>
</PCs>
The decision of what node to update is made selecting an item from a dropdown list (the PC name).
With my current code, the new item is always added as last line of node with "pc_
name = esc01". I want to be able to added it to esc02 or esc03 and so on... How can this be accomplished? (Using xdocument)
If I understand you correctly, what you are looking for is the FirstOrDefault extension method. In there specify which node you are wanting, in this case a string from your dropdown box, which can be passed in. So to get the first node:
var pc = xmlClone.Element("PCs").Elements("PC").FirstOrDefault(e => e.Element("pc_name").Value == "esc01");
Now you have this in your XElement:
<PC>
<pc_name>esc01</pc_name>
<pc_ip>10.10.10.10</pc_ip>
<pc_hwStatus>Working</pc_hwStatus>
</PC>
To get any element like that, just replace this clause:
.FirstOrDefault(e => e.Element("pc_name").Value == "esc01");
with this one
.FirstOrDefault(e => e.Element("pc_name").Value == desiredPC);
where desiredPC is the value of the xml node: pc_name.
Now to add your data just call the plain old Add method:
pc.Add(new XElement("user_name", txt_v0_nombre.Text);
That should do the trick for you.
Here's a solution that uses LINQ query syntax with LINQ to XML:
XDocument document = XDocument.Parse(xmlContent);
string pcName = "esc02";
IEnumerable<XElement> query =
from pc in document.Element("PCs").Elements("PC")
where pc.Element("pc_name").Value.Equals(pcName)
select pc;
XElement xe = query.FirstOrDefault();
if (xe != null)
{
xe.Add(new XElement("user_name", "DMS"));
}
I have incorporated your sample data and this query into a demonstration program. Please see below for the output from the demonstration program followed by the program itself.
Expected Output
<PC>
<pc_name>esc02</pc_name>
<pc_ip>10.10.10.11</pc_ip>
<pc_hwStatus>Under Maintenance</pc_hwStatus>
<user_name>DMS</user_name>
</PC>
Demonstration Program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace LinqToXmlDemo
{
public class Program
{
public static void Main(string[] args)
{
string xmlContent = GetXml();
XDocument document = XDocument.Parse(xmlContent);
XElement xe = FindPCName(document, "esc02");
if (xe != null)
{
xe.Add(new XElement("user_name", "DMS"));
Console.WriteLine(xe);
}
else
{
Console.WriteLine("Query returned no results.");
}
}
private static XElement FindPCName(XDocument document, String pcName)
{
IEnumerable<XElement> query =
from pc in document.Element("PCs").Elements("PC")
where pc.Element("pc_name").Value.Equals(pcName)
select pc;
return query.FirstOrDefault();
}
private static String GetXml()
{
return
#"<?xml version='1.0' encoding='utf-8'?>
<PCs>
<PC>
<pc_name>esc01</pc_name>
<pc_ip>10.10.10.10</pc_ip>
<pc_hwStatus>Working</pc_hwStatus>
</PC>
<PC>
<pc_name>esc02</pc_name>
<pc_ip>10.10.10.11</pc_ip>
<pc_hwStatus>Under Maintenance</pc_hwStatus>
</PC>
</PCs>";
}
}
}
Method .Element returns the first element with the specified name.
You can get the whole list with method .Elements, and iterate this list to find the one you are looking for.

How to get specific Values from a xml file with same Name in a element?

I don't know how to extract values from this specific XML document, and am looking for some help as I'm not very experienced on xml parsing.
I have to use XDocument.Load to load the file.
Actually i am using
doc = XDocument.Load(uri);
challenge = GetValue(doc, "Challenge");
this works without any problems, but how to get the inner values of the Element Rights ? (multiple "Name")
At the end of the day i need to now
Phone = x
Dial = x
HomeAuto = x
BoxAdmin = x
It’s also possible that some of the entries (Phone,Dial,HomeAuto,BoxAdmin) is missing. This
is dynamic.
Here is my xml File:
<SessionInfo>
<SID>68eba0c8cef752a7</SID>
<Challenge>37a5fe9f</Challenge>
<BlockTime>0</BlockTime>
<Rights>
<Name>Phone</Name>
<Access>2</Access>
<Name>Dial</Name>
<Access>2</Access>
<Name>HomeAuto</Name>
<Access>2</Access>
<Name>BoxAdmin</Name>
<Access>2</Access>
</Rights>
</SessionInfo>
Edit: (Add GetValue method)
public string GetValue(XDocument doc, string name)
{
XElement info = doc.FirstNode as XElement;
return info.Element(name).Value;
}
NB: this solution uses extension methods, so the using directives are important or you won't see the required functions.
using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Collections.Generic;
namespace StackOverflow
{
class Program
{
const string xml = "<SessionInfo><SID>68eba0c8cef752a7</SID><Challenge>37a5fe9f</Challenge><BlockTime>0</BlockTime><Rights><Name>Phone</Name><Access>2</Access><Name>Dial</Name><Access>2</Access><Name>HomeAuto</Name><Access>2</Access><Name>BoxAdmin</Name><Access>2</Access></Rights></SessionInfo>";
static void Main(string[] args)
{
XDocument doc = XDocument.Parse(xml); //loads xml from string above rather than file - just to make it easy for me to knock up this sample for you
string nameOfElementToFind = "Name";
IEnumerable<XElement> matches = doc.XPathSelectElements(string.Format("//*[local-name()='{0}']",nameOfElementToFind));
//at this stage you can reference any value from Matches by Index
Console.WriteLine(matches.Count() > 2 ? "Third name is: " + matches.ElementAt(2).Value : "There less than 3 values");
//or can loop through
foreach (XElement match in matches)
{
Console.WriteLine(match.Value);
//or if you also wanted the related access info (this is a bit loose / assumes the Name will always be followed by the related Value
//Console.WriteLine("{0}: {1}", match.Value, match.XPathSelectElement("./following-sibling::*[1]").Value);
}
Console.WriteLine("Done");
Console.ReadKey();
}
}
}
The important bit here is the line IEnumerable<XElement> matches = doc.XPathSelectElements(string.Format("//*[local-name()=\'{0}\']",nameOfElementToFind));. After the string.format takes place the XPath is //*[local-name()='Name']. This XPath statement says to find all nodes with the name Name. The local-name() function's there because we haven't said what schema's being used, in this instance we want any element called Name, regardless of schema.
XmlNamespaceManager nm = new XmlNamespaceManager(new NameTable());
nm.AddNamespace("eg", "http://Example/Namespace/Replace/With/Your/Docs/Namespace");
IEnumerable<XElement> matches = document.XPathSelectElements("//eg:Name", nm);
The double forward-slash says to search anywhere in the document. To limit it to Rights you could say /eg:SessionInfo/eg:Rights/eg:Name. In case you're unfamiliar with it, XPath's an awesome language / essential if you want to get the most out of working with XML docs. If you have any questions about it please give us a shout, or have a look around online; there are great tutorials out there.

Categories

Resources