Matching a trigger multiple times on the same line

Posted by Mudguy on Mon 19 Feb 2007 03:32 PM — 4 posts, 26,178 views.

#0
I have a trigger that looks like this:

<triggers>
  <trigger
   enabled="y"
   expand_variables="y"
   keep_evaluating="y"
   match="[ *(\d+)] (ginseng root|valerian|goldenseal root|myrrh gum)"
   regexp="y"
   repeat="y"
   send_to="12"
   sequence="100"
  >
  <send>do_stuff()</send>
  </trigger>
</triggers>

and I am wanting it to match exactly four times on this text from the server:

  [  11] bayberry bark      [   7] bellwort flower    [  10] black cohosh
  [  80] bloodroot leaf     [   8] blueberry          [  39] echinacea
  [  15] ginseng root       [  16] goldenseal root    [   8] hawthorn berry
  [  14] irid moss          [  18] kelp               [  14] kola nut
  [ 200] kuzu root          [ 498] lady's slipper     [  10] leather
  [  30] lobelia seed       [  16] myrrh gum          [   3] piece of stag's
  [ 101] prickly ash bark   [  11] prickly pear       [  11] sileris
  [  11] skullcap           [  10] slippery elm       [ 237] valerian
  [  54] venom sac          [   2] yellow ink

However, it doesn't work like I want it to on line 3 of the input - it matches on the ginseng entry, but not on the goldenseal entry. This is something of a showstopper.

What is the problem here, and how do I fix it? I have ticked the 'Repeat on same line' box in the trigger creation dialogue box, and as far as I can see, the trigger's not greedy enough to eat the whole line.
Amended on Mon 19 Feb 2007 03:34 PM by Mudguy
USA #1
My trigger for that looked like this, and it worked all the time for capture:

<triggers>
  <trigger
   enabled="y"
   expand_variables="y"
   ignore_case="y"
   keep_evaluating="y"
   match="[(?:\s+)(\d+)](?:\s+)(.*?)(?:\s+)"
   regexp="y"
   repeat="y"
   send_to="12"
   sequence="100"
  >
  <send>if "%2" in world.GetVariable("herblist"):
 if int("%1") &lt; 100:
  al_alert(string.capitalize("%2") + "  left!")</send>
  </trigger>
</triggers>

And in the variable for 'herblist' was just what herbs I wanted to keep track of:

<variables>
  <variable name="herblist">bayberry bark|bellwort flower|echinacea|irid moss|prickly ash bark|skullcap|valerian|ginseng root|hawthorn berry|kelp|lobelia seed|prickly pear|sileris|slippery elm|cohosh|goldenseal root|kola nut</variable>
</variables>


Hope that helps.
USA #2
Well.. My guess is that its the spaces. I.e., its actually seeing:

Find:
ginseng +
root|valerian|goldenseal +
root|myrrh gum

This is why Rakon's works and yours doesn't. His is looking for the name "after" the lines are already captured. The solution is to use:

((ginseng root)|valerian|(goldenseal root)|(myrrh gum))

I think. This will wreck havoc with your wildcard count, but it should specifically "require" that each phrase be treated as a complete match, and not as three seperate sets of options.

Frankly, the wildcard issue always bugged me... Some triggers can get unbelievably complex due to this and until recently there was no way to "get" the wildcards over a certain number, so you have to compromise. Now.. You are *still* stuck trying to figure out "which" wildcard is the right one, because () also designates a wildcard, not just a grouping. In a long complex pattern with "many" optionals, you might have 5 actual wildcards, but 50 "subgroups" and the wildcard locations needed to return the "intended" stuff might be 4, 15, 25, 43 and 48, of which "4" would have previously been the only one you could get to. Rakon's solution works better, but has the flaw that if someone uses the same identical indention and layout for say, a shop, the script is going to waste time parsing things it doesn't need to, looking for something in the shop that isn't there. You can simply use an alias that enables the trigger when needed, then disables it when something else happens, but its not always possible to figure out what that "something else" is.

Your kind of stuck either doing it so it can't match, then fiddling with the wildcards to get what you intended in the first place, letting the trigger match things it isn't supposed to, or creating some set of rules to enable/disable the trigger. All three methods have annoyances.
Australia Forum Administrator #3
Quote:

... it matches on the ginseng entry, but not on the goldenseal entry ...


The real problem here is that the trigger processing optimizes certain things. In particular, "repeat on same line" is intended for colouring multiple words. If you set a "change colour", like this you will see it:


custom_colour="2"


You can see from that, that it does indeed match twice when expected.

However, and this is what is catching your script, it then calls the script once, per line. Thus the script is called for the first match only.

The good news is that there is a fairly simple way around this.

To do this you will need a script file (not just "send to script"), which I am guessing you have from the way you call do_stuff ().

Next you need to change your trigger slightly to call a script function by name, rather than the way you did, like this:


<triggers>
  <trigger
   enabled="y"
   expand_variables="y"
   keep_evaluating="y"
   match="\[ *(\d+)\] (ginseng root|valerian|goldenseal root|myrrh gum)"
   name="herb_trigger"
   regexp="y"
   repeat="y"
   script="herb_trigger_script"
   sequence="100"
  >
  </trigger>
</triggers>



I also gave it a name, for reasons that will become apparent (the name "herb_trigger").

Now inside the script file we need to reprocess that line, using the PCRE regexp handler, built into the Lua script. This is an exact copy of the one that MUSHclient uses, and thus we can pass to it the same regular expression from the trigger.

To allow for future enhancement, I pull the regexp directly from the trigger itself.

Here is the script:


function herb_trigger_script (name, line, wildcards)
 
  -- function to handle individual matches
  local function one_herb (m, t)
    print ("count", t [1])
    print ("herb",  t [2])
  end -- one_herb 

  local re = rex.new (GetTriggerInfo (name, 1))  -- get the match text, make into a regexp
  re:gmatch (line, one_herb)  -- match repeatedly, call function

end -- herb_trigger_script 


What this does is use re:gmatch to re-match on the trigger line, calling the function one_herb for each match. Then you can do whatever you want with the match. The function one_herb is passed a table of substrings (t), out of which I extract the count and herb name.

You could optimize this slightly by moving the call to rex.new out of the function, and simply pass the name of the trigger to it, to save compiling the regular expression every time, but it might not make much difference.

My output was:


  [  11] bayberry bark      [   7] bellwort flower    [  10] black cohosh
  [  80] bloodroot leaf     [   8] blueberry          [  39] echinacea
  [  15] ginseng root       [  16] goldenseal root    [   8] hawthorn berry
count 15
herb ginseng root
count 16
herb goldenseal root
  [  14] irid moss          [  18] kelp               [  14] kola nut
  [ 200] kuzu root          [ 498] lady's slipper     [  10] leather
  [  30] lobelia seed       [  16] myrrh gum          [   3] piece of stag's
count 16
herb myrrh gum
  [ 101] prickly ash bark   [  11] prickly pear       [  11] sileris
  [  11] skullcap           [  10] slippery elm       [ 237] valerian
count 237
herb valerian
  [  54] venom sac          [   2] yellow ink


Quote:

Some triggers can get unbelievably complex due to this and until recently there was no way to "get" the wildcards over a certain number, so you have to compromise.


My solution above shows how you can achieve this without too much complexity.