Simple Linq to XML Query Doesn't Work - c#

I nominate me for village idiot.
Why doesn't this work:
foreach (XElement clientField in _clientXml.Descendants("row").Descendants())
{
var newFieldName =
from sourceField in _sourceEntries.Descendants("Field")
where (string)sourceField.Attribute("n") == (string)clientField.Attribute("n")
select new
{
FieldName = ((string) sourceField.Attribute("n")),
AcordRef = ((string) sourceField.Attribute("m"))
};
foreach (var element in newFieldName)
{
Console.WriteLine("Field Name: {0}",
element.FieldName, element.AcordRef);
}
}
My source XML files are loaded with XElement.Load(myFileName). In debug, clientField has an attribute n="Policy Number". The first element of _sourceEntries.Descendants("Field") also has an attribute n="Policy Number". Indeed, each element in _clientXml.Descendants("row").Descendants() has a matching row in _sourceEntries.Descendants("Field"). And, I know just enough to know that the select is lazy, so in debug I look at the Console.WriteLine block. No matter what I've tried, newFieldName is an empty set.
Just in case, here's the first element of the client file:
<Column_0 n="Policy Number">ABC000123</Column_0>
And, here's the fist element of the _sourceEntries collection:
<Field n="Policy Number" c="1" l="" s="" cd="" m="1805" f="" />
I know it's going to be something simple, but I just don't see what I'm doing wrong.
Thanks.
Randy

This accomplished what I ultimately needed to do:
foreach (var clientField in _clientXml.Descendants("row").Descendants())
{
foreach (var acordMapRef in
from sourceEntry in _clientTemplate.Descendants("SourceEntries").Descendants("Field")
where (string) clientField.Attribute("n") == (string) sourceEntry.Attribute("n")
from acordMapRef in _clientTemplate.Descendants("Acord").Descendants("Field")
where (string) sourceEntry.Attribute("m") == (string) acordMapRef.Attribute("id")
select acordMapRef)
{
clientField.Attribute("n").Value = (string) acordMapRef.Attribute("n");
}
}
But, it's surely a candidate for ugliest code of the month. One thing I noticed in fooling around is that elements in an XElement tree don't seem to match to XElements in an IEnumerable collection. You might notice in the original code, above, I had an object _sourceEntries. This was a collection derived from _clientTemplate.Descendants("SourcEntries").Descendants("Field"). I would have thought that the two forms were essentially equivalent for my purposes, but apparently not. I'd appreciate somebody commenting on this issue.
Thanks folks!

Try changing:
where (string)sourceField.Attribute("n") == (string)clientField.Attribute("n")
To:
where sourceField.Attribute("n").Value == clientField.Attribute("n").Value

Related

Loop to iterate up parent nodes until it finds specific tag

What I'm doing is finding a specific value within an XML document and then I want to iterate upwards through each parent until it finds the parent with a specific tag name.
List<XElement> fieldReferences = new List<XElement>();
fieldReferences.AddRange(element.XPathSelectElements(string.Format("descendant::nameValue[#idref='{0}']", fieldName)));
fieldReferences.AddRange(element.XPathSelectElements(string.Format("descendant::nameValue[#value='{0}']", fieldName)));
string parentIterator = ".Parent";
string attributeValue = ".Attribute('id').Value";
string parentElementName = ".Name";
foreach (var value in fieldReferences)
{
var parentField = string.Format("{0}{1}", parentIterator, parentElementName);
while (value + parentField != "private" || value + parentField != "public")
{
// keep appending .Parent until it finds what it needs
}
//var parentField = value.Parent.Parent.Parent.Parent.Attribute("id").Value;
outputFields.Add(parentField, name.FirstOrDefault());
}
The issue that I'm having is that parentField will always be evaluated as a string so it'll never actually check the .Parent.Name property of value.
I don't work often with C# so I'm sure there's a much easier way to do this so my question is: How can I get my parentField string to evaluate the way I want OR how can I do this in a different way to achieve the same end result?
EDIT: Here's what my xml looks like. The XPAthSelectElement gets the nameValue element and I want to iterate through each parent element until I find the private tag
<private id="I need to iterate upwards through each parent element until I find this one">
<value>
<request>
<nameValues>
<nameValue idref="I found this" />
<nameValue value=""/>
</nameValues>
</request>
</value>
</private>
So you don't actually need to do this many string operations to then go crazy with XPath. Once you found your child target element, you can just use the Parent property on the XElement iteratively until you find the XElement with a private/public tag. So that gives us this code:
foreach (XElement element in fieldReferences)
{
XElement currentElement = element;
while (currentElement.Parent != null)
{
currentElement = currentElement.Parent;
if (currentElement.Name == "private" || currentElement.Name == "public") {
outputFields.Add(currentElement, /* not sure what you want here */);
break;
}
}
}
So currentElement would start out as the element with the nameValue tag from your example. In the while loop, each iteration currentElement changes to its parent node until there is no more parent or currentElement has become a private or a public tag. If the latter is the case, it gets appended to your result.
You can use the XElement.Ancestors function to get a list of all the elements that contain the nodes you found, then just select the ones you want using LINQ. No need for any loops.
var results = fieldReferences.Select(element => element.Ancestors()
.Where(ancestor => ancestor.Name == "public" ||
ancestor.Name == "private")
.FirstOrDefault());
Note that this will go all the way up the tree, and may have issues if there are multiple matching ancestors (or no matching ancestor). Let me know if that is a problem for you, and what result you want in that case, and I can make adjustments.

Checking if treeview contains an item

This problem seems simple enough. I have a treeview, let's call it MyTreeView, populated with all of the drive letters, so the treeview looks like this:
A:\
C:\
D:\
F:\
How do I check if the treeview contains a specific item? How does the treeview identify its items?
I have created a MessageBox to show MyTreeView.Items.GetItemAt(1), and it identifies item 1 as:
"System.Windows.Controls.TreeViewItem Header:C:\ Items.Count:1"
Try the easiest thing first, which obviously doesn't work:
if (MyTreeView.Items.Contains(#"C:\")
{
MessageBox.Show(#"Tree contains C:\");
}
The next easiest thing would be to try making a TreeViewItem that looks similar to what I want, which also doesn't work:
TreeViewItem myItem = new TreeViewItem();
myItem.Header = #"C:\";
if (MyTreeView.Items.Contains(myItem)
{
MessageBox.Show("Tree contains " + myItem.ToString());
}
Just to make sure I had the fundamental concept right, I tried some circular logic, which actually does work:
var myItem = MyTreeView.Items.GetItemAt(1);
if (MyTreeView.Items.Contains(myItem)
{
MessageBox.Show("Tree contains " + myItem.ToString());
}
Which outputs:
"Tree contains System.Windows.Controls.TreeViewItem Header:C:\ Items.Count:1"
What am I doing wrong? How do I check if my tree contains something like "C:\" ?
edit:
The code for building the tree is this:
(basically a copy and paste from the internet)
foreach (string myString in Directory.GetLogicalDrives())
{
TreeViewItem item = new TreeViewItem();
item.Header = myString;
item.Tag = myString;
item.FontWeight = FontWeights.Normal;
item.Items.Add(dummyNode); // this is (object)dummyNode = null
item.Expanded += new RoutedEventHandler(DWGFolder_Expanded);
item.Selected += new RoutedEventHandler(DWGFolder_Selected);
// the Expanded event is very similar,
// subitem.Header is the folder name (Testing),
// while subitem.Tag is the full path (C:\Testing)
MyTreeView.Items.Add(item);
}
So basically I'm trying to match TreeViewItem objects.
I believe .Contains() would check for the value by reference since it isn't a simple string object. This requires you to iterate through each of the items until you retrieve the item which matches the header.
LINQ Example
if (MyTreeView.Items.Cast<TreeViewItem>().Any(item => item.Header.ToString() == #"C:\"))
{
MessageBox.Show(#"Tree contains C:\");
}
Contains looks for the exact same instance inside the collection. If you don't have the object you want to check already, you can't use Contains.
But you can use some basic LINQ query... Add the LINQ namespace to your class:
using System.Linq;
If your items are indeed just Strings, then use this query (EDIT - Though, in case they're just Strings, Contains should work, since their equality comparers don't behave like those of regular reference types, but compare by value):
if (MyTreeView.Items.Cast<string>().Any(s => s == #"C:\"))
{
// Do stuff
}
If your items are TreeViewItems, you can use this one:
if (MyTreeView.Items.Cast<TreeViewItem>().Any(i => i.Header.ToString() == #"C:\"))
{
// Do stuff
}
But your items could be any class we don't know, or your header binding could change... Without knowing how you're adding the items to the TreeView, it's hard to give you the best alternative.
EDIT - Keep in mind that this will only search in the first level of the tree. If the item you're looking for is placed somewhere deeper, you'll have to do a recursive search. At that point, maybe just keeping the values stored somewhere from the start would be better.

Finding an element with Linq and lambda expressions: but doesn't work

Have a look to the following example:
The first solution, using the foreach, works pretty well and easily. But I was trying to write it using Linq and I could not achieve this result. I made some attempts but no one succeeded.
I expect to find just one element.
The problem is not at runtime: I don't know very well the Linq sintax and so I don't know how to get the element called PlacedSelection (the foreach structure clarifies where I'm looking for it). Instead in my attempt I could get the PlacedCategory elements.. but I don't need this..
PlacedSelection ActualSelection = null;
foreach (var placedCategory in Model.Coupon.Categories)
{
foreach (PlacedSelection placedSelection in placedCategory.Value.Selections)
{
var pp = placedSelection.EventId;
if (pp == Model.EventId)
{
ActualSelection = placedSelection;
break;
}
}
}
//IEnumerable<KeyValuePair<string, PlacedCategory>> p = Model.Coupon.Categories(c => c.Value.Selections.Any(s=> s.EventId == Model.EventId));
It looks like you want:
PlacedSelection actualSelection = Model.Coupon.Categories
.SelectMany(cat => cat.Value.Selections)
.FirstOrDefault(selection => selection.EventId == Model.EventId);
Any would be used if you were trying to find the category, but you're trying to find the selection, by the looks of it.

delete element from xml using LINQ

I've a xml file like:
<starting>
<start>
<site>mushfiq.com</site>
<site>mee.con</site>
<site>ttttt.co</site>
<site>jkjhkhjkh</site>
<site>jhkhjkjhkhjkhjkjhkh</site>
<site>dasdasdasdasdasdas</site>
</start>
</starting>
Now I need to delete any <site>...</site> and value will randomly be given from a textbox.
Here is my code :
XDocument doc = XDocument.Load(#"AddedSites.xml");
var deleteQuery = from r in doc.Descendants("start") where r.Element("site").Value == txt.Text.Trim() select r;
foreach (var qry in deleteQuery)
{
qry.Element("site").Remove();
}
doc.Save(#"AddedSites.xml");
If I put the value of first element in the textbox then it can delete it, but if I put any value of element except the first element's value it could not able to delete! I need I'll put any value of any element...as it can be 2nd element or 3rd or 4th and so on.... can anyone help me out?
EDIT: Okay, with further editing, it's clearer what you want to do, and as it happens it's significantly easier than you're making it, thanks to the Remove extension method on IEnumerable<T> where T : XNode:
string target = txt.Text.Trim();
doc.Descendants("start")
.Elements("site")
.Where(x => x.Value == target)
.Remove();
That's all you need.

How to check for list in LINQ

I am trying to read a file and process using LINQ.
I have a exclude list where if i encounter certain words in the file, i should omit that line
my code is
string sCodeFile = #"C:\temp\allcode.lst";
List<string> sIgnoreList = new List<string>() { "foo.c", "foo1.c" };
var wordsPerLine = from line in File.ReadAllLines(sCodeFile)
let items = line.Split('\n')
where !line.Contains(sIgnoreList.ToString())
select line;
foreach (var item in wordsPerLine)
{
console.WriteLine(item);
}
My LST file looks like below
\voodoo\foo.c
\voodoo\voodoo.h
\voodoo\std.c
\voodoo\foo1.h
in the end i want only
\voodoo\voodoo.h
\voodoo\std.c
How can i process the ignored list in contains? with my above code i dont get the desired output for sure
can any one help?
regards,
Karthik
Revised my answer. The bug is that you're doing a ToString on the ignore list, which certainly will not work. You must check each item in the list, which can be done using something like this:
where !sIgnoreList.Any(ignore => line.Contains(ignore))
A curiosity: since the above lambda is just passing a value into a method that only take the value as a parameter, you can write this even more compact as a method group like this:
where !sIgnoreList.Any(line.Contains)
Try this.
string sCodeFile = #"C:\temp\allcode.lst";
List<string> sIgnoreList = new List<string>() { "foo.c", "foo1.c" };
var wordsPerLine = File.ReadAllLines(sCodeFile).Where(n =>
{
foreach (var ign in sIgnoreList)
{
if (n.IndexOf(ign) != -1)
return false;
}
return true;
});
It passes the current element (n) to a lambda function, which checks it against every element of the sIgnoreList. Returning false means the element is ignored, true means it's returned.
Change it to:
where !sIgnoreList.Contains(line)
You need to compare each single line and check that it doesn't exist in the ignore list.
That's why the Vladislav's answer did not work.
Here's the working solution:
var result = from line in File.ReadAllLines(codeFile)
where !ignoreList.Any(line.Contains)
select line;
The problem was you didn't want to check for the whole path and messed up words/lines part a bit.

Categories