How can I search delimited data for a matching string?
Working with text is one of the areas where LiveCode shines most. Here I explain a few ways how one could go about searching delimited text.
Two easy ways
The easiest way to know if a certain item exists in a delimited list is the "is among" operator.
on mouseUp put "random,list,of,items,that,have,no,particular,order" into theList put "no" is among the items of theList --true end mouseUp
I am not setting the itemdelimiter, because by default the itemdelimiter is always set to comma (,). But of course you could set it to tab, and then use it for a table field (or any other character you like).
Of course this doesn't really find the actual item, because it only tells me whether an item exists. Luckily, that often doesn't matter, because I might only want to know if an item exists, and not where exactly in an unordered list it appears.
Another way is to use the find command (see example in image above). For using find in delimited lists, it's a good idea to include the delimiter at the end or beginning. Find allows me to see where an item is in a field, but of course I can't use that position in code or do further work with it, because the find command only offers a visual search result.
When I am interested in where within a text a chunk appears, then can I use the offset functions. The offset, itemoffset, wordoffset and lineoffset functions all work the same way, so I will make examples that use them interchangeably.
To just show the position of an item in a field, I can use the following code:
on mouseUp select item itemoffset("foo", field 1) of field 1 end mouseUp
This example behaves very similarly to the find command, but now the text is selected, and I could for example replace the selection with another text.
The result of the itemoffset function will be 0 (zero) when the searched term is not in the container. So in my example, if "foo" is not in the field, the insertion point will be put before the first char of field 1.
Extra feature: By default, LiveCode only returns the number of the first item that contains "foo". So the itemoffset example above would tell me "1", even if the first item in the field is "bazfoobar". To find items that match a search term exactly, I'd have to set the wholeMatches to true.
Attention: Make sure to read each of the chunk type descriptions in the dictionary. Some of them have specific behaviors, which are normally not applied that way (like something between quotes being just one word, no matter how many spaces there are).
Rows of trouble
Up to now we only wanted to find a specific term once. But what if we have lots of occurrence of an item, and want to know the position of each of them?
For this, the offset functions all have an optional third parameter, which I can set to where the offset should start. Let me show an example text:
So if I want to know where "c" is, but skip the first occurrence, I could say:
put lineoffset("c", myList,1) -- will be 2 for the above example
Now maybe you'd say it should be 3, and I'd agree with that. But offset is a bit special, as it factors your specified skipping value into its return value. Although the "c" is on line 3, lineoffset returns 2 if you specify a skip value of 1 (2 + 1 equals the 3 we were actually looking for).
To get every item in a comma delimited list that contains "c", I made the following example code. Note how I use the value returned by the function to find the next "c" (within the repeat loop).
on mouseUp put itemoffset("c", field 1) into theValue put theValue & comma into theResult repeat forever put itemoffset("c", field 1,theValue) into newValue if newValue = 0 then -- all done! exit repeat end if add newValue to theValue put theValue & comma after theResult end repeat delete char -1 of theResult -- remove trailing comma put theResult end mouseUp
But frankly, all the return values, and parameters, and strange use of repeat forever make my head spin. That is why I normally use this following code, when I want to find every occurrence of an item. It does exactly the same as the code above, just in a completely different way.
on mouseUp put itemoffset("c", field 1) into theValue if theValue <> 0 then repeat for each item currentItem in field 1 add one to myCounter if currentItem contains "c" then -- with "wholematches": if currentItem is "c" then put myCounter & comma after theResult end if end repeat end if delete char -1 of theResult put theResult end mouseUp