Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Regex test not working for handout name

1593998868
timmaugh
Pro
API Scripter
Given a situation where a character can have an associated config file for a script: Character name: Brad the Hirsute Config Handout name: IAConfig-Brad the Hirsute ...I have a script that listens for a character name change so that it can prompt if there is a config file to rename, too. So if Brad the Hirsute becomes Brad the Bald and he had a config file out there under his old name, the player should get a prompt asking if they want to rename the config. The problem is, I'm getting no hits on my regex test... const handleCharNameChange = (character, prev) => { let oldrx = new RegExp(`/(iaconfig-)(${prev.name})/`, 'gi'); // group 1: iaconfig- from iaconfig-prevName // group 2: prevName from iaconfig-prevName let newrx = new RegExp(`/(iaconfig-)(${character.get('name')})/`, 'gi'); // group 1: iaconfig- from iaconfig-characterame // group 2: charactername from iaconfig-charactername let testhos = findObjs({ type: "handout" }).filter((h) => { return oldrx.test(h.get('name')) }); log(`There are ${testhos.length} handouts satisfying the first naming test.`);             // returns 0 testhos = findObjs({ type: "handout" }).filter((h) => { return oldrx.test(h.get('name').toLowerCase()) }); log(`There are ${testhos.length} handouts satisfying the second naming test.`);             // returns 0 The text of the name is in there, as this formation works: let testhos = findObjs({ type: "handout" })             .filter((h) => { return h.get('name').toLowerCase() === `iaconfig-${ prev.name.toLowerCase() }` })             .map((h,i) => { log(`(${i}): ${h.get('name')}`) });             // outputs "(1): IAConfig-Brad the Hirsute" I have tried dropping the name to lower case when the regex is made (shouldn't make a difference... and it didn't). I tried including a non-capturing group with an or condition of either a hyphen or the html entity for a hyphen. I tried using the decodeUrlEncoding() function from the Utility Functions in the API reference (even though that was listed to be for the notes and GM notes fields). None of that seems to work. I can't think it's something about encoding in the field since the explicit name test works (flattening everything to lower case and skipping the regex completely), so what am I doing wrong in the regex?
1594004582
The Aaron
Roll20 Production Team
API Scripter
Try: let oldrx = new RegExp(`(iaconfig-)(${prev.name})`, 'gi');
1594005029
timmaugh
Pro
API Scripter
Oh,... Great googa-mooga.. Can't believe I did that. Can't test right this moment, but I'm sure that's going to work. Thanks! ...and also, forget this thread ever happened. ...this is not the thread you are looking for.
1594015181
The Aaron
Roll20 Production Team
API Scripter
Hahaha!  Ironically, it WAS the thread I was looking for... I can't count the number of times I've done that. (Well, I probably could, it would be every time I've used RegExp()...).  Whenever I have reflex problems, I drop them into regex101.com or similar just to make sure I'm not doing something silly...again. =D
1594038261
timmaugh
Pro
API Scripter
Yes! I've had two tabs open and devoted to regex101.com, and it just made me more confu-strated (a common portmanteau state when it comes to regex, I think). My brain kept short-circuiting the problem: log(`/(iaconfig-)(${prev.name})/`) \\: /(iaconfig-)(Brad the Hirsute)/ Brain: You know, that looks right. Me: I know Brain: It *looks* like a regex. Smells like a regex. Tastes like a-- Me: Brain! Brain: Sorry. ...It *does* look right, though. I mean, see, it's got the slanty lines and everything. Me: Yeah, you need those when you do a regex. Brain: You know what you should do? You should take it to regex101.com. You already have a tab open. Me: You're right, I do-- Brain: In fact, you have, like, 3 tabs open for regex101. Me: I do not. I have two. Brain: It's because you're not smart. That's why I have to do all the thinking around here. Me: ... Brain: Me.You ...do... realize... what you're saying? Brain: Me: ...about yourself? Brain: Hey, you know what you should do? You should take that to regex101. Me: Yeah... let me do that-- Brain: Just remember -- and this is very important: regex101 does NOT need the slashes. Since that's a part of them constructing the regular expression, you don't need to include the slashy bits. Me: No slashy bits? Brain: No slashy bits. Me: Just everything between. Brain: (nodding) Everything between. Me: It's a good thing I have you, Brain. Brain: Well, we can't all be smart. Without me, you're the sort of person who would have 2 tabs of regex101 open. Me: I did that... *with* you-- you know what, let's just go test. I'm sure you'll spot the problem, Brain. ### END SCENE ###
1594039025
The Aaron
Roll20 Production Team
API Scripter
=D
1594066596
timmaugh
Pro
API Scripter
So that's working great... but I seem to be getting only one return in the test of the handouts. Given config handouts like this: (which, in case that later gets deleted, is an image that shows 1 config file for Brad the Hirsute and 2 config files for Brad the Bald) If I name Brad from Brad the Hirsute to Brad the Bald (or the reverse), I want to throw a message at the chat to point out that there are multiple config files the player/gm should manage, and it should have an api chat button for each (to open). The problem is I'm not getting the second return. This image should have three buttons (one for each config handout), but it only has 2: Here is the code where I get the handouts under consideration: const handleCharNameChange = (character, prev) =&gt; { log(`===== HANDLE CHAR NAME CHANGE =====`); let oldrx = new RegExp(`(iaconfig-)(${escapeRegExp(prev.name.toLowerCase())})`, 'gi'); // group 1: iaconfig- from iaconfig-prevname // group 2: prevName from iaconfig-prevname let newrx = new RegExp(`(iaconfig-)(${escapeRegExp(character.get('name').toLowerCase())})`, 'gi'); // group 1: iaconfig- from iaconfig-characterame // group 2: charactername from iaconfig-charactername let oldhos = findObjs({ type: "handout" }).filter((h) =&gt; { return oldrx.test(h.get('name')) }); if (oldhos.length===0) return; // no config handouts found state.insertarg[character.get('name')] = state.insertarg[prev.name] || { cfgObj: Object.assign({}, state.insertarg.global.cfgObj) }; let cfgObj = state.insertarg[character.get('name')].cfgObj; let msg = "", btn = "", api = "", newhos = findObjs({ type: "handout" }).filter((h) =&gt; { return newrx.test(h.get('name')) }); log(`Newhos Length: ${newhos.length}`); log(`Oldhos Length: ${oldhos.length}`); log(newhos.length + oldhos.length); if (newhos.length + oldhos.length &gt; 1) { // detect conflicts msg = `That character has multiple script configurations, either under ${character.get('name')} or under ${prev.name}. You should only keep one, and it should be named IAConfig-${character.get('name')}. Open handouts for comparison?&lt;br&gt;`; api = `<a href="http://journal.roll20.net/handout/`" rel="nofollow">http://journal.roll20.net/handout/`</a>; btn = oldhos.reduce((a, v, i) =&gt; { return a + apibutton_api({ bg: cfgObj.bg, api: api + v.id, label: `(${i + 1}) ${v.get('name').replace(/iaconfig-/i, "")}`, css: "min-width: 25px;" + cfgObj.css }) + ' ' }, ""); btn = newhos.reduce((a, v, i) =&gt; { return a + apibutton_api({ bg: cfgObj.bg, api: api + v.id, label: `(${i + 1}) ${v.get('name').replace(/iaconfig-/i, "")}`, css: "min-width: 25px;" + cfgObj.css }) + ' ' }, btn); } else { // only get here if there is a config for the old name but not the new (ie, no collision) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;... // this part works &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} The three log statements in the middle produce this output: "Newhos Length: 1" "Oldhos Length: 1" 2 In whatever direction I rename this character, there should be 3 buttons, and one of those sets of handouts (new/old) should have 2 entries... but I can't see what I'm doing wrong to miss it. It "fails" (that is, only produces 1 result when there should be 2) in both directions, whether he's being named from Hirsute to Bald or the reverse.
1594072075
The Aaron
Roll20 Production Team
API Scripter
Hmm. Long shot, but try putting \s* on either end to eat white space. I usually transform bands to a key format, kind of like slugging titles, then match on that. Usually that's when taking input from users, but you could try a similar "fuzzy" approach just to see if you can isolate a difference between the two names that isn't obvious.&nbsp;
1594133182
timmaugh
Pro
API Scripter
OK, I'm beginning to think the problem is not on my end of the code. I tried Aaron's suggestion of padding with \s*, and I even did a search to replace any white space character within the character's name with \s when the regex is built. My current regex builds look like this: let oldrx = new RegExp(`\s*(iaconfig-)(${escapeRegExp(prev.name).replace(/\s/g, `\\s`).toLowerCase()})\s*`, 'gi'); let newrx = new RegExp(`\s*(iaconfig-)(${escapeRegExp(character.get('name')).replace(/\s/g, `\\s`).toLowerCase()})\s*`, 'gi'); That returns the same number of handouts (1 for each name, when there should be 2 for 'Brad the Bald'). But, here's the thing... I renamed the first Bald handout from "IAConfig-Brad the Bald" to "IAConfig-Brad the Beautiful", then I renamed the character (toggling between Brad the Hirsute and Brad the Bald) to trigger my message. I got 1 return for each handout name (Hirsute and Bald). That means the Bald handout in place registered to the findObjs() search. Next, I renamed the remaining "IAConfig-Brad the Bald" handout to be "IAConfig-Brad the Shiny" and I renamed the "IAConfig-Brad the Beautiful" handout back to "IAConfig-Brad the Bald." Then I renamed the character to trigger the message... and I got 1 return for each (old/new name). So that means that *this* Bald handout ALSO registers to the findObjs() search. I just can't get them BOTH to register at the same time... In fact, when I do a select all/copy on one bald handout name and do a select all/paste into the other bald handout name, they should be EXACTLY the same. But when I toggle the character's name to trigger the message, I still only have 1 return. It's like the findObjs() function is somehow optimized to expect unique names and only returning 1 even though the id property is supposed to be the unique identifier. Can anyone confirm this, or suggest another test?
1594144344

Edited 1594150448
The Aaron
Roll20 Production Team
API Scripter
If you don't have too many, you can try logging all the names and ids and see if it's showing up twice: findObjs({type: "handout"}).forEach( h =&gt; log(`${h.id} ::= [${h.get("name")}]`));
1594145292
timmaugh
Pro
API Scripter
Yep... looking like something on the internal side. This code: log(`===== TEST ALL WITH OLD REGEX =====`); findObjs({ type: "handout" }).map((h) =&gt; { log(`${oldrx.test(h.get('name'))}: ${h.get('name')}`); }); log(`===== TEST ALL WITH NEW REGEX =====`); findObjs({ type: "handout" }).map((h) =&gt; { log(`${newrx.test(h.get('name'))}: ${h.get('name')}`); }); ...should walk through all of the handouts and tell me if they pass each test, or not. No matter which direction I name the character, these two lines should output the same number of results. They don't. Naming from Hirsute to Bald (meaning Hirsute is the old regex and Bald is the new), the map of the OLD produces 46 returns (including only 1 entry for "IAConfig-Brad the Bald"). The map of the NEW produces 47 returns (including 2 entries for "IAConfig-Brad the Bald," one of which reports as true and one which reports as false). Here is the relevant bit from that log (leaving out the handouts that aren't related): "===== TEST ALL WITH OLD REGEX =====" "false: IAConfig-Global" "true: IAConfig-Brad the Hirsute" "false: IAConfig-Brad the Bald" "===== TEST ALL WITH NEW REGEX =====" "false: IAConfig-Global" "false: IAConfig-Brad the Hirsute" "true: IAConfig-Brad the Bald" "false: IAConfig-Brad the Bald"
1594151796

Edited 1594151856
timmaugh
Pro
API Scripter
I added that line in, with the log heading of "===== ALL WITH IDs =====", and my results are: Number of Handouts Mapped Against Old Regex: 47 Number of Handouts Mapped Against New Regex: 46 Number of Handouts forEach With IDs: 47 In this case, I was naming from Bald to Hirsute, so I was naming AWAY from the 2 config handouts to the 1 (Bald = OLD, Hirsute = NEW). The relevant log: "===== TEST ALL WITH OLD REGEX =====" "false: IAConfig-Global" "false: IAConfig-Brad the Hirsute" "true: IAConfig-Brad the Bald" "false: IAConfig-Brad the Bald" "===== TEST ALL WITH NEW REGEX =====" "false: IAConfig-Global" "true: IAConfig-Brad the Hirsute" "false: IAConfig-Brad the Bald" "===== ALL WITH IDs =====" -MB5sBXs6QPLwWdZaLMn ::= IAConfig-Global -MBJyPNplmaMigSg7i7_ ::= IAConfig-Brad the Hirsute -MBND38-6Z-FqlDz-WJY ::= InsertArgsEphemeral -MBZbK3Wevkoz_l_Mnnt ::= IAConfig-Brad the Bald -MB_O9BYePQDyYnROfkp ::= IAConfig-Brad the Bald It matches my expectation that the side which should have 2 positive matches (in this case, OLD), does indeed have 2 hits, but only one is showing as having passed the test. The other side (NEW), only sees one of them and it is failing (as it should). I keep thinking that somehow a filter condition is being set up as a pseudo key for the length of the filter operation, except that this test isn't using a filter() but a map() instead. I'll test with completely new handouts and name collisions, and I will test the forEach() call in Aaron's suggestion by putting the regex test inside there. We know, now, that it is seeing every handout. Nothing about the regex test should operate in any sort of exclusionary/filtering way against the dataset... right?
1594152919

Edited 1594155668
timmaugh
Pro
API Scripter
OK, I used Aaron's suggested forEach() and put my regex tests inside. Here are the lines: log(`===== ALL WITH IDs =====`); findObjs({ type: "handout" }).forEach(h =&gt; { &nbsp;&nbsp;&nbsp;&nbsp;log(`${h.id} ::= ${h.get("name")}`); log(`OLD::= ${oldrx.test(h.get('name')) ? "true " : "false"}: ${h.get('name')}`); log(`NEW::= ${newrx.test(h.get('name')) ? "true ": "false"}: ${h.get('name')}`); }); That produces 141 lines out output (expected for 47 handouts, 3 lines each), but I think that just demonstrates that it isn't the regex that is introducing the limitation (dropping the handout with the duplicate name from one or another map() functions). Nevertheless, it still shows only one of the duplicate-named handouts satisfying the regex: ===== ALL WITH IDs ===== -MB5sBXs6QPLwWdZaLMn ::= IAConfig-Global OLD::= false: IAConfig-Global NEW::= false: IAConfig-Global -MBJyPNplmaMigSg7i7_ ::= IAConfig-Brad the Hirsute OLD::= true : IAConfig-Brad the Hirsute NEW::= false: IAConfig-Brad the Hirsute -MBZbK3Wevkoz_l_Mnnt ::= IAConfig-Brad the Bald OLD::= false: IAConfig-Brad the Bald NEW::= true : IAConfig-Brad the Bald&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;======== THIS ONE MATCHES -MB_O9BYePQDyYnROfkp ::= IAConfig-Brad the Bald OLD::= false: IAConfig-Brad the Bald NEW::= false: IAConfig-Brad the Bald&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;======== THIS ONE DOESN'T MATCH
1594154922

Edited 1594158383
timmaugh
Pro
API Scripter
Another test, more results... I created three new handouts manually, using very strict procedure: Add a handout, name it ("Worf"), copy the name to the clipboard, put in All Players journals, and designate All Players can control Add a handout, paste in clipboard ("Worf"), put in All Players journals, and designate All Players can control Add a handout, name it ("Mog"), put in All Players journals, and designate All Players can control EDIT: These handout names were prefixed with "IAConfig-", but otherwise, the above is accurate. I then named a character to Worf, and then from Worf to Mog. Where before the discrepancy in the number of handouts processed in the map() functions was 1 (47 vs 46), this time it was 2 (1 each for the duplicates of Bald and Worf). Also, the combined forEach() test produced similar results: "-MBeuvX7WaW2Cyj3M4Sq ::= IAConfig-Worf" "OLD::= true : IAConfig-Worf"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;======== THIS LINE "NEW::= false: IAConfig-Worf" "-MBevBsBxpwQmH8GFqao ::= IAConfig-Worf" "OLD::= false: IAConfig-Worf"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;======== SHOULD MATCH THIS LINE "NEW::= false: IAConfig-Worf" "-MBevFd7FcNl3KNk8t03 ::= IAConfig-Mog" "OLD::= false: IAConfig-Mog" "NEW::= true : IAConfig-Mog" New thought... does the Regex have to be reset between tests? Is it interpreting its last match as the starting point for its future searches (even though we're testing different strings), so after it returns a match it doesn't match that same title again the second time...?
1594155552
timmaugh
Pro
API Scripter
I know... you're all terribly thrilled with what is becoming a field journal of code troubleshooting, but I think I'm making progress, and maybe these notes will help someone running into the same problem in the future. Testing that last idea (that the regex would need to be somehow "started-over"), I did a brute-force test by moving the construction of the regex inside the forEach() call: log(`===== ALL WITH IDs =====`); findObjs({ type: "handout" }).forEach(h =&gt; { &nbsp;&nbsp;&nbsp;&nbsp;oldrx = new RegExp(`\s*(iaconfig-)(${escapeRegExp(prev.name).replace(/\s/g, `\\s`).toLowerCase()})\s*`, 'gi'); &nbsp;&nbsp;&nbsp;&nbsp;newrx = new RegExp(`\s*(iaconfig-)(${escapeRegExp(character.get('name')).replace(/\s/g, `\\s`).toLowerCase()})\s*`, 'gi'); &nbsp;&nbsp;&nbsp;&nbsp;log(`${h.id} ::= ${h.get("name")}`); &nbsp;&nbsp;&nbsp;&nbsp;log(`OLD::= ${oldrx.test(h.get('name')) ? "true " : "false"}: ${h.get('name')}`); &nbsp;&nbsp;&nbsp;&nbsp;log(`NEW::= ${newrx.test(h.get('name')) ? "true ": "false"}: ${h.get('name')}`); }); And the output seems to be what I would want: ===== ALL WITH IDs ===== "-MBeuvX7WaW2Cyj3M4Sq ::= IAConfig-Worf" "OLD::= false: IAConfig-Worf" "NEW::= true : IAConfig-Worf"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;======== SHOWS AS TRUE "-MBevBsBxpwQmH8GFqao ::= IAConfig-Worf" "OLD::= false: IAConfig-Worf" "NEW::= true : IAConfig-Worf"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;======== ALSO SHOWS AS TRUE "-MBevFd7FcNl3KNk8t03 ::= IAConfig-Mog" "OLD::= true : IAConfig-Mog" "NEW::= false: IAConfig-Mog" So that's a good result, but a ton of overhead to recreate the expression every time. For those who know Regular Expressions, is there a flag or a command switch to tell it to start fresh at the beginning each time? Or, given all of this data, can someone make a better diagnosis of what is going on?
1594156238
The Aaron
Roll20 Production Team
API Scripter
That's really strange. &nbsp;I'm gong to have to try and repeat your tests when I get my laptop back.&nbsp;
1594158516
timmaugh
Pro
API Scripter
Sounds good. I had wondered if the String.match() function (fed the regex) would work differently, since that would be the string initiating the search, rather than the regex initiating a .test(). The results were the same. I'll keep plugging away at it, but let me know if you find anything! Thanks!
1594160319

Edited 1594209111
timmaugh
Pro
API Scripter
Another test: using map(), but rebuilding the regex new every time before logging. That does not behave any differently than pre-building the regex outside of the map (which is to say: bad). So maybe it seems that it isn't a matter of the regex needing to be "reset" so much as it is some bad interaction of the regex with the map() and reduce() functions...? So far the only way that has worked completely is to use the forEach and build the regex new every time.
1594213293

Edited 1594213893
timmaugh
Pro
API Scripter
I've got it serviceable, but not solved. Working off information from a stack overflow question , I removed the global flag from the regex. That immediately made my filter statement correct (the actual purpose of my code that, because it wasn't working, was leading to all of the testing)... which is to say that the identically named handouts show up using a formation like: let oldrx = new RegExp('..........','i');&nbsp;&nbsp;&nbsp;&nbsp;// &lt;===== no global flag let oldhos = findObjs({ type: "handout" }).filter( (h) = &gt; {return oldrx.text(h.get('name')) }); Apparently the filter() implements the regex test in a way that doesn't improperly limit the number of returns (it limits exactly as it should, testing each string fresh). My api buttons are built on a reduce() of that array, so that is straightforward and a 1:1 output: (which, in case it gets deleted later, is an image showing my feedback to the user and including three buttons - 2 for "Worf" and 1 for "Mog") I think the global flag accounts for why the filter() had the wrong number, before (more returns seemed to fail the test), as well as why the tests were failing even after after Aaron's suggested forEach() made sure we were getting the full number of returns (putting the test inside the forEach() gave us a return for every handout, but it showed that some of the handouts were simply failing the regex test). I think all of that was the global flag. What is still squirrelly is that simply running a regex test in a straight map() call reduces the number of returns in a way that it shouldn't. If I run a regex test against data diagnostically, it shouldn't interact with an iteration *over* that data. If I return all of my handouts to an array and then report the length, I get 49. If I then use the map() and regex test over that array and log every line with a true/false of whether it passed, I only get 48 returns (duplicate names for Bald/Hirsute stay in if the test has to do with Worf/Mog, but the duplicate UNDER CONSIDERATION gets dropped).
1594223109
The Aaron
Roll20 Production Team
API Scripter
Bizarre. I'm looking forward to digging into that...
1594238568

Edited 1594242906
timmaugh
Pro
API Scripter
I set up a fiddle &nbsp; to see if it was something related to the interaction of map and a regex, but it seems to function as you'd expect. At that fiddle, the original crew includes a split duplicate of Worf and a back-to-back duplicate of Gowron, just to see if the arrangement of the elements in the array matter... but none of the crew members get dropped during the map operation. You can add more crew to test, too. Hopefully you can figure out something, here, Aaron, because I feel like this walks the problem up to the door of the Roll20 findObjs() function, but I have been known (as demonstrated in this thread, above) to be wrong. =p I'll be interested to see what you find out!