C# - Issues Selecting XML with Linq - c#

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
};

Related

How can i get a collection of child elements - query returns null using Xdocument

Can anybody please tell me why this Xdocument query is returning null when there are elements / attributes that i'm trying to grab.
I'm trying to get a collection of the <version> elements so i can read the attributes of them. Example XML:
<dmodule>
<idstatus>
<dmaddres>
<dmc>Some DMC</dmc>
<dmtitle><techname>My techname</techname><infoname>My infoname</infoname></dmtitle>
<issno issno="004" type="revised">
<issdate year="2016" month="11" day="30"></dmaddres>
<status>
<security class="2">
<rpc>RPC1</rpc>
<orig>ORIG1</orig>
<applic>
<model model="2093">
**<version version="BASE"></version>
<version version="RNWB"></version>**</model></applic>
<techstd>
<autandtp>
<authblk></authblk>
<tpbase>-</tpbase></autandtp>
<authex></authex>
<notes></notes></techstd>
<qa>
<firstver type="tabtop"></qa></status>
</idstatus>
<dmodule>
And this is how i'm trying to get the <version> elements:
XDocument doc = XDocument.Load(sgmlReader);
List<string> applicabilityList = null;
//this doesn't work
//var applics = doc.XPathSelectElements("dmodule/idstatus/status/applic/model/version");
//nor does this
var applics = doc.Descendants("idstatus").Descendants("applic").Elements("version");
foreach (var applic in applics)
{
string applicVersion = applic.Attribute("version").ToString();
applicabilityList.Add(applicVersion);
}
return applicabilityList;
Either query as shown above returns no results. Cleary a silly mistake in my query but i'm out of practice.
That's because you are missing the model element.
...
<applic>
<model model="2093">
<version version="BASE"></
...
If all you are interested are the version elements you can simply do:
var versions = doc.Descendants("version");
This is the working code
var applics = doc.Descendants("dmodule")
.Descendants("idstatus")
.Descendants("status")
.Descendants("security")
.Descendants("applic")
.Descendants("model")
.Elements("version");

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']")

C# - Linq to XML - Exclude elements from query

I have this XML file:
<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
<MyXml>
All 3 elements that are called 'MandatoryElementX' will always appear in the file. The elements called 'CustomElementX' are unknown. These can be added or removed freely by a user and have any name.
What I need is to fetch all the elements that are not MandatoryElements. So for the file above I would want this result:
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
I don't know what the names of the custom elements may be, only the names of the 3 MandatoryElements, so the query needs to somehow exclude these 3.
Edit:
Even though this was answered, I want to clarify the question. Here is an actual file:
<Partner>
<!--Mandatory elements-->
<Name>ALU FAT</Name>
<InterfaceName>Account Lookup</InterfaceName>
<RequestFolder>C:\Documents and Settings\user1\Desktop\Requests\ALURequests</RequestFolder>
<ResponseFolder>C:\Documents and Settings\user1\Desktop\Responses</ResponseFolder>
<ArchiveMessages>Yes</ArchiveMessages>
<ArchiveFolder>C:\Documents and Settings\user1\Desktop\Archive</ArchiveFolder>
<Priority>1</Priority>
<!--Custom elements - these can be anything-->
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
</Partner>
The result here would be:
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
You can define a list of mandatory names and use LINQ to XML to filter:
var mandatoryElements = new List<string>() {
"MandatoryElement1",
"MandatoryElement2",
"MandatoryElement3"
};
var result = xDoc.Root.Descendants()
.Where(x => !mandatoryElements.Contains(x.Name.LocalName));
Do you have created this xml or do you get it by another person/application?
If it's yours I would advise you not to number it. You can do something like
<MyXml>
<MandatoryElement id="1">value<\MandatoryElement>
<MandatoryElement id="2">value<\MandatoryElement>
<MandatoryElement id="3">value<\MandatoryElement>
<CustomElement id="1">value<\CustomElement>
<CustomElement id="2">value<\CustomElement>
<MyXml>
In the LINQ-Statement you don't need the List then.
Your question shows improperly formatted XML but I am assuming that is a typo and the real Xml can be loaded into the XDocument class.
Try this...
string xml = #"<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
</MyXml> ";
System.Xml.Linq.XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Root.Descendants()
.Where(x => !x.Name.LocalName.StartsWith("MandatoryElement"));
lets say TestXMLFile.xml will contain your xml,
XElement doc2 = XElement.Load(Server.MapPath("TestXMLFile.xml"));
List<XElement> _list = doc2.Elements().ToList();
List<XElement> _list2 = new List<XElement>();
foreach (XElement x in _list)
{
if (!x.Name.LocalName.StartsWith("Mandatory"))
{
_list2.Add(x);
}
}
foreach (XElement y in _list2)
{
_list.Remove(y);
}

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);

Load repetitively-named XML nodes using Linq [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 );
}
}

Categories