I'm trying to count with XPATH Builder in Sitecore, the number of items which have more than 5 values in a multilist field.
I cannot count the number of "|" from raw values, so I can say I am stuck.
Any info will be helpful.
Thank you.
It's been a long time since I used XPath in Sitecore - so I may have forgotten something important - but:
Sadly, I don't think this is possible. XPath Builder doesn't really run proper XPath. It understands a subset of things that would evaluate correctly in a full XPath parser.
One of the things it can't do (on the v8-initial-release instance I have to hand) is be able to process XPath that returns things that are not Sitecore Items. A query like count(/sitecore/content/*) should return a number - but if you try to run that using either the Sitecore Query syntax, or the XPath syntax options you get an error:
If you could run such a query, then your answer would be based on an expression like this, to perform the count of GUIDs referenced by a specific field:
string-length( translate(/yourNodePath/#yourFieldName, "abcdefg1234567890{}-", "") ) + 1
(Typed from memory, as I can't run a test - so may not be entirely correct)
The translate() function replaces any character in the first string with the relevant character in the second. Hence (if I've typed it correctly) that expression should remove all your GUIDs and just leave the pipe-separator characters. Hence one plus the length of the remaining string is your answer for each Item you need to process.
But, as I say, I don't think you can actually run that from Query Builder...
These days, people tend to use Sitecore PowerShell Extensions to write ad-hoc queries like this. It's much more flexible and powerful - so if you can use that, I'd recommend it.
Edited to add: This question got a bit stuck in my head - so if you are able to use PowerShell, here's how you might do it:
Assuming you have declared where you're searching, what MultiList field you're querying, and what number of selections Items must exceed:
$root = "/sitecore/content/Root"
$field = "MultiListField"
$targetNumber = 3
then the "easy to read" code might look like this:
foreach($item in Get-ChildItem $root)
{
$currentField = Get-ItemField $item -ReturnType Field -Name $field
if($currentField)
{
$count = $currentField.Value.Split('|').Count
if($count -gt $targetNumber)
{
$item.Paths.Path
}
}
}
It iterates the children of the root item you specified, and gets the contents of your field. If that field name had a value, it then splits that into GUIDs and counts them. If the result of that count is greater than your threshold it returns the item's URI.
You can get the same answer out of a (harder to read) one-liner, which would look something like:
Get-ChildItem $root | Select-Object Paths, #{ Name="FieldCount"; Expression={ Get-ItemField $_ -ReturnType Field -Name $field | % { $_.Value.Split('|').Count } } } | Where-Object { $_.FieldCount -gt $targetNumber } | % { $_.Paths.Path }
(Not sure if that's the best way to write that - I'm no expert at PowerShell syntax - but it gives the same results as far as I can see)
Related
I'm working on Xpath/Xquery to return values of multiple child nodes based on a sibling node value in a single query. My XML looks like this
<FilterResults>
<FilterResult>
<ID>535</ID>
<Analysis>
<Name>ZZZZ</Name>
<Identifier>asdfg</Identifier>
<Result>High</Result>
<Score>0</Score>
</Analysis>
<Analysis>
<Name>XXXX</Name>
<Identifier>qwerty</Identifier>
<Result>Medium</Result>
<Score>0</Score>
</Analysis>
</FilterResult>
<FilterResult>
<ID>745</ID>
<Analysis>
<Name>XXXX</Name>
<Identifier>xyz</Identifier>
<Result>Critical</Result>
<Score>0</Score>
</Analysis>
<Analysis>
<Name>YYYY</Name>
<Identifier>qwerty</Identifier>
<Result>Medium</Result>
<Score>0</Score>
</Analysis>
</FilterResult>
</FilterResults>
I need to get values of Score and Identifier based on Name value. I'm currently trying with below query but not working as desired
fn:string-join((
for $Identifier in fn:distinct-values(FilterResults/FilterResult/Analysis[Name="XXXX"])
return fn:string-join((//Identifier,//Score),'-')),',')
The output i'm looking for is this
qwerty-0,xyz-0
Your question suggests some fundamental misunderstandings about XQuery, generally. It's hard to explain everything in a single answer, but 1) that is not how distinct-values works (it returns string values, not nodes), and 2) the double slash selections in your return statement are returning everything because they are not constrained by anything. The XPath you use inside the distinct-values call is very close, however.
Instead of calling distinct-values, you can assign the Analysis results of that XPath to a variable, iterate over them, and generate concatenated strings. Then use string-join to comma separate the full sequence. Note that in the return statement, the variable $a is used to concat only one pair of values at a time.
string-join(
let $analyses := FilterResults/FilterResult/Analysis[Name="XXXX"]
for $a in $analyses
return $a/concat(Identifier, '-', Score),
',')
=> qwerty-0,xyz-0
I have 3 fields(Name, Code, displayNmae) in the list, here I need to get a list as output in which I get all the fields but need to split displayname field by colon and then add list again for each splitted value which is display name.
Hence in output list I will have the 5 row as total display name are 5 in 2 rows.
Need the linq query for this problem.
Name Code displayName
Napkins_tableware - Napkins and tableware - 3_ply:conventional_napkins
hand-towel - Hand and Towel - 2_ply:towel roll:coloured
Output should be like this :
Name Code displayName
Napkins_tableware - Napkins and tableware - 3_ply
Napkins_tableware - Napkins and tableware - conventional_napkin
hand-towel - Hand and Towel - 2_ply
hand-towel - Hand and Towel - towel roll
hand-towel - Hand and Towel - coloured
Solution which I tried in C#
foreach(ProductDetailsWithFilters qs in CategoryProductList())
{
foreach(string friendlyname in qs.displayName.Split(':'))
{
qs.displayName = friendlyname;
tempCategoryProductList.Add(qs);
}
}
If you're translating to LINQ, when you have nested foreach loops those correspond to 'from' clauses in query syntax (or in dot syntax, subsequent ones become SelectMany, see below.) The following should be close to what you want:
var query =
from qs in CategoryProductList()
from friendlyName in qs.displayName.Split(':')
select new ProductDetailsWithFilters(qs.Code, qs.Category, friendlyName);
Note: Because functional programming should be side-effect-free, it's better to select a new instance ProductDetailsWithFilters than it is to modify the existing one in your query. My presumption is that you can call a constructor to build a new one of these.
For you to modify the existing property like your loop implementation does, you would have to iterate over the result -- LINQ doesn't provide such a thing in the framework. Such side-effects often lead to hard-to-find bugs.
To do the equivalent of the above query with a SelectMany and dot-syntax:
var query = CategoryProductList()
.SelectMany(
qs => qs.DisplayName.Split(':'),
(qs, friendlyName) => new ProductDetailsWithFilters(qs.Code, qs.Category, friendlyName));
Both lead to functionally identical code. In this case, I tend to prefer the query-syntax over the dot-syntax partly because because there are several SelectMany overloads and handling the projection involves repeating the variables across both lambda expressions. If you had another "from" to add, the query-syntax hides the management of 'transparent identifiers' that you would otherwise have to deal with in dot-syntax equivalent code. Whatever your preference, you now have both.
It's worth noting that queries are lazy -- they do nothing until you iterate over their result. So it's really what you do with the result from here that is the interesting part - store it .ToList(), directly data-bind it to a UI, use it to service a web-API, etc...
I have a bunch of strings that look, for example, like this:
<option value="Spain">Spain</option>
And I want to extract the name of the country from inside.
The easiest way I could think of to do this in Ruby was to use a regular expression of this form:
country = line.match(/>(.+)</)
However, this returns >Spain<. So I did this:
line.match(/>(.+)</).to_s.gsub!(/<|>/,"")
Works well enough, but I'd be surprised if there's not a more elegant way to do this? It seems like using a regular expression to declare how to find the thing you want, without actually wanting the enclosing strings that were used to match it to be part of the data that gets returned.
Is there a conventional approach to this problem?
The right way to deal with that string is to use an HTML parser, for example:
country = Nokogiri::HTML('<option value="Spain">Spain</option>').at('option').text
And if you have several such strings, paste them together and use search:
html = '<option value="Spain">Spain</option><option value="Canada">Canada</option>'
countries = Nokogiri::HTML(html).search('option').map(&:text)
# ["Spain", "Canada"]
But if you must use a regex, then:
country = '<option value="Spain">Spain</option>'.match('>([^<]+)<')[1]
Keep in mind that match actually returns a MatchData object and MatchData#to_s:
Returns the entire matched string.
But you can access the captured groups using MatchData#[]. And if you don't like counting, you could use a named capture group as well:
country = '<option value="Spain">Spain</option>'.match('>(?<name>[^<]+)<')['name']
Everything in this code works properly, except the contents of the $1 variable aren't being properly displayed. According to my tests, all the matching is being done properly, I am just having trouble figuring out how to actually output the contents of $1.
codeTags = {
/\[b\](.+?)\[\/b\]/m => "<strong>#{$1}</strong>",
/\[i\](.+?)\[\/i\]/m => "<em>#{$1}</em>"
}
regexp = Regexp.new(/(#{Regexp.union(codeTags.keys)})/)
message = (message).gsub(/#{regexp}/) do |match|
codeTags[codeTags.keys.select {|k| match =~ Regexp.new(k)}[0]]
end
return message.html_safe
Thank you!
As soon as you do this:
codeTags = {
/\[b\](.+?)\[\/b\]/m => "<strong>#{$1}</strong>",
/\[i\](.+?)\[\/i\]/m => "<em>#{$1}</em>"
}
The #{$1} bits in the values are interpolated using whatever happens to be in $1 at the time. The values will most likely be "<strong></strong>" and "<em></em>" and those aren't very useful.
And regexp is already a regular expression object so gsub(/#{regexp}/) should be just gsub(regexp). Similar things apply to the keys of codeTags, they're already regular expression objects so you don't need to Regexp.new(k).
I'd change the whole structure, you're overcomplicating things. Just something simple like this would be fine for only two replacements:
message = message.gsub(/\[b\](.*?)\[\/b\]/) { '<strong>' + $1 + '</strong>' }
message = message.gsub(/\[i\](.*?)\[\/i\]/) { '<em>' + $1 + '</em>' }
If you try to do it all at once you'll have problems with nesting in something like this:
message = 'Where [b]is[/b] pancakes [b]house [i]and[/i] more[/b] stuff?'
You'd end up having to use a recursive gsub and possibly some lambdas if you wanted to properly handle things like that with a single expression.
There are better things to spend your time on than trying to be clever on something like this.
Response to comments: If you have more bb-tags and some smilies to worry about and several messages per page then you should HTMLify each message when you create it. You could store only the HTML version or both HTML and BB-Code versions if you want the BB-Code stuff around for some reason. This way you'd only pay for the HTMLification once per message and producing your big lists would be nearly free.
Can I use an xpath query on a result already obtained using xpath?
In most hosting languages/environments (like XSLT, XQuery, DOM) you can. Don't know about PHP, but it would be strange if it doesn't allow this.
Of course, the result of the first query must be a node-set, in order for a future "/" operator to be possible/allowed/successful on it.
I have done it in PHP/SimpleXML. The thing that I didn't understand at first is that you're still dealing with the full SimpleXML object, so if you start with "/nodename", you're operating on root. If you start with "nodename" you are starting at the beginning of the result node. Here's my example:
$parsed=simplexml_load_string($XML);
$s = '/ItemSearchResponse/Items/Item';
$items = $parsed->xpath($s);
foreach($items as $item)
{
$s = 'ItemAttributes/Feature';
$features[]=$item->xpath($s);
$s = 'ASIN';
$asins[]=$item->xpath($s);
$s = 'ImageSets/ImageSet[#Category="primary"]';
$primary_img_set=$item->xpath($s);
$s = 'MediumImage/URL';
$medium_image_url[] = $primary_img_set[0]->xpath($s);
}
In PHP, for example, you can run a query with a context, i.e. a given node. So if you have got a DOMNodeList as a result of the first query you can do things like this:
$query1 = '//p';
$query2 = './a'; // do not forget the dot
$node = $xpath->query($query1)->item(0);
$result = $xpath->query($query2, $node);
Of course this is a silly example because it could have been done just in one shot with the correct XPath experssion but I believe it illustrates your question.