Need some help with a XElement Query - c#

This query isn't working as I'm expecting it to. Anyone see the problem?
I'm trying to get an element by it's name, but it's not returning anything. Here is the specific part of the function I need some help with:
Update
The solution was to use an XName instead of a string. Like so:
var matchingElements = elements.Where(e => e.Name.Equals(XName.Get(name)));

Try changing your line to:
elements.Where(e => e.Name.LocalName == name)
The LocalName part is the important part, as otherwise you are comparing equality of an XName with a string. Remember, XML supports names of the style "prefix:element-name". In that example, "prefix" is the identifier associated with the namespace returned by e.Name.Namespace and "element-name" is the identifier returned by e.Name.LocalName.

Kirk's answer is right on the money. I wanted to point out a few issues with your code.
This line unnecessarily counts all the elements:
var hasMatch = matchingElements.Count() > 0;
You can replace it with Any() which will terminate early once an element is found:
var hasMatch = matchingElements.Any();
Next, having verified that hasMatch is true you should call First() instead of FirstOrDefault() since you know it has to have a value at that point.
Having said that, you could actually rewrite your code as follows:
var matchingElement = elements.FirstOrDefault(e => e.Name.LocalName == name);
return (string)matchingElement;
Here casting to a string will return the value of the element if it was found, otherwise it would return null. The cast is used just in case it was null since you wouldn't be able to use matchingElement.Value which would throw a NullReferenceException if no element was found. You should also consider using SingleOrDefault if you expect only one element to exist.

I think you need to add the root namespace to the name of the element.
You can also try using the XContainer.Descendants(XName) method instead.

Related

Get the first element of System.array?

I am writing a script for a task in DisplayFusion. There's an array with window classes of which I need the first.
How to get the first element of System.array regardless of the index' type?
I tried
String windowClass = BFS.Window.GetClass(myArray[Array.IndexOf(myArray)]);
but that throws an error
(No overloading for method 'IndexOf', accepts 1-Arguments)
IndexOf is for finding the index of the first element in the array that matches the value you passed. I think you're after [0].
String windowClass = BFS.Window.GetClass(myArray[0]);
Please note that if the array is empty this will throw an exception.
You could also use .First() or FirstOrDefault() from System.Linq, but [0] is made for what you need.

Linq - Is Exact String in String Array

I'm trying to write a Linq query that loops through a set of Umbraco nodes and checks if it's Document Type Alias is in a string array. I've got something very close:
if (allowedDocTypes != null && allowedDocTypes.Length > 0)
{
allowedDocTypes = allowedDocTypes.Where(x => !string.IsNullOrEmpty(x)).ToArray();
nodes = nodes.Where(x => x.DocumentTypeAlias.ContainsAny(allowedDocTypes));
}
allowedDocTypes is a string array that includes the document types. The first line inside the if statement removes any empty strings from the array. Finally, I'm making use of the ContainsAny method to check if the document type alias is in the string array.
This almost works in that it'll check if the document type alias contains any of the string in the string array. However, it works for partial matches as well but I really need exact matches.
For example, the string array has a value of review in it. What ContainsAny appears to do is pull through all the nodes with a document type alias of review but it'll also pull through any with a document type alias of preview.
Is there a way to easily change this so that review would be an exact match rather than partial?
Thanks,
Ben
All you really should have to do is reverse the logic a bit and use Contains:
nodes = nodes.Where(x => allowedDocTypes.Contains(x.DocumentTypeAlias));

Using Find on a List of custom objects with a complex predicate

This may not be the right way to do this, but here goes...
I have a list of 'rules' that I'm defining in XML along the lines of..
<Config>
<Rule key1="1" key2="Jack" value="Red" />
<Rule key1="2" key2="John" value="Blue" />
...
</Config>
I'm trying to come up with a way that new 'rules' can be defined and used outside of re-compiling and testing program.
So the program deserializes the XML into a list of custom objects (List). No problem so far.
Now, I want to use a Find to do something like... (C#)
Rule found = rules.Find(x => (x.key1 == "2") && (x.key2 == "John"));
... returns the 2nd rule from above and hence the value I'm looking for of "Blue".
Is this even possible or am I way off base?
You should use SingleOrDefault:
Rule found = rules.SingleOrDefault(r => r.key1 == "2" && r.key2 == "John");
Quoting MSDN:
Returns the only element of a sequence, or a default value if the sequence is empty; this method throws an exception if there is more than one element in the sequence.
Note that found will be null if no element was found that satisfies the given expression.
Also, it will throw an InvalidOperationException if there is more than one element in your list that satisfies the condition.
I think this is expected since you are doing a search based on unique keys for each rule. If there are two rules that have the same keys, an exception should be raised.
If you are expecting exactly one element (and to throw an exception if there are more or none is found), you should use Single:
Rule found = rules.Single(r => r.key1 == "2" && r.key2 == "John");
Returns the only element of a sequence, and throws an exception if there is not exactly one element in the sequence.

How to evaluate a simple C# string

I have been reading up Expression trees, and I think this is a good example to use them, still I can't seem to grasp how this would be done.
I have a set of strings that I want evaluated, they are all of the type:
exp == exp , or exp != exp , or exp (<,>,>=,<=) exp if exp is Numerical Type.
The exp do not need to check if they are valid I am fine with them blowing up if they are not.
My issue is, how to I parse to get the actual obj.
I want to pass a string like these below
Owner.Property.Field == 3;
or
Owner.Field == 3;
or
Owner.Method(1) == true
And get if the evaluation is true or not. MY issue is how do I travel down the "path" on the left and get the value?
I implemented a version with Reflection and string parsing, that somehow does the work - except for when we are using a method, and honestly its not that performant at all. I want this to be as performant as possible can get, and if possible give me a small explanation of how the expression works , so I can learn.
You can use code generation libraries like CodeDOM or Roslyn to generate Func that will do the evaluation.
For example, in Roslyn you can create a Session and set an object containing Owner as the Host object of the Session. than you can generate the code in the Session as you wish like the following:
Session session = ScriptEngine.CreateSession(objectContainingOwnerAsProperty);
bool result = session.Execute<bool>("Owner.Field == 8");
Now result will contain the evaluation result for your string without reflection nor string analysis.

Retrieve all nodes where attributes contain a given string

I want to retrieve all the RULE nodes that have one or more attributes that contains a given string. Once i found them I need to replace the string with a new one.
I tested those XPath query but they do not work:
string xpathquery = "/FIREWALL/ETH/RULE[contains(#*,'" + toTest + "')]"; //DOES NOT WORK
string xpathquery = "/FIREWALL/ETH/RULE[contains(attribute::*,'" + toTest +"')]"; //DOES NOT WORK
The strange thing is that if I use #attributename or attribute::attributename it actually works. Where is the error?
I am using the contains function because there could be two different cases, let's say I want to replace $HOST1 with $HOST2:
<RULE .... host="$HOST1"/> //NO PROBLEM
<RULE .... host="$HOST1:21"/> //I NEED TO REPLACE ONLY $HOST1, NOT THE ENTIRE STRING
I can't just set the Value attribute of XmlNode but I need to modify the string. Actually I don't know if XPath can handle this with a find and replace or something like this.
The query you're using checks the concatenated text of all the attributes. The correct syntax is:
string xpathquery = "/FIREWALL/ETH/RULE/#*[contains(.,'" + toTest + "')]/..";
In other words, find a matching attribute and return its parent element. Of course, if what you want to do is to work with the attribute values, you can dispense with the /.. on the end, in which case you'll be returned the actual attribute nodes and can just iterate through them modifying the values accordingly.
The standard XPath function contains() requires a single string as its first argument. When the first argument is a list of nodes (as in your case), only the string value of the first node in the provided node-set is used. This is how you get your current results.
Solution:
/FIREWALL/ETH/RULE/#*[contains(.,' + toTest + ')]

Categories