Load repetitively-named XML nodes using Linq [C#] - c#

I'm working on a program that needs to be able to load object-properties from an XML file. These properties are configurable by the user and XML makes sense to me to use.
Take the following XML document.
<?xml version="1.0" encoding="utf-8" ?>
<udpcommands>
<command name="requser">
<cvar name="reqchallege" value="false" />
</command>
<command name="reqprocs">
<cvar name="reqchallenge" value="false" />
</command>
</udpcommands>
I need to be able to load values from the cvars above to properties. I'm think Linq-To-XML would be good for it (I'm looking for applications of Linq so I can learn it). I've got a Linq-to-XML query done to select the right "command" based on the name.I was reading MSDN for help on this.
The following code snippet goes in a constructor that takes the parameter "string name" which identifies the correct XML <command> to pull.
I would like to have one linq statement to pull each <cvar> out of that XML given the section name, dumping everything to an IEnumerable. Or, I'm looking for a better option perhaps. I'm open for anything really. I would just like to use Linq so I can learn it better.
XElement doc = XElement.Load("udpcommands.xml");
IEnumerable<XElement> a = from el in doc.Elements()
where el.FirstAttribute.Value == name
select el;
foreach (var c in a)
{
Console.WriteLine(c);
}
The above code snippet outputs the following to the console:
<command name="requser">
<cvar name="reqchallege" value="false" />
</command>

Something like this should do:
var result =
doc.Elements("command")
.Single( x => x.Attribute("name").Value == name)
.Elements("cvar");
This will give you an IEnumerable<XElement> where each XElement represents a cvar in the specified command.
Note that if the specified command does not exist, the call to Single will cause an error. Likewise if the specified attribute is not found on the command.
EDIT As per your comments, you could do something along the lines of:
// Result will be an XElement,
// or null if the command with the specified attribute is not found
var result =
doc.Elements("command")
// Note the extra condition below
.SingleOrDefault( x => x.Attribute("name")!=null && x.Attribute("name").Value == name)
if(result!=null)
{
// results.Elements() gives IEnumerable<XElement>
foreach(var cvar in results.Elements("cvar"))
{
var cvarName = cvar.Attribute("name").Value;
var cvarValue = Convert.ToBoolean( cvar.Attribute("value").Value );
}
}

Related

How to change an attribute in a subnode in C#?

I want to change the attribute value (in this case "51") of the "NextMemberId" in an xml file that looks like this:
<File>
<MemberList>
<NextMemberId Value="51" />
<Member Id="1" ..... />
<Member Id="2" ..... />
</MemberList>
</File>
The following code works, but I would like to know if it can be done in a more direct way without having to run a foreach loop:
var memberId = 1;
var memberlist = Doc.DocumentElement.SelectSingleNode("MemberList");
foreach (XmlNode node in memberlist.ChildNodes)
{
var nodeElement = node as XmlElement;
if (nodeElement != null && nodeElement.Name == "NextMemberId")
{
nodeElement.SetAttribute("Value", memberId.ToString());
}
}
Thanks for any inspiration!
The correct path to get NextMemberId from File according to your sample XML would be :
var nodeElement = Doc.DocumentElement.SelectSingleNode("MemberList/NextMemberId");
nodeElement.SetAttribute("Value", memberId.ToString());
If there are multiple NextMemberId in your actual XML, and you need to filter by Value attribute, then you can add an XPath predicate similar to what the other answer suggested :
var nodeElement = Doc.DocumentElement.SelectSingleNode("MemberList/NextMemberId[#Value=51");
Notice that you can choose to keep or leave single-quotes around 51 depending on whether you want compare the Value as a string or a number, respectively.
You can select single node with specified attribute like this:
var nextMemberIdNode = Doc.DocumentElement.SelectSingleNode("NextMemberId[#Value='51']")

Obtaining values out of XML using Where clause

Good Evening! I have a multi-level XML file that I need to extract some data out of and then spit it back out in a CSV file. I have been struggling with the extracting the specific data that I need. I have gotten my XML query to the point of where I get the data returned that I need, but I am unsure of how to get the specific pieces of data I need out of the data in question.
What I need out of the below as my end result is the following...
For each person node, I need to identify the following pieces of data and record them to create the export file. This would run in a foreach loop over a large number of person records
var emailAddress - This is user + ‘#’ + registry where association == type “ 2”
I.E. testuser#domain.tld
var alias - The next part is the alias – That comes from within the person node and is user where association == type “1”
I.E. test1
var servername - The last part to be extracted is the servername which is inside that same person tag and is registry in association where type == “1”
I.E. servername.fqdn
I will then be using a StringBuilder to join all of these on every foreach to give the desired output.
I will not know the domain name or server name prior to the execution of the tools on the file. It could also vary within the file, hence why these are not simply hardcoded.
My current code returns the full association information below.
I greatly appreciate ANY assistance. I have learned a ton from here, and greatly appreciate all of you.
My XML file looks like
<eim server="servername" timestamp="1489506332830">
<domain name="domainname" description="domainanme">
<registry name="servername" type="2342.52342.123-caseIgnore" kind="1" description="Created by wizard.">
<alias type="Other">passwordMap=false</alias>
<alias type="DnsHostName">servername</alias>
</registry>
<registry name="domainanme" type="2342.52342.123-caseIgnore" kind="1" description="Created by wizard.">
<alias type="KerberosRealm">domainnamee</alias>
<alias type="Other">passwordMap=false</alias>
</registry>
<person name="tuser" description="Test User">
<association type="1" registry="servername.fqdn" user="test1"/>
<association type="2" registry="domainname.tld" user="testuser"/>
</person>
<person name="tuser2" description="Test User2">
<association type="1" registry="servername.fqdn" user="test2"/>
<association type="2" registry="domainname.tld" user="testuser2"/>
</person>
My current code is
XmlDocument doc = new XmlDocument();
doc.Load("c:\\temp\\eimexport.eiml");
List<string> testing = new List<string>();
XmlElement eimElement = doc.DocumentElement;
XmlNodeList eimNodes = eimElement.SelectNodes("/eim/domain/person");
foreach (XmlNode parseEimNode in eimNodes)
{
testing.Add(parseEimNode.InnerXml);
}
foreach (var s in testing)
{
Console.WriteLine(s);
}
Console.ReadLine();
Since you're using XmlDocument, filtering can be done using XPath predicate expression (expression in []) :
foreach (XmlNode parseEimNode in eimNodes)
{
var email = parseEimNode.SelectSingleNode("association[#type=2]");
var user = email.GetAttribute("user");
var domain = email.GetAttribute("registry");
var server = parseEimNode.SelectSingleNode("association[#type=1]");
var _alias = server.GetAttribute("user");
var servername = server.GetAttribute("registry");
// process all information gathered so far accordingly
}

How to delete certain root from xml file?

My '.xml' file looks this way:
<?xml version="1.0" encoding="utf-8"?>
<Requestes>
<Single_Request num="1">
<numRequest>1</numRequest>
<IDWork>1</IDWork>
<NumObject>1</NumObject>
<lvlPriority>Высокий</lvlPriority>
</Single_Request>
<Single_Request num="2">
<numRequest>2</numRequest>
<IDWork>2</IDWork>
<NumObject>2</NumObject>
<lvlPriority>Средний</lvlPriority>
</Single_Request>
<Periodic_Request num="1">
<numRequest>3</numRequest>
<IDWork>23</IDWork>
<pFrequency>23</pFrequency>
<lvlPriority>Низкий</lvlPriority>
<time_service>23</time_service>
<time_last_service>23</time_last_service>
<relative_time>23</relative_time>
</Periodic_Request>
</Requestes>
So I need to delete Single_Request with atribute value equal to sTxtBlock_numRequest.Text. I have tried to do it this way:
XDocument doc = XDocument.Load(FilePath);
IEnumerable<XElement> sRequest = doc.Root.Descendants("Single_Request").Where(
t => t.Attribute("num").Value =="sTxtBlock_numRequest.Text"); //I'm sure, that problem is here
sRequest.Remove();
doc.Save(FilePath);
Unfortunattly, nothing has happanned, don`t know how to solve the problem.
This is why , I am looking forward to your help.
You are comparing attribute value with string literal "sTxtBlock_numRequest.Text". You should pass value of textbox text instead:
doc.Root.Elements("Single_Request")
.Where(t => (string)t.Attribute("num") == sTxtBlock_numRequest.Text)
.Remove();
Note - it's better to use Elements when you are getting Single_Request elements of root, because Descendants will search whole tree, instead of looking at direct children only. Also you can call Remove() without saving query to local variable.

C# - Issues Selecting XML with Linq

I am trying to pull an event description in XML, but I am having trouble accessing the data.
I am trying to access the eventDetailsValue element.
Here is a sample of my code:
(version 1)
XElement doc = XElement.Parse(e.Result);
evtDesc = doc.Element("eventDetails").Element("eventDetails").Element("eventDetailsValue").Element("eventDetailsValue").Value;
(version2)
XElement doc = XElement.Parse(e.Result);
var xGood = from detaildoc in doc.Descendants("eventDetails")
from d in detaildoc.Elements("eventDetail").Elements("eventDetailsValue")
select d;
I have tried the following for a different element and it worked:
GeoLat = Convert.ToDouble(doc.Element("latitude").Value);
Here is a sample of the xml result (i removed the values for simplicity):
<event>
<longitude></longitude>
<latitude></latitude>
<category></category>
<dma></dma>
<activeAdvantage></activeAdvantage>
<seoUrl></seoUrl>
<assetID></assetID>
<eventID></eventID>
<eventDetailsPageUrl></eventDetailsPageUrl>
- <mediaTypes>
<mediaType></mediaType>
<mediaType></mediaType>
<mediaType></mediaType>
<mediaType></mediaType>
<mediaType></mediaType>
</mediaTypes>
<eventContactEmail />
<eventContactPhone />
<eventName></eventName>
<eventDate></eventDate>
<eventLocation></eventLocation>
<eventAddress></eventAddress>
<eventCity></eventCity>
<eventState></eventState>
<eventZip></eventZip>
<eventCountry></eventCountry>
<usatSanctioned></usatSanctioned>
<regOnline></regOnline>
<eventCloseDate></eventCloseDate>
<currencyCode></currencyCode>
<eventTypeID></eventTypeID>
<eventType></eventType>
<hasEventResults></hasEventResults>
<hasMetaResults></hasMetaResults>
<showMap></showMap>
<eventContactEmail />
<eventContactPhone />
<displayCloseDate></displayCloseDate>
<excludedFromEmailing></excludedFromEmailing>
<regOpensMessage />
<regFunnel></regFunnel>
<isValid></isValid>
<displayRegistration></displayRegistration>
- <channels>
- <channel>
<channelName></channelName>
<primaryChannel></primaryChannel>
</channel>
</channels>
- <eventDetails>
- <eventDetail>
<eventDetailsName></eventDetailsName>
<eventDetailsOrder></eventDetailsOrder>
<eventDetailsValue></eventDetailsValue>
</eventDetail>
- <eventDetail>
<eventDetailsName></eventDetailsName>
<eventDetailsOrder></eventDetailsOrder>
<eventDetailsValue></eventDetailsValue>
</eventDetail>
</eventDetails>
<eventDonationLinks />
<eventSanctions />
- <eventCategories>
- <eventCategory>
<categoryID></categoryID>
<categoryGroupCount></categoryGroupCount>
<categoryName></categoryName>
<categoryType></categoryType>
<categoryOrder></categoryOrder>
<numRegistered></numRegistered>
<maxRegistrations></maxRegistrations>
<percentFull></percentFull>
<displayDate></displayDate>
<closeDate></closeDate>
<actualCloseDate></actualCloseDate>
<isExpired></isExpired>
- <priceChanges>
- <priceChange>
<price></price>
<priceUntilDate></priceUntilDate>
</priceChange>
</priceChanges>
</eventCategory>
</eventCategories>
<eventUrl></eventUrl>
<eventContactUrl></eventContactUrl>
<eventImageUrl></eventImageUrl>
</event>
Any help would be appreciated!
The eventDetailsValue is in an array of eventDetail elements. So you need to diferentiate which element in the array you want. With this (and these LinqToXml extensions: http://searisen.com/xmllib/extensions.wiki) you can write it like this:
XElement doc = XElement.Parse(e.Result);
var details = doc.GetEnumerable("eventDetails/eventDetail", x => new
{
Name = x.Get("eventDetailsName", string.Empty),
Order = x.Get("eventDetailsOrder", string.Empty),
Value = x.Get("eventDetailsValue", string.Empty)
});
details is an IEnumerable<object> of Name's, Order's and the Value(s) you want. You can now loop through details and get the value(s) you want. I made Name, Order and Value all be strings, but by calling Get<type>("name", defaultValueByType) you can have them be other types instead.
You can loop through them like this:
foreach(var detail in details)
{
string value = detail.Value;
}
GetEnumerable is shorthand (in this case) for:
doc.Element("eventDetails").Elements("eventDetail").Select(x => new ...)
But it does null checking for you, which if your xml always produces the above xml, there would be no problems to do it long hand. And Get returns the proper value.
Note: Because this is a WindowsPhone7 project, you'll have to set a compiler flag of WindowsPhone7 so that the extensions compile without complaint (hopefully/I haven't tested it).
Try this query:
var xGood = from detaildoc in doc.Descendants("eventDetails")
select new
{
Value = detaildoc.Elements("eventDetail").Elements("eventDetailsValue").Value
};

Detecting structural differences in XML using Linq and XElement

I'm trying to audit some XML that is used in a bespoke piece of software. Im able to detect changes in identical structures using 'XNode.DeepEquals' and then adding an extra attribute to the elements that have changed so I can highlight them.
My problem is that, when the structure does change this methodology fails. ( I'm enumerating over both XElements at the same time performing a DeepEquals, if they are not equal - recursively calling the same method to filter out where the exact changes occurr )
Obviously this now falls apart when I'm enumerating and the nodes being compared are not the same. See Below Sample:
Before
<?xml version="1.0" encoding="utf-16"?>
<Prices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Price default="true">
<Expression operator="Addition">
<LeftOperand>
<AttributeValue field="ccx_bandwidth" />
</LeftOperand>
<RightOperand>
<Constant value="10" type="Integer" />
</RightOperand>
</Expression>
</Price>
<Price default="false">
<Expression operator="Addition">
<LeftOperand>
<AttributeValue field="ccx_bandwidth" />
</LeftOperand>
<RightOperand>
<Constant value="99" type="Integer" />
</RightOperand>
</Expression>
</Price>
<RollupChildren />
After
<?xml version="1.0" encoding="utf-16"?>
<Prices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Price default="true">
<Expression operator="Addition">
<LeftOperand>
<AttributeValue field="ccx_bandwidth" />
</LeftOperand>
<RightOperand>
<Constant value="10" type="Integer" />
</RightOperand>
</Expression>
</Price>
<RollupChildren />
So you can see that the latter Price Node has been removed and I need to show this change.
At the moment I have access to both pieces of xml and modify them on load of the audit application with an 'auditchanged' attribute which in my silverlight app i bind the background too with a converter.
I'd been playing around with Linq to Xml and looking at joining the two XElements in a query but wasn't sure how to proceed.
Ideally what I would like to do is merge the two XElements together but add a seperate attribute depending on if it's added or removed which i can then bind to with a converter to say highlight in red or green appropriately.
Does anyone have any bright ideas on this one? ( I'd been looking at XmlDiff however I can't use that in Silverlight, I don't think? )
I have a generic differ class in the codeblocks library http://codeblocks.codeplex.com
Loading your XML documents and treating each document as an IEnumerable (flattened XML tree) should allow you to use the differ as shown here: http://codeblocks.codeplex.com/wikipage?title=Differ%20Sample&referringTitle=Home
Here's the source code for differ.cs: http://codeblocks.codeplex.com/SourceControl/changeset/view/96119#1887406
Diff prototype is:
static IEnumerable<DiffEntry> Diff(IEnumerable<T> oldData, IEnumerable<T> newData, Comparison<T> identity, Comparison<T> different)
The important part here is the descendants query. It turns every element in the first doc in a list of its ancestors, where every item contains the name of the element and it's index among its siblings of the same name. I think this can be somehow used for joining, though I have no idea how to do full outer join with linq. So instead i just use these lists to find elements in the second document, and then depending on the result, probably mark it as either deleted or changed.
var doc = XDocument.Load(in_A);
var doc2 = XDocument.Load(in_B);
var descendants = doc.Descendants().Select(d =>
d.AncestorsAndSelf().Reverse().Select(el =>
new {idx = el.ElementsBeforeSelf(el.Name).Count(), el, name = el.Name}).ToList());
foreach (var list in descendants) {
XContainer el2 = doc2;
var el = list.Last().el;
foreach (var item in list) {
if (el2 == null) break;
el2 = el2.Elements(item.name).Skip(item.idx).FirstOrDefault();
}
string changed = "";
if (el2 == null) changed += " deleted";
else {
var el2e = el2 as XElement;
if (el2e.Attributes().Select(a => new { a.Name, a.Value })
.Except(el.Attributes().Select(a => new { a.Name, a.Value })).Count() > 0) {
changed += " attributes";
}
if (!el2e.HasElements && el2e.Value != el.Value) {
changed += " value";
}
el2e.SetAttributeValue("found", "found");
}
if (changed != "") el.SetAttributeValue("changed", changed.Trim());
}
doc.Save(out_A);
doc2.Save(out_B);

Categories