It is currently February 29th, 2024, 4:27 pm

Help creating a search box that searches a .txt file

Get help with creating, editing & fixing problems with skins
User avatar
MazinLabib10
Posts: 14
Joined: January 21st, 2023, 3:47 pm

Re: Help creating a search box that searches a .txt file

Post by MazinLabib10 »

Yincognito wrote: May 25th, 2023, 9:07 pm Sure. You have two options:
Team - Variant 1.jpg
Team - Variant 2.jpg
Basically, you can try skipping the ?s dot-all (i.e. matching newlines too) flag and use the \R to match both the \r\n and the single \n as newline markers (one in Windows, the other on Macs), taking care to escape the / by preceding with \ in the first variant, or, alternatively, you can keep using the flag and make sure you use unambiguous \N patterns for non linebreak characters instead of the typical . which with the flag matches newlines as well.
At the moment, I'm leaning towards using one of these methods only because eclectic-tech's method consists of a quite a bit of stuff that I don't understand currently. :oops: Since I'm planning on making this a public release, it would make more sense to use something I understand in case I need to fix bugs or something later. I'll try to figure out how it works and see where that takes me. Regardless, thanks to both of you for the responses!
Yincognito wrote: May 25th, 2023, 9:07 pm Take note of the site used for testing, the places where each thing goes, and the setting of the PCRE regex flavor in the top right corner of the page. It's a great place to test your regexes, assuming you accurately reproduce the conditions you have in the skin, and it's a great place to learn how to write regex patterns, by looking at the very helpful and concise sidebar at the left, once you clicked the RegEx Reference menu in it.
That looks really useful! I've been looking for somewhere to test regexes but whatever I did find, I couldn't work out how to use. I'll be sure to check this one out! :thumbup:
User avatar
Yincognito
Rainmeter Sage
Posts: 6829
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Help creating a search box that searches a .txt file

Post by Yincognito »

MazinLabib10 wrote: May 26th, 2023, 10:45 am At the moment, I'm leaning towards using one of these methods only because eclectic-tech's method consists of a quite a bit of stuff that I don't understand currently. :oops: Since I'm planning on making this a public release, it would make more sense to use something I understand in case I need to fix bugs or something later. I'll try to figure out how it works and see where that takes me. Regardless, thanks to both of you for the responses!

That looks really useful! I've been looking for somewhere to test regexes but whatever I did find, I couldn't work out how to use. I'll be sure to check this one out! :thumbup:
First, sorry for the delay in responding, had a busy day. I already knew about regex returning more occurrences when searching for say "FC", but that can be solved in various ways. One of them is to use regex conditionals to get multiple occurrences of the searched text. A regex conditional usually looks like (?(?=stringtosearch)stringtoparse) where (?=stringtosearch) is a lookahead expression - for example (this assumes you have a Clubs.txt file in the @Resources subfolder of your skin folder):

Code: Select all

[Variables]
Item=(?(?=.*\R\N*[#ClubName]\N* \/).*\R(\N*[#ClubName]\N*) \/)
ClubIndexMax=5
ClubIndex=1
ClubName=FC
; Notes:
; - keep the Item variable before the ClubName one, or escape [#ClubName] to literal in the Item variable above, replacing it with [#*ClubName*]
; - if changing the ClubName variable to search for something else, remember using [!CommandMeasure Clubs "Update"] to reload the file from disk

[Rainmeter]
Update=1000
AccurateText=1
DynamicWindowSize=1

---Measures---

[Clubs]
Measure=WebParser
URL=file://#@#Clubs.txt
RegExp=(?siU)[#Item][#Item][#Item][#Item][#Item]
UpdateRate=-1
FinishAction=[!UpdateMeasureGroup ClubGroup][!UpdateMeter Result][!Redraw]
DynamicVariables=1

[Club1]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=1
UpdateDivider=-1
DynamicVariables=1

[Club2]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=2
UpdateDivider=-1
DynamicVariables=1

[Club3]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=3
UpdateDivider=-1
DynamicVariables=1

[Club4]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=4
UpdateDivider=-1
DynamicVariables=1

[Club5]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=5
UpdateDivider=-1
DynamicVariables=1

---Meters---

[Result]
Meter=String
SolidColor=0,0,0,128
FontColor=255,255,255,255
FontFace=Consolas
FontSize=16
AntiAlias=1
MeasureName=Club#ClubIndex#
Text=Club Found #ClubIndex# = %1
UpdateDivider=-1
MouseScrollUpAction=[!SetVariable ClubIndex ((#ClubIndexMax#+#ClubIndex#-1-1)%#ClubIndexMax#+1)][!UpdateMeter Result][!Redraw]
MouseScrollDownAction=[!SetVariable ClubIndex ((#ClubIndexMax#+#ClubIndex#+1-1)%#ClubIndexMax#+1)][!UpdateMeter Result][!Redraw]
DynamicVariables=1
Here, I get 5 "items" (or occurrences of the searched text) in [Clubs], and then distribute them in the [ClubN] measures, where N ranges from 1 to 5. For conveniency, I stored the regex pattern for finding such an item in the variable named Item at the beginning of the code. ClubName is the (static, since I didn't make the whole InputText structure here) search string, and you can iterate through the first 5 occurrences via mouse scrolling, which is changing ClubIndex from 1 to ClubIndexMax. Check the two notes for some other details about the code. You can test the regex by pasting something like this in the Expression box from the site I screenshot earlier, letting the same test text (note the presence of ^ aka the start of the string anchor below, that helps in returning single matches too, but it's not necessarily needed in Rainmeter, in this case):

Code: Select all

(?siU)^(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)
References:
- WebParser and lookahead expressions
- nesting variables
- escaping variables

P.S. Of course, either the Item variable or the StringIndex options can be further adjusted to return the web address with the matches for that club in some other measures. And if something still needs adjustment, there is always a (regex) Substitute.
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
MazinLabib10
Posts: 14
Joined: January 21st, 2023, 3:47 pm

Re: Help creating a search box that searches a .txt file

Post by MazinLabib10 »

Yincognito wrote: May 26th, 2023, 6:17 pm First, sorry for the delay in responding, had a busy day. I already knew about regex returning more occurrences when searching for say "FC", but that can be solved in various ways. One of them is to use regex conditionals to get multiple occurrences of the searched text. A regex conditional usually looks like (?(?=stringtosearch)stringtoparse) where (?=stringtosearch) is a lookahead expression - for example (this assumes you have a Clubs.txt file in the @Resources subfolder of your skin folder):

Code: Select all

[Variables]
Item=(?(?=.*\R\N*[#ClubName]\N* \/).*\R(\N*[#ClubName]\N*) \/)
ClubIndexMax=5
ClubIndex=1
ClubName=FC
; Notes:
; - keep the Item variable before the ClubName one, or escape [#ClubName] to literal in the Item variable above, replacing it with [#*ClubName*]
; - if changing the ClubName variable to search for something else, remember using [!CommandMeasure Clubs "Update"] to reload the file from disk

[Rainmeter]
Update=1000
AccurateText=1
DynamicWindowSize=1

---Measures---

[Clubs]
Measure=WebParser
URL=file://#@#Clubs.txt
RegExp=(?siU)[#Item][#Item][#Item][#Item][#Item]
UpdateRate=-1
FinishAction=[!UpdateMeasureGroup ClubGroup][!UpdateMeter Result][!Redraw]
DynamicVariables=1

[Club1]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=1
UpdateDivider=-1
DynamicVariables=1

[Club2]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=2
UpdateDivider=-1
DynamicVariables=1

[Club3]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=3
UpdateDivider=-1
DynamicVariables=1

[Club4]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=4
UpdateDivider=-1
DynamicVariables=1

[Club5]
Group=ClubGroup
Measure=WebParser
URL=[Clubs]
StringIndex=5
UpdateDivider=-1
DynamicVariables=1

---Meters---

[Result]
Meter=String
SolidColor=0,0,0,128
FontColor=255,255,255,255
FontFace=Consolas
FontSize=16
AntiAlias=1
MeasureName=Club#ClubIndex#
Text=Club Found #ClubIndex# = %1
UpdateDivider=-1
MouseScrollUpAction=[!SetVariable ClubIndex ((#ClubIndexMax#+#ClubIndex#-1-1)%#ClubIndexMax#+1)][!UpdateMeter Result][!Redraw]
MouseScrollDownAction=[!SetVariable ClubIndex ((#ClubIndexMax#+#ClubIndex#+1-1)%#ClubIndexMax#+1)][!UpdateMeter Result][!Redraw]
DynamicVariables=1
Here, I get 5 "items" (or occurrences of the searched text) in [Clubs], and then distribute them in the [ClubN] measures, where N ranges from 1 to 5. For conveniency, I stored the regex pattern for finding such an item in the variable named Item at the beginning of the code. ClubName is the (static, since I didn't make the whole InputText structure here) search string, and you can iterate through the first 5 occurrences via mouse scrolling, which is changing ClubIndex from 1 to ClubIndexMax. Check the two notes for some other details about the code. You can test the regex by pasting something like this in the Expression box from the site I screenshot earlier, letting the same test text (note the presence of ^ aka the start of the string anchor below, that helps in returning single matches too, but it's not necessarily needed in Rainmeter, in this case):

Code: Select all

(?siU)^(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)(?(?=.*\R\N*FC\N* \/).*\R(\N*FC\N*) \/)
References:
- WebParser and lookahead expressions
- nesting variables
- escaping variables

P.S. Of course, either the Item variable or the StringIndex options can be further adjusted to return the web address with the matches for that club in some other measures. And if something still needs adjustment, there is always a (regex) Substitute.
Oh don't worry! I actually removed the edit because I'd found a fix for it and I didn't think you'd have seen it already. However, what you've suggested now is so much more efficient than what I came up with. So thanks a lot!
User avatar
Yincognito
Rainmeter Sage
Posts: 6829
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Help creating a search box that searches a .txt file

Post by Yincognito »

MazinLabib10 wrote: May 26th, 2023, 7:05 pm Oh don't worry! I actually removed the edit because I'd found a fix for it and I didn't think you'd have seen it already. However, what you've suggested now is so much more efficient than what I came up with. So thanks a lot!
Yeah, I noticed both the original and your edit, but I'm not one to let stuff linger around. In fact, here's another method, also efficient, which instead of using WebParser's StringIndex, relies on getting the entire file contents and removing everything but the matching club names, transforming the data into a comma separated list, on which it then computes the number of elements, and finally iterates through each of its elements, also by scrolling:

Code: Select all

[Variables]
ClubCount=0
ClubIndex=0
ClubName=fc

[Rainmeter]
Update=1000
AccurateText=1
DynamicWindowSize=1

---Measures---

[Clubs]
Measure=WebParser
URL=file://#@#Clubs.txt
RegExp=(?siU)^(.*)$
UpdateRate=-1
RegExpSubstitute=1
Substitute="(?siU).*(?:\R(\N*#ClubName#\N*) \/|$)":"\1,","\\1,":""
FinishAction=[!UpdateMeasureGroup ClubGroup][!UpdateMeter Result][!Redraw]
DynamicVariables=1

[Count]
Group=ClubGroup
Measure=String
String=[Clubs]
UpdateDivider=-1
RegExpSubstitute=1
Substitute="(?siU).*,":"+1","^$":"0"
OnUpdateAction=[!SetVariable ClubCount ([Count])]
DynamicVariables=1

[Club]
Group=ClubGroup
Measure=String
String=[Clubs]
UpdateDivider=-1
RegExpSubstitute=1
Substitute="(?siU)^(?:(?:.*,){0,#ClubIndex#}+(.*),.*|.*)$":"\1","\\1":""
DynamicVariables=1

---Meters---

[Result]
Meter=String
SolidColor=0,0,0,128
FontColor=255,255,255,255
FontFace=Consolas
FontSize=16
AntiAlias=1
MeasureName=Club
Text=Club Found #ClubIndex# = %1
UpdateDivider=-1
MouseScrollUpAction=[!SetVariable ClubIndex (Clamp(#ClubIndex#-1,0,#ClubCount#?#ClubCount#-1:0))][!UpdateMeasure Club][!UpdateMeter Result][!Redraw]
MouseScrollDownAction=[!SetVariable ClubIndex (Clamp(#ClubIndex#+1,0,#ClubCount#?#ClubCount#-1:0))][!UpdateMeasure Club][!UpdateMeter Result][!Redraw]
DynamicVariables=1
Creating the list is based on the same regex pattern as before (no conditional needed this time), with the help of which we retain the captures (enclosed by plain round brackets and referenced by \1) and add commas after them, in effect deleting the parts before the searched string matches and up to the end of the string (i.e. parts that are to the left, between, and to the right of the matches), and finally removing the literal "\1" leftovers if any.

Counting the elements in the list is based on simply replacing each element followed by a comma with "+1", handling the empty string exception case with the help of "0", then converting the "+1+1..." string into a numerical formula by enclosing it between round brackets when we set the ClubCount variable.

Picking the ClubIndex-th element (with the index starting at 0 and not 1 this time) is done by using (?:) to skip the first ClubIndex elements via (?:.*,){0,#ClubIndex#}+ where we used {quantifiers}, capturing the following element by enclosing it between plain round brackets, and ignoring the rest (or, via |.*, everything, if nothing is captured). We again retain only the capture, in effect discarding the rest, remove the literal "\1" leftovers if any, and we're done.

When scrolling, we clamp / limit the index between 0 and ClubCount-1 (unless ClubCount is 0, in which case we don't use -1 but 0 as the upper limit) by using numerical conditionals.

One advantage of this approach is that you don't need to create N measures, each with its own StringIndex, to get the found string matches, a single measure is enough. Another advantage is that, since we don't need to create N measures, we are not limited by N anymore, so we can iterate through a theoretically (but not practically, since regex has also some limits, of course much larger) unlimited number of matches, if we wanted to.

References:
- most of the regex details are found in the RegEx Reference (a few lines in the Groups And References + Quantifiers And Alternation topics) sidebar on the leftside of the RegExR.com page I screenshot before
- numerical conditionals in Rainmeter formulas
- the rest is just a bit of creativity to get what one needs
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
MazinLabib10
Posts: 14
Joined: January 21st, 2023, 3:47 pm

Re: Help creating a search box that searches a .txt file

Post by MazinLabib10 »

Yincognito wrote: May 26th, 2023, 10:26 pm Yeah, I noticed both the original and your edit, but I'm not one to let stuff linger around. In fact, here's another method, also efficient, which instead of using WebParser's StringIndex, relies on getting the entire file contents and removing everything but the matching club names, transforming the data into a comma separated list, on which it then computes the number of elements, and finally iterates through each of its elements, also by scrolling:

Code: Select all

[Variables]
ClubCount=0
ClubIndex=0
ClubName=fc

[Rainmeter]
Update=1000
AccurateText=1
DynamicWindowSize=1

---Measures---

[Clubs]
Measure=WebParser
URL=file://#@#Clubs.txt
RegExp=(?siU)^(.*)$
UpdateRate=-1
RegExpSubstitute=1
Substitute="(?siU).*(?:\R(\N*#ClubName#\N*) \/|$)":"\1,","\\1,":""
FinishAction=[!UpdateMeasureGroup ClubGroup][!UpdateMeter Result][!Redraw]
DynamicVariables=1

[Count]
Group=ClubGroup
Measure=String
String=[Clubs]
UpdateDivider=-1
RegExpSubstitute=1
Substitute="(?siU).*,":"+1","^$":"0"
OnUpdateAction=[!SetVariable ClubCount ([Count])]
DynamicVariables=1

[Club]
Group=ClubGroup
Measure=String
String=[Clubs]
UpdateDivider=-1
RegExpSubstitute=1
Substitute="(?siU)^(?:(?:.*,){0,#ClubIndex#}+(.*),.*|.*)$":"\1","\\1":""
DynamicVariables=1

---Meters---

[Result]
Meter=String
SolidColor=0,0,0,128
FontColor=255,255,255,255
FontFace=Consolas
FontSize=16
AntiAlias=1
MeasureName=Club
Text=Club Found #ClubIndex# = %1
UpdateDivider=-1
MouseScrollUpAction=[!SetVariable ClubIndex (Clamp(#ClubIndex#-1,0,#ClubCount#?#ClubCount#-1:0))][!UpdateMeasure Club][!UpdateMeter Result][!Redraw]
MouseScrollDownAction=[!SetVariable ClubIndex (Clamp(#ClubIndex#+1,0,#ClubCount#?#ClubCount#-1:0))][!UpdateMeasure Club][!UpdateMeter Result][!Redraw]
DynamicVariables=1
Creating the list is based on the same regex pattern as before (no conditional needed this time), with the help of which we retain the captures (enclosed by plain round brackets and referenced by \1) and add commas after them, in effect deleting the parts before the searched string matches and up to the end of the string (i.e. parts that are to the left, between, and to the right of the matches), and finally removing the literal "\1" leftovers if any.

Counting the elements in the list is based on simply replacing each element followed by a comma with "+1", handling the empty string exception case with the help of "0", then converting the "+1+1..." string into a numerical formula by enclosing it between round brackets when we set the ClubCount variable.

Picking the ClubIndex-th element (with the index starting at 0 and not 1 this time) is done by using (?:) to skip the first ClubIndex elements via (?:.*,){0,#ClubIndex#}+ where we used {quantifiers}, capturing the following element by enclosing it between plain round brackets, and ignoring the rest (or, via |.*, everything, if nothing is captured). We again retain only the capture, in effect discarding the rest, remove the literal "\1" leftovers if any, and we're done.

When scrolling, we clamp / limit the index between 0 and ClubCount-1 (unless ClubCount is 0, in which case we don't use -1 but 0 as the upper limit) by using numerical conditionals.

One advantage of this approach is that you don't need to create N measures, each with its own StringIndex, to get the found string matches, a single measure is enough. Another advantage is that, since we don't need to create N measures, we are not limited by N anymore, so we can iterate through a theoretically (but not practically, since regex has also some limits, of course much larger) unlimited number of matches, if we wanted to.

References:
- most of the regex details are found in the RegEx Reference (a few lines in the Groups And References + Quantifiers And Alternation topics) sidebar on the leftside of the RegExR.com page I screenshot before
- numerical conditionals in Rainmeter formulas
- the rest is just a bit of creativity to get what one needs
First of all, I have to mention that I truly appreciate that you've put in so much time and effort to help me with my skin which is just a project I'm doing as a pastime. So genuinely, thanks a lot! :rosegift:
Now coming back to the matter, last night I managed to get the search box and suggestions working fine with the last regex you suggested. It shows a maximum of 4 suggestions and when clicked, it stores the name in the "Team" variable. All I have left is for it to get the corresponding URL too. So, although your new method looks even better, I'd rather not change everything again 'cause it's so close to completion.
User avatar
Yincognito
Rainmeter Sage
Posts: 6829
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Help creating a search box that searches a .txt file

Post by Yincognito »

MazinLabib10 wrote: May 27th, 2023, 11:35 am First of all, I have to mention that I truly appreciate that you've put in so much time and effort to help me with my skin which is just a project I'm doing as a pastime. So genuinely, thanks a lot! :rosegift:
Now coming back to the matter, last night I managed to get the search box and suggestions working fine with the last regex you suggested. It shows a maximum of 4 suggestions and when clicked, it stores the name in the "Team" variable. All I have left is for it to get the corresponding URL too. So, although your new method looks even better, I'd rather not change everything again 'cause it's so close to completion.
Yeah, no problem - it makes sense. My new method is much more suited for a single scrolling meter scenario anyway, which I generally prefer in these situations due to covering an unlimited amount of values without taking a proportionally larger space on the screen. If your case is based on some popup / box where you display all the 4 suggestions at the same time for obvious click and action reasons, then my old method is definitely the way to go (even though the new method can be made to work that way too) - which is why I mentioned it first, to begin with. :thumbup:
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth