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));
Related
I have a search criteria stored in a string:
string Searchstr = "(r.Value.Contains("PwC") || (r.Value.Contains("Canadian") && r.Value.Contains("thrive"))) || (r.Value.Contains("Banana") && r.Value.Contains("Gayle"))"
I want to use this in a If statement to check the values:
if(searchstr)
{
then do this....
}
but the if should have a searchstr as boolean.
How to convert this to boolean?
EDIT: The requirement is to give search criteria dynamically in a text box in the following format - "PwC OR (Canadian AND thrive)".
Which will be used to search an XML file.
Therefore I have loaded an XML file and want to have a Where condition in LINQ for which I need to use Dynamic LINQ but string is not allowed in that and also I have some braces to deal with.
Thinking of that I have taken the resultset from the XML(The tag value which i need to search)
var selectedBook = from r in document.Root.Descendants("Archives").Elements("Headline").Elements("Para")
select r;
and would ideally like to try something like:
var query=selectedbook.Where(searchstr)
OR
if(searchstr){....then do this}
You will need to do a bit of work to make this happen, but it is possible.
You should have a look at the dynamic LINQ library. This allows you to specify LINQ conditions (and other clauses) as strings and execute them just like LINQ operators.
Start with the explanation on ScottGu's blog and follow the links:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
I'm assuming the string is going to reference only a specific set of objects (r or r.Value in this case, for example - or anything else you want, as long as you know it beforehand). If this is the case, then:
Create a delegate that takes the objects (that may be referenced) as parameters
and returns a bool, as you want.
Programmatically write a small C# source file in memory that defines the query
as the body of a method (with a fixed name, preferably) that conforms to the delegate specified above.
Use the CSharpCodeProvider class to compile an assembly
with your custom function that returns the bool you want.
Run the dynamically written and compiled code from your main program.
Well as you may guess it is not going to be straight forward but at the same time it is not as hard a problem as it seems
You can perform a few steps to get what you want:
Get the search expression as input (for e.g. "PwC OR (Canadian AND thrive)")
Write an extension method on XElement that returns true and takes the search criteria as input. You will then be able to use
var selectedBook = from r in
document.Root.Descendants("Archives").Elements("Headline").Elements("Para")
where r.SatisfiesCriteria(searchCriteria)
select r;
Write a parser class that parses searchCritera and stores it in parsed format. (for e.g. you can convert it into postfix notation). This is quite easy and you can use standard algorithm for this. For your purpose OR, AND will be operators and PwC etc. will be operands. Parenthesis will get removed as part of parsing.
Now simply invoke this parser from with in your extension method and then evaluate the postfix expression you get. This again can be done through standard stack based evaluation. Infact it would be better if you parse the criteria once and then only evaluate in where. While evaluating you need to replace the operands with r.Value.Contains
It seems like a good scenario for http://scriptcs.net/
I'm using C# in reading an XML file and counting how many "elements" there are in an XML tag, like this for example...
<Languages>English, Deutsche, Francais</Languages>
there are 3 "elements" inside the Languages tag: English, Deutsche, and Francais . I need to know how to count them and return the value of how much elements there are. The contents of the tag have the possibility of changing over time, because the XML file has to expand/accommodate additional languages (whenever needed).
IF this is not possible, please do suggest workarounds for the problem. Thank you.
EDIT: I haven't come up with the code to read the XML file, but I'm also interested in learning how to.
EDIT 2: revisions made to question
string xml = #"<Languages>English, Deutsche, Francais</Languages>";
var doc = XDocument.Parse(xml);
string languages = doc.Elements("Languages").FirstOrDefault().Value;
int count = languages.Split(',').Count();
In response to your edits which indicate that you're not simply trying to pull out comma separated strings from an XML element, then your approach to storing the XML in the first place is incorrect. As another poster commented, it should be:
<Languages>
<Language>English</Language>
<Language>Deutsche</Language>
<Language>Francais</Language>
</Languages>
Then, to get the count of languages:
string xml = #"<Languages>
<Language>English</Language>
<Language>Deutsche</Language>
<Language>Francais</Language>
</Languages>";
var doc = XDocument.Parse(xml);
int count = doc.Element("Languages").Elements().Count();
First, an "ideal" solution: do not put more than one piece of information in a single tag. Rather, put each language in its own tag, like this:
<Languages>
<Language>English</Language>
<Language>Deutsche</Language>
<Language>Francais</Language>
</Languages>
If this is not possible, retrieve the content of the tag with multiple languages, split using allLanguages.Split(',', ' '), and obtain the count by checking the length of the resultant array.
Ok, but just to be clear, an XML Element has a very specific meaning. In fact, the entire codeblock you have is an XML Element.
XElement xElm = new XElement("Languages", "English, Deutsche, Francais");
string[] elements = xElm.Value.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
trying the split the string data and build into xml format using linq to xml,but i am facing some difficulities to generate a xml string
mystring = {1:abcd}{2:efgh}{3:/r/n:12:mmm/r/n:65:nnn}
required output :
<MESSAGE>
<BLOCK1>
<VALUE>abcd</VALUE>
</BLOCK1>
<BLOCK2>
<VALUE>efgh</VALUE>
</BLOCK2>
<BLOCK3>
<TAG>12</TAG>
<VALUE>mmm</VALUE>
<TAG>65</TAG>
<VALUE>nnn</VALUE>
</BLOCK3>
</MESSAGE>
Kindly advice on the above same
mystring = {1:abcd}{2:efgh}{3:/r/n:12:mmm/r/n:65:nnn}
First use Regex to get matching patterns of brackets.
1:abcd 2:efgh 3:/r/n:12:mmm/r/n:65:nnn
Then with the resulting strings that contain your strings build your xml. You may want to replace all the /r/n with empty space as in "3:/r/n:12:mmm/r/n:65:nnn".Replace("/r/n", ""). Then to call .Split(':') to turn it into an array.
From there you are going to have to develop your logic to parse the string array into your nodes. It appears if the value can be int.TryParse(string, out int value) then it goes into a TAG element otherwise a VALUE element.
And of course skip any empty array elements. You can do that in your Split with Split(':', StringSplitOptions.RemoveEmptyEntries) if you are ok not seeing them in your resulting array.
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 + ')]
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.