How to parse nested elements using LINQ to XML - c#

I have an XML file with multiple checkItem elements. I need to save each checkItem element into a database. I'm having a difficult time getting exactly what I need using the query below.
<checkItem>
<checkItemType>check</checkItemType>
<checkAmount>195000</checkAmount>
<nonMICRCheckData>
<legalAmount>195000</legalAmount>
<issueDate>2010-04-30</issueDate>
<other>PAY VAL 20 CHARACTER</other>
</nonMICRCheckData>
<postingInfo>
<date>2013-05-01</date>
<RT>10108929</RT>
<accountNumber>111111111</accountNumber>
<seqNum>11111111</seqNum>
<trancode>111111</trancode>
<amount>195000</amount>
<serialNumber>1111111</serialNumber>
</postingInfo>
<totalImageViewsDelivered>2</totalImageViewsDelivered>
<imageView>
<imageIndicator>Actual Item Image Present</imageIndicator>
<imageViewInfo>
<Format>
<Baseline>TIF</Baseline>
</Format>
<Compression>
<Baseline>CCITT</Baseline>
</Compression>
<ViewSide>Front</ViewSide>
<imageViewLocator>
<imageRefKey>201305010090085000316000085703_Front.TIF</imageRefKey>
<imageFileLocator>IFTDISB20130625132900M041.zip</imageFileLocator>
</imageViewLocator>
</imageViewInfo>
<imageViewInfo>
<Format>
<Baseline>TIF</Baseline>
</Format>
<Compression>
<Baseline>CCITT</Baseline>
</Compression>
<ViewSide>Rear</ViewSide>
<imageViewLocator>
<imageRefKey>201305010090085000316000085703_Rear.TIF</imageRefKey>
<imageFileLocator>IFTDISB20130625132900M041.zip</imageFileLocator>
</imageViewLocator>
</imageViewInfo>
</imageView>
</checkItem>
Here is the query I've been working with. I've tried several different ways with no luck. Without the use of .Concat, I cannot get the other elements; however, using .Concat does not allow me to get all values in a manageable format. I need to separate the Front and Rear imageViews based on the ViewSide value, and only need the imageRefKey and imageFileLocator values from the imageView element. Can anyone point me in the right direction?
var query = doc.Descendants("checkItem")
//.Concat(doc.Descendants("postingInfo"))
//.Concat(doc.Descendants("imageViewLocator"))//.Where(x => (string)x.Element("ViewSide") == "Front"))
//.Concat(doc.Descendants("imageViewInfo").Where(x => (string)x.Element("ViewSide") == "Rear"))
.Select(x => new {
CheckAmount = (string) x.Element("checkAmount"),
ImageRefKey = (string) x.Element("imageRefKey"),
PostingDate = (string) x.Element("dare"),
//FrontViewSide = (string) x.Element("ViewSide"),
//RearViewSide = (string) x.Element("BViewSide")
});

You can easily get nested elements of any XElement by just calling the Elements() method of that instance, then calling Select() on that collection, to created a nested collection of an anonymous type in your main anonymous type.
var query = doc.Elements("checkItem")
.Select( x =>
new
{
CheckAmount = (string) x.Element("checkAmount"),
ImageRefKey = (string) x.Element("imageRefKey"),
PostingDate = (string) x.Element("dare"),
ImageViews = x.Element("ImageView").Elements("ImageViewInfo")
.Select(iv=>
new
{
Format = iv.Element("Format").Element("Baseline").Value
// more parsing here
}
}

Related

How to check the attribute value string from linq in c#

I have a string value in column of database table :-
<Attributes><ProductAttribute ID="322"><ProductAttributeValue><Value>782</Value></ProductAttributeValue></ProductAttribute></Attributes>
There are multiple column with the same format.
Now I need to check ProductAttributeValue and get the data from linQ
currently I am doing by
var id = 782
var string = "<Attributes><ProductAttribute ID="322"><ProductAttributeValue><Value>" + id + "</Value></ProductAttributeValue></ProductAttribute></Attributes>";
var value = sometable.where(x => x.valueString == string).FirstOrDefault();
Is there any way to get direct from linq?
This can be done using LINQ to XML.
using System.Linq;
using System.Xml.Linq;
...
var id = "Value To Find";
var str = "<Attributes><ProductAttribute ID=\"322\"><ProductAttributeValue><Value>" + id + "</Value></ProductAttributeValue></ProductAttribute></Attributes>";
var xml = XDocument.Parse(str);
var val = xml
.Element("Attributes")
.Element("ProductAttribute")
.Element("ProductAttributeValue")
.Element("Value")?.Value;
Since there is only 1 of each element in the xml data structure you can use Element, if there are multiple you can use Elements and operate on them as a collection.
You can filter elements like usual using Where and other extension methods.
var valToFind = "722";
var val = xml
.Element("Attributes")
.Elements("ProductAttribute")
.Where(node => node
.Element("ProductAttributeValue")
?.Element("Value")
?.Value == valToFind
)
.FirstOrDefault();
The above will find the ProductAttribute node that has a ProductAttributeValue Value equal to the valToFind. valToFind is a string for quick comparison against the xml string value.

Sitefinity: Dynamic Content query optimization with field values

I will attempt to be as specific as possible. So we are using Sitefinity 8.1.5800, I have a couple dynamic content modules named ReleaseNotes and ReleaseNoteItems. ReleaseNotes has some fields but no reference to ReleaseNoteItems.
Release Note Items has fields and a related data field to ReleaseNotes.
So I can query all ReleaseNoteItems as dynamic content pretty quickly less than a second.
I then use these objects provided by sitefinity and map them to a C# object so I can use strong type. This mapping process is taking almost a minute and using over 600 queries for only 322 items (N+1).
In Short: I need to get all sitefinity objects and Map them to a usable c# object quicker than I currently am.
The method for fetching the dynamic content items (takes milliseconds):
private IList<DynamicContent> GetAllLiveReleaseNoteItemsByReleaseNoteParentId(Guid releaseNoteParentId)
{
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(String.Empty);
Type releasenoteitemType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.ReleaseNoteItems.Releasenoteitem");
string releaseNoteParentTypeString = "Telerik.Sitefinity.DynamicTypes.Model.ReleaseNotes.Releasenote";
var provider = dynamicModuleManager.Provider as OpenAccessDynamicModuleProvider;
int? totalCount = 0;
var cultureName = "en";
Thread.CurrentThread.CurrentUICulture = new CultureInfo(cultureName);
Type releasenoteType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.ReleaseNotes.Releasenote");
// This is how we get the releasenote items through filtering
DynamicContent myCurrentItem = dynamicModuleManager.GetDataItem(releasenoteType, releaseNoteParentId);
var myMasterParent =
dynamicModuleManager.Lifecycle.GetMaster(myCurrentItem) as DynamicContent;
var relatingItems = provider.GetRelatedItems(
releaseNoteParentTypeString,
"OpenAccessProvider",
myMasterParent.Id,
string.Empty,
releasenoteitemType,
ContentLifecycleStatus.Live,
string.Empty,
string.Empty,
null,
null,
ref totalCount,
RelationDirection.Parent).OfType<DynamicContent>();
IList<DynamicContent> allReleaseNoteItems = relatingItems.ToList();
return allReleaseNoteItems;
}
This is the method that takes almost a minute that is mapping sitefinity object to C# object:
public IList<ReleaseNoteItemModel> GetReleaseNoteItemsByReleaseNoteParent(ReleaseNoteModel releaseNoteItemParent)
{
return GetAllLiveReleaseNoteItemsByReleaseNoteParentId(releaseNoteItemParent.Id).Select(rn => new ReleaseNoteItemModel
{
Id = rn.Id,
Added = rn.GetValue("Added") is bool ? (bool)rn.GetValue("Added") : false,
BugId = rn.GetValue<string>("bug_id"),
BugStatus = rn.GetValue<Lstring>("bugStatus"),
Category = rn.GetValue<Lstring>("category"),
Component = rn.GetValue<Lstring>("component"),
#Content = rn.GetValue<Lstring>("content"),
Criticality = rn.GetValue<Lstring>("criticality"),
Customer = rn.GetValue<string>("customer"),
Date = rn.GetValue<DateTime?>("date"),
Grouped = rn.GetValue<string>("grouped"),
Override = rn.GetValue<string>("override"),
Patch_Num = rn.GetValue<string>("patch_num"),
PublishedDate = rn.PublicationDate,
Risk = rn.GetValue<Lstring>("risk"),
Title = rn.GetValue<string>("Title"),
Summary = rn.GetValue<Lstring>("summary"),
Prod_Name = rn.GetValue<Lstring>("prod_name"),
ReleaseNoteParent = releaseNoteItemParent,
McProductId = GetMcProductId(rn.GetRelatedItems("McProducts").Cast<DynamicContent>()),
}).ToList();
}
Is there any way to optimize this all into one query or a better way of doing this? Taking almost a minute to map this objects is too long for what we need to do with them.
If there is no way we will have to cache the items or make a SQL query. I would rather not do caching or SQL query if I do not have to.
Thank you in advance for any and all help you can provide, I am new to posting questions on stackoverflow so if you need any additional data please let me know.
Is there a reason why you are doing a .ToList() for the items? Is it possible for you to avoid that. In my opinion, most of the time(of the 1 minute) is taken to convert all your items into a list. Conversion from Sitefinity object to C# object is not the culprit here.
Look Arno's answer here: https://plus.google.com/u/0/112295105425490148444/posts/QrsVtxj1sCB?cfem=1
You can use the "Content links manager" to query dynamic modules relationships (both by parent -ParentItemId- or by child -ChildItemId-) much faster:
var providerName = String.Empty;
var parentTitle = "Parent";
var relatedTitle = "RelatedItem3";
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(providerName);
Type parentType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.ParentModules.ParentModule");
Type relatedType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.RelatedModules.RelatedModule");
ContentLinksManager contentLinksManager = ContentLinksManager.GetManager();
// get the live version of all parent items
var parentItems = dynamicModuleManager.GetDataItems(parentType).Where(i => i.GetValue<string>("Title").Contains(parentTitle) && i.Status == ContentLifecycleStatus.Live && i.Visible);
// get the ids of the related items.
// We use the OriginalContentId property since we work with the live vesrions of the dynamic modules
var parentItemIds = parentItems.Select(i => i.OriginalContentId).ToList();
// get the live versions of all the schedules items
var relatedItems = dynamicModuleManager.GetDataItems(relatedType).Where(i => i.Status == ContentLifecycleStatus.Live && i.Visible && i.GetValue<string>("Title").Contains(relatedTitle));
// get the content links
var contentLinks = contentLinksManager.GetContentLinks().Where(cl => cl.ParentItemType == parentType.FullName && cl.ComponentPropertyName == "RelatedField" && parentItemIds.Contains(cl.ParentItemId) && cl.AvailableForLive);
// get the IDs of the desired parent items
var filteredParentItemIds = contentLinks.Join<ContentLink, DynamicContent, Guid, Guid>(relatedItems, (cl) => cl.ChildItemId, (i) => i.OriginalContentId, (cl, i) => cl.ParentItemId).Distinct();
// get the desired parent items by the filtered IDs
var filteredParentItems = parentItems.Where(i => filteredParentItemIds.Contains(i.OriginalContentId)).ToList();
I would imagine that every release note item under a single release note would be related to the same product wouldn't it?
If so, do you need to do the GetMcProductId method for every item?

Sort List by date values

I have the following list -
List<string> finalMessageContent
where
finalMessageContent[0] = "<div class="mHr" id="mFID">
<div id="postedDate">11/12/2015 11:12:16</div>
</div>" // etc etc
I am trying to sort the list by a particular value located in the entires - postedDate tag.
Firstly I have create an new object and then serialized it to make the html elements able to be parsed -
string[][] newfinalMessageContent = finalMessageContent.Select(x => new string[] { x }).ToArray();
string json = JsonConvert.SerializeObject(newfinalMessageContent);
JArray markerData = JArray.Parse(json);
And then used Linq to try and sort using OrderByDescending -
var items = markerData.OrderByDescending(x => x["postedDate"].ToString()).ToList();
However this is failing when trying to parse the entry with -
Accessed JArray values with invalid key value: "postedDate". Array position index expected.
Perhaps linq is not the way to go here however it seemed like the most optimised, where am I going wrong?
First, i would not use string methods, regex or a JSON-parser to parse HTML. I would use HtmlAgilityPack. Then you could provide such a method:
private static DateTime? ExtractPostedDate(string inputHtml, string controlID = "postedDate")
{
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(inputHtml);
HtmlNode div = doc.GetElementbyId(controlID);
DateTime? result = null;
DateTime value;
if (div != null && DateTime.TryParse(div.InnerText.Trim(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out value))
result = value;
return result;
}
and following LINQ query:
finalMessageContent = finalMessageContent
.Select(s => new { String = s, Date = ExtractPostedDate(s) })
.Where(x => x.Date.HasValue)
.OrderByDescending(x => x.Date.Value)
.Select(x => x.String)
.ToList();
Don't know if I get your question right.
But did you know that you can parse HTML with XPath?
foreach (var row in doc.DocumentNode.SelectNodes("//div[#id="postedDate"]"))
{
Console.WriteLine(row.InnerText);
}
this is just an example from the top of my head you might have to double-check the XPath query depending on your document. You can also consider converting it to array or parsing the date and do other transformations with it.
Like I said this is just from the top of my head. Or if the html is not so compley consider to extract the dates with an RegEx but this would be a topic for another question.
HTH
Json Serializer serializes JSON typed strings. Example here to json
To parse HTML I suggest using HtmlAgility https://htmlagilitypack.codeplex.com/
Like this:
HtmlAgilityPack.HtmlDocument htmlparsed = new HtmlAgilityPack.HtmlDocument();
htmlParsed.LoadHtml(finalMessageContent[0]);
List<HtmlNode> OrderedDivs = htmlParsed.DocumentNode.Descendants("div").
Where(a => a.Attributes.Any(af => af.Value == "postedDate")).
OrderByDescending(d => DateTime.Parse(d.InnerText)); //unsafe parsing

Linq-to-xml to get child nodes

I am having trouble identifying how to use linq-to-xml to extract total price and individual prices from the xml below (e.g I want to get the fare price and also sum of all prices). Any help would be much appreciated especially with using the method syntax of linq-to-xml
I use the following code to get the data loaded into an xDocument and work with the xmlResponse object to parse the response.
var xmlResponse = from element in xdoc.Descendants()
select element;
and get data like
xmlResponse.SingleOrDefault(x => x.Name.LocalName == "Registration")
Below is a subset of thwe xml response :-
<StateList>
<State>
<SourceJobID>J999999999999</SourceJobID>
<TargetJobState>Complete</TargetJobState>
<TargetJobID>11111111</TargetJobID>
<TargetSystem>TESTSYSTEM</TargetSystem>
<VehicleDetails>
<Registration>TESTREGISRATION</Registration>
<Plate>11111111111</Plate>
<CO2Rating>160</CO2Rating>
<Badge>1111111</Badge>
<Description>TEST DESCRIPTION</Description>
</VehicleDetails>
<CompleteDetails>
<CompletedOn>2015-09-15T13:39:11+01:00</CompletedOn>
<JobDistance>0</JobDistance>
<WaitingTime />
<CO2Usage>0</CO2Usage>
<ChargeList>
<Charge>
<Name>Airport Pickup</Name>
<Currency>GBP</Currency>
<Price>0.00</Price>
</Charge>
<Charge>
<Name>Fare</Name>
<Currency>GBP</Currency>
<Price>0.00</Price>
</Charge>
<Charge>
<Name>Extra Stops</Name>
<Currency>GBP</Currency>
<Price>0.00</Price>
</Charge>
</ChargeList>
</CompleteDetails>
</State>
Assuming you only have a single state like in your example, you could do something like the following:
decimal fare = decimal.Parse(xml.Descendants("Charge").Single(x => x.Element("Name").Value == "Fare").Element("Price").Value);
decimal total = xml.Descendants("Charge").Sum(x => decimal.Parse(x.Element("Price").Value));
Although if you have a series of elements in your list you will have to modify that.
EDIT: If, as you say in the comments, you would like to sum only certain charges:
// Valid names of charges to sum.
string[] names = { "Airport Pickup", "Fare" };
// Iterate over every state.
foreach (var state in xml.Descendants("State"))
{
// Get all charge elements in the current state whose names are contained in 'names' - then convert their 'Price' element to decimal and sum them.
decimal stateTotal = state.Descendants("Charge").Where(x => names.Contains(x.Element("Name").Value)).Sum(x => decimal.Parse(x.Element("Price").Value));
}
if(doc.Descendants("Charge").Any())
{
var FarePrice = doc.Descendants("Charge")
.Where(x => x.Descendants("Name").First().Value.Equals("Fare")).First().Element("Price").Value;
var Sum = doc.Descendants("Charge")
.Select(x => Convert.ToDouble(x.Descendants("Price").First().Value))
.Sum();
Console.WriteLine("Fare price:{0} Sum:{1}",FarePrice,Sum);
}
It returns 35 as sum for 10 and 25 inputs.
Fiddle here : https://dotnetfiddle.net/cuHXBn

How to write XPath expression to select node name from its value

I'm trying to write an XPath expression to select the name of a node from its value in "qualities" and then select in "qualityNames" the value inside node whose name has previously captured.
E.g. In "qualities" - got value "4", take name "rarity3" then in "qualityNames" I got node named "rarity3" and take value "amazingrarity"
<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>
I'm doing this in C# (It's a MVC App) and I'd prefer to use XPath because I'm indexing the XML and I haven't found a fastest way to query in-memory technique (this XML file has ~3MB and I'm using IndexingXPathNavigator).
Use the local-name() and text() functions + predicates. For value "4" it will be
//qualityNames/*[local-name()=local-name(//qualities/*[text() = '4'])]
Tested with http://www.xpathtester.com
Sounds like you want to create a dictionary of key/value pairs (assuming the node names are only needed to find matches and aren't important to your code).
If so, you can use the following:
var doc = XElement.Parse(#"<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>");
var query = from quality in doc.XPathSelectElements("qualities/*")
join qualityName in doc.XPathSelectElements("qualityNames/*")
on quality.Name equals qualityName.Name
select new { Key = quality.Value, Value = qualityName.Value };
var qualities = query.ToDictionary(a => a.Key, a => a.Value);
var quality3 = qualities["3"];
// quality3 == "Vintage"
var quality4 = qualities["4"];
// quality4 == "amazingrarity"
EDIT: example of how to cache this dictionary
// add reference to System.Web dll
public Dictionary<string, string> GetQualities()
{
// assuming this code is in a controller
var qualities = this.HttpContext.Cache["qualities"] as Dictionary<string, string>;
if (qualities == null)
{
// LoadQualitiesFromXml() is the code above
qualities = LoadQualitiesFromXml();
this.HttpContext.Cache["qualities"] = qualities;
}
return qualities;
}
I think this is what you asked
var rarity3ValueInQualities = xml.SelectSingleNode("/result/qualities/rarity3").InnerText;
var rarity3ValueInqualityNames = xml.SelectSingleNode("/result/qualityNames/rarity3").InnerText;

Categories