Regular Expression

Discuss macro implementations, ask for macro help (to share your creations, see User Creations, probably either Campaign Frameworks or Drop-in Resources).

Moderators: dorpond, trevor, Azhrei, giliath, jay, Mr.Ice

Post Reply
neofax
Great Wyrm
Posts: 1694
Joined: Tue May 26, 2009 8:51 pm
Location: Philadelphia, PA
Contact:

Regular Expression

Post by neofax »

I am writing a macro for RPTroll's Savage Worlds FW that takes a statblock and turns it into a token. My problem is that many of the stats have a newline character in them. If I take out the newline my RegEx works, but I am having trouble looking for the "true" end of sub-block of the stats as the newline can fall anywhere within the line. I know I can look for each stat seperately, but I would like to figure this out. Here is my RegEx and statblock.

Code: Select all

Agility (d[0-9]+), Smarts (d[0-9]+), Spirit (d[0-9]+), Strength (d[0-9]+), Vigor (d[0-9]+)


Code: Select all

LESHIJ
Leshiji (less-idg-ee) are wood spirits. They inhabit
all the forests of Rassilon, typically tending an area of
100 square miles. They are also known as “fathers of
the wood wights,” though in truth the role they perform
is more like a supervisor, watching over and aiding
a gang of wood wights within their domain. Wood
wights call them “forest lords” out of respect for their
powers.
As protectors of both forest and animals, their form is
a blend of the two. Their usual form is that of a humansized
fi gure with bark-like skin, and hair and beard of
living grass, but with a tail, hooves, and horns.
Attributes: Agility d6, Smarts d10, Spirit d8, Strength d8,
Vigor d10
Skills: Fighting d6, Guts d6, Intimidation d8, Notice d8,
Spellcasting d12, Stealth d8, Survival d8, Swimming d8,
Tracking d8
Pace: 6; Parry: 5; Toughness: 7
Gear: Large club (Str+d8).
Special Abilities:
* Alter Size: An as action, a leshij can alter its size
up or down. The creature makes a Smarts roll,
with each success and raise indicating a one point
change in Size. For every point of Size change,
Strength and Toughness increase or decrease one
step (above d12, Strength rises a single point—
d12+1, d12+2, and so on). They gain the Large
Monstrous Ability at Size +4, Huge at +8, and Gargantuan
at +12.
* Forest Born: Leshiji never suffer diffi cult ground
penalties in forests.
* Powers: Leshiji can use the following spells: animate
war tree (song of awakening), armor (bark
skin), barrier (wall of wood), bladebreaker (only
works on axes), confusion (victim hears voices
on the wind), defl ection (branches intercept attacks),
disease (wasting sickness), healing (plants
and animals only), entangle (trees grab at targets),
fog cloud (special: trees move to block out light),
growth/shrink (song of changing), nightmare (victim
dreams he is wandering through a dark, everchanging
forest), refuge (trees form a shelter), summon
elemental (wood only).
* Shapeshift: Leshiji know the shape change power
but can only assume the form of mundane plants and
animals. They use Smarts as their arcane skill die.
* Speak with Nature: Leshiji can communicate with all
mundane animals and plants.
* Summon Beasts: A leshij can summon forest creatures
to him by singing. This requires an action and a
Smarts roll at –2. If successful, 1d6+2 wolves or 1d4
bears come from the surrounding wilds in 1d6+2
rounds.
Image
Time-Zone information UTC -5

User avatar
aliasmask
Deity
Posts: 8603
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: Regular Expression

Post by aliasmask »

Do skills always fall after the attributes? You can have the regex remove newlines between attributes and skills, then re-add one before skills.

neofax
Great Wyrm
Posts: 1694
Joined: Tue May 26, 2009 8:51 pm
Location: Philadelphia, PA
Contact:

Re: Regular Expression

Post by neofax »

Yes, on all of the statblocks I have seen. Granted, other 3PP may use a slightly different version and I know HeroLabs does, but for the most part the fall directly after.

Also, I am getting an error with this one in MapTool:

Code: Select all

[H: id = strfind(statblock, "^([A-Z ]+)$")]
java.lang.IllegalArgumentException: Illegal group reference error executing expression id = strfind(statblock, "^([A-Z ]+)$").
Image
Time-Zone information UTC -5

User avatar
aliasmask
Deity
Posts: 8603
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: Regular Expression

Post by aliasmask »

You need \ before the $, maybe \\.

User avatar
Azhrei
Site Admin
Posts: 12057
Joined: Mon Jun 12, 2006 1:20 pm
Location: Tampa, FL

Re: Regular Expression

Post by Azhrei »

Yes, two. The first backslash protects the second one from being interpreted by the MTscript parser. Then that backslash and the dollar sign are passed to the Java regex routines which see the backslash as protecting the dollar sign (ie. removing any special meaning it might otherwise have).

neofax
Great Wyrm
Posts: 1694
Joined: Tue May 26, 2009 8:51 pm
Location: Philadelphia, PA
Contact:

Re: Regular Expression

Post by neofax »

I removed it and it worked fine. For some reason when I put the statblock in the input dialog and it subsequently writes the info to the GM Notes part of the token, MapTool strips out the CRLFs from the data. This works in my favor. I am now in the process of changing all of the data in the framework to use a "_" for spaces so I can then do a replace for the statblock and find all of the Edges/Hindrances. Then I should be like 80% done and will need help on making the macro leaner and more efficient. Here is what I have so far:

Code: Select all

[H: "<!-- Statblock2Token v0.1
Changes: Original
Macro takes a Savage Worlds statblock as input and updates selected token with various stats.
Macro assumes Savage Worlds properties in the selected token.
Statblock format should mirror Savage Worlds bestiary format.
Not handled: Special Abilities

            *CURRENTLY UNUSED*
General concepts. In order to isolate sections of the statblock, we generate a regular expression that includes all typical keywords and use it when searching for the end of a statblock section. This will prevent a string from running off into the next section.
            *CURRENTLY UNUSED*
-->"]

[H: ids = getSelected()]
[H: abort(if(ids ==  "", 0, 1))]
[H: keywords = "(Attributes|Skills|Pace|Parry|Toughness|Gear|Special Abilities)"]
[H: "<!-- TOKEN SETUP BEGIN -->"]
[H: status = input("statblock|Insert statblock here|Enter statblock|TEXT|WIDTH = 40")]
[H: abort(if(status < 1, 0, 1))]
[H: setPropertyType("Basic")]
[H: propnames = getPropertyNames()]
[H, foreach(propname, propnames): resetProperty(propname) ]
[H: setGMNotes(statblock)]
[H: output = "Token Updated: <BR />"]
[H: id = strfind(statblock, "^([A-Z ]+) ")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [tName = lower(getGroup(id, 1, 1))]
   [tName = upper(tName, 1)]
   [output = "Name: "+tName+", "]
   [Race = tName]
   [setName(tName)]
   }]
[H: "<!-- TOKEN SETUP END -->"]

[H: "<!-- SENSES BEGIN -->"]
[H: id = strfind(statblock, "Senses ([lL]ow-?light|[dD]arkvision|[iI]nfravision)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [sighttype = upper(substring(getGroup(id, 1, 1), 0, 1))+substring(getGroup(id, 1, 1), 1)]
   [sighttype = replace(sighttype, "-", "")]
   [setSightType(sighttype)]
   [setHasSight(1)]
   [output = output+"Sighttype: "+sighttype+", "]
   }]
[H: "<!-- SENSES END -->"]

[H: "<!-- STATS BEGIN -->"]
[H: id = strfind(statblock, "Agility (d[0-9]+), Smarts (d[0-9]+), Spirit (d[0-9]+), Strength (d[0-9]+), Vigor (d[0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Agility = getGroup(id, 1, 1)]
   [IF(Agility == ""): Agility = "d4e"]
   [Smarts = getGroup(id, 1, 2)]
   [IF(Smarts == ""): Smarts = "d4e"]
   [Spirit = getGroup(id, 1, 3)]
   [IF(Spirit == ""): Spirit = "d4e"]
   [Strength = getGroup(id, 1, 4)]
   [IF(Strength == ""): Strength = "d4e"]
   [Vigor = getGroup(id, 1, 5)]
   [IF(Vigor == ""): Vigor = "d4e"]
   [output = output+"Agil: "+Agility+", Smrt: "+Smarts+", Sprt: "+Spirit+", Str: "+Strength+", Vig: "+Vigor+"<BR />"]
   }]
[H: "<!-- STATS END -->"]

[H: id = strfind(statblock, "Charisma: ([0-9\-]+);")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Charisma = getGroup(id, 1, 1)]
   [output = output+"Charisma: "+Charisma+", "]
   }]

[H: id = strfind(statblock, "Pace: ([0-9]+);")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Pace = getGroup(id, 1, 1)]
   [output = output+"Pace: "+Pace+", "]
   }]

[H: id = strfind(statblock, "Parry: ([0-9]+);")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Parry = getGroup(id, 1, 1)]
   [output = output+"Parry: "+Parry+", "]
   }]

[H: id = strfind(statblock, "Toughness: ([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Toughness = getGroup(id, 1, 1)]
   [output = output+"Toughness: "+Toughness+"<BR />"]
   }]

[H: "<!-- SKILLS BEGIN -->"]
[H: id = strfind(statblock, "Fighting (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Fighting = getGroup(id, 1, 1)]
   [output = output+"Skills: Fighting: "+Fighting+", "]
   }]
[H: id = strfind(statblock, "Shooting (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Shooting = getGroup(id, 1, 1)]
   [output = output+"Shooting: "+Shooting+", "]
   }]

[H: id = strfind(statblock, "Throwing (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Throwing = getGroup(id, 1, 1)]
   [output = output+"Throwing: "+Throwing+", "]
   }]

[H: id = strfind(statblock, "Notice (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Notice = getGroup(id, 1, 1)]
   [output = output+"Notice: "+Notice+", "]
   }]

[H: id = strfind(statblock, "Guts (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Guts = getGroup(id, 1, 1)]
   [output = output+"Guts: "+Guts+", "]
   }]

[H: id = strfind(statblock, "Boating (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Boating = getGroup(id, 1, 1)]
   [output = output+"Boating: "+Boating+", "]
   }]

[H: id = strfind(statblock, "Climbing (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Climbing = getGroup(id, 1, 1)]
   [output = output+"Climbing: "+Climbing+", "]
   }]

[H: id = strfind(statblock, "Driving (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Driving = getGroup(id, 1, 1)]
   [output = output+"Driving: "+Driving+", "]
   }]

[H: id = strfind(statblock, "Gambling (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Gambling = getGroup(id, 1, 1)]
   [output = output+"Gambling: "+Gambling+", "]
   }]

[H: id = strfind(statblock, "Healing (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Healing = getGroup(id, 1, 1)]
   [output = output+"Healing: "+Healing+", "]
   }]

[H: id = strfind(statblock, "Intimidation (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Intimidation = getGroup(id, 1, 1)]
   [output = output+"Intimidation: "+Intimidation+", "]
   }]

[H: id = strfind(statblock, "Investigation (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Investigation = getGroup(id, 1, 1)]
   [output = output+"Investigation: "+Investigation+", "]
   }]

[H: id = strfind(statblock, "Lockpicking (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Lockpicking = getGroup(id, 1, 1)]
   [output = output+"Lockpicking: "+Lockpicking+", "]
   }]

[H: id = strfind(statblock, "Persuasion (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Persuasion = getGroup(id, 1, 1)]
   [output = output+"Persuasion: "+Persuasion+", "]
   }]

[H: id = strfind(statblock, "Piloting (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Piloting = getGroup(id, 1, 1)]
   [output = output+"Piloting: "+Piloting+", "]
   }]

[H: id = strfind(statblock, "Repair (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Repair = getGroup(id, 1, 1)]
   [output = output+"Repair: "+Repair+", "]
   }]

[H: id = strfind(statblock, "Riding (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Riding = getGroup(id, 1, 1)]
   [output = output+"Riding: "+Riding+", "]
   }]

[H: id = strfind(statblock, "Stealth (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Stealth = getGroup(id, 1, 1)]
   [output = output+"Stealth: "+Stealth+", "]
   }]

[H: id = strfind(statblock, "Streetwise (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Streetwise = getGroup(id, 1, 1)]
   [output = output+"Streetwise: "+Streetwise+", "]
   }]

[H: id = strfind(statblock, "Survival (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Survival = getGroup(id, 1, 1)]
   [output = output+"Survival: "+Survival+", "]
   }]

[H: id = strfind(statblock, "Swimming (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Swimming = getGroup(id, 1, 1)]
   [output = output+"Swimming: "+Swimming+", "]
   }]

[H: id = strfind(statblock, "Taunt (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Taunt = getGroup(id, 1, 1)]
   [output = output+"Taunt: "+Taunt+", "]
   }]

[H: id = strfind(statblock, "Tracking (d[0-9+-]+)")]
[H, IF(0 < getFindCount(id)), CODE:
   {
   [Tracking = getGroup(id, 1, 1)]
   [output = output+"Tracking: "+Tracking+"<BR />"]
   }]
[H: "<!-- SKILLS END -->"]

[H: "<!-- EDGES BEGIN -->"]
[H: tEdges = ""]
[H: output = output+"Edges: "]
[H: cEdges = getLibProperty("Edges", "Lib:GM")+","+getLibProperty("SettingEdges", "Lib:GM")]
[H, FOREACH(e, cEdges), CODE:
   {
   [id = strfind(statblock, e+"\\s*([+-]?[0-9]+)")]
   [IF(0 < getFindCount(id)), CODE:
   {
      <!-- Remove spaces from skill name -->
      [eName = replace(eName, " ", "")]
      [eVal = getGroup(id, 1, 1)]
      [output = output + eName + ", "]
   }]
   [tEdges = listappend(tEdges, eName)]
}]
[H: output = output+"<BR />"]
[H: SkillsJ = tSkillsJ]
[H: "<!-- EDGES END -->"]

[H: "<!-- HINDRANCES BEGIN -->"]
[H: "<!-- HINDRANCES END -->"]

[R, S, G: output]
Image
Time-Zone information UTC -5

neofax
Great Wyrm
Posts: 1694
Joined: Tue May 26, 2009 8:51 pm
Location: Philadelphia, PA
Contact:

Re: Regular Expression

Post by neofax »

OK, finished changing all of the JSON data for Edges/Hindrances to have "_" so I can match them to the statblock. However, I am having a problem with CODE levels and don't want to pass the info off to another macro. Here is where I am at:

Code: Select all

[H: "<!-- EDGES BEGIN -->"]
[H: tEdges = ""]
[H: output = output+"Edges: "]
[H: cEdges = getLibProperty("Edges", "Lib:GM")+","+getLibProperty("SettingEdges", "Lib:GM")]
[id = strfind(statblock, "Edges:([A-Za-z \(\),-]+)Powers:")]
[id = replace(id, "(", "")]
[id = replace(id, ")", "")]
[id = replace(id, " ", "_")]
[H, FOREACH(e, cEdges), CODE:
   {
   [IF(0 < getFindCount(id)), CODE:
   {
      [eName = getGroup(id, 1, roll.count)]
      [output = output + eName + ", "]
   }]
   [tEdges = listappend(tEdges, eName)]
}]
[H: output = output+"<BR />"]
[H: Edges = tEdges]
[H: "<!-- EDGES END -->"]


So, where the "IF(0, getFindCount" is I was thinking to add a [wfunc]count_(roll_option)[/wfunc], but that will bust the CODE level limit. So, how would I go about finding the next match and adding it to the list?
Image
Time-Zone information UTC -5

User avatar
wolph42
Deity
Posts: 9779
Joined: Fri Mar 20, 2009 5:40 am
Location: Netherlands
Contact:

Re: Regular Expression

Post by wolph42 »

well I DID just write a topic that3 or more code levels are possible, but its a bit risky as this is an 'undocumented feature'. Then there's an article I wrote about how to deal with it . I think you can combine your foreach and the if, but you'll need to check the article.

neofax
Great Wyrm
Posts: 1694
Joined: Tue May 26, 2009 8:51 pm
Location: Philadelphia, PA
Contact:

Re: Regular Expression

Post by neofax »

Thanks Wolph42 that second wiki article may work. I am getting a problem with my replaces not working. I can trap all of the Edges, but I cannot replace all instances of parenthesis, dashes and spaces.

OK, I am closer. I was an idiot and wasn't placing the trapped value into a variable and then do a replace on the variable. Now that I figured that out, I thought a regular expression of "(\\w+)" would capture all of the words up to a ",". Which it doesn't so now I need to find out how to trap each set of words up to but not including a comma.
Image
Time-Zone information UTC -5

neofax
Great Wyrm
Posts: 1694
Joined: Tue May 26, 2009 8:51 pm
Location: Philadelphia, PA
Contact:

Re: Regular Expression

Post by neofax »

OK, why do I get an IllegalArgument Exception error on "^([\\p{Alnum}&\\s]+,)+[\\p{Alnum}&\\s]+$"? This should pull out all of the alphanumeric characters between commas starting from the beginning to the end.

~~Nevermind~~
This works for adding Edges and Hindrances:

Code: Select all

[H: "<!-- EDGES BEGIN -->"]
[H: tEdges = ""]
[H: output = output+"Edges: "]
[H: cEdges = getLibProperty("Edges", "Lib:GM")+","+getLibProperty("SettingEdges", "Lib:GM")]
[H: id = strfind(statblock, "Edges:([A-Za-z \\(\\),-]+)Powers:")]
[H: matchString = getGroup(id, 1, 1)]
[H: matchString = replace(matchString, "\\(", "")]
[H: matchString = replace(matchString, "\\)", "")]
[H: matchString = replace(matchString, "-", "")]
[H: id = strfind(matchString, "([A-Za-z0-9 ]+)")]
[H, count(getFindCount(id)), CODE:
   {
      [H: matchString = getGroup(id, roll.count+1, 0)]
      [H: matchString = replace(matchString, "^\\s+", "")]
      [H: matchString = replace(matchString, "\\s+\$", "")]
      [H: matchString = replace(matchString, " ", "_")]
      [H, FOREACH(e, cEdges), IF(matchString == e), CODE:
      {
         [tEdges = listAppend(tEdges, e)]
         [output = output + e + ", "]
      }]
   }]
[H: output = output+"<BR />"]
[H: Edges = tEdges]
[H: "<!-- EDGES END -->"]


May take me longer to get where I wanted, but I got there.
Image
Time-Zone information UTC -5


neofax
Great Wyrm
Posts: 1694
Joined: Tue May 26, 2009 8:51 pm
Location: Philadelphia, PA
Contact:

Re: Regular Expression

Post by neofax »

Thank you! I feel proud. Took me most of the night as after I got it working I tried it on a token and it choked on adding gear so I figured that out and now it works. All I have left is weapons and special case skills like Knowledge/Crafts and realized the abilities need the exploding bit on the end of the dice. So yeah fun all around.
Image
Time-Zone information UTC -5


User avatar
StarMan
Dragon
Posts: 908
Joined: Mon Jul 18, 2011 1:10 pm
Location: Toronto

Re: Regular Expression

Post by StarMan »

I don't know if this is what you're after but here's the macro I use for retrieving text from D&D 4e stat blocks:

Code: Select all

<!--
[
FieldName=""]
[
NextFieldList=""]
[
varsFromStrProp(macro.args)]
[IF(
indexOf(TextFieldName)+1), CODE:
{
    [
Text=substring(TextindexOf(TextFieldName)+length(FieldName), length(Text))]

    [
EndPos=length(Text)]
    [FOREACH(
NextFieldNextFieldList), CODE:
    {
        [
Pos=indexOf(TextNextField)]
        [IF(
Pos>0): EndPos=min(PosEndPos)]
    }]

    [
macro.return=trim(substring(Text0EndPos))]
};
{
    [
macro.return=""]
}]
--> 


As you can see, it just returns the text found between two strings. FieldName is the first boundary and NextFieldList contains possibilities for terminating text. I know this is a very simplistic solution but I'm not good at writing any other kind! :wink: IOW, regex discussions make my tiny brain hurt. Yes, I can grasp the basic concepts but I have a long way to go in getting up to Azhrei's, AM's, JamesManhattan's, etc calibre.

Using your text (which we will assume is in the MyText property) as an example, let's say I wanted to retrieve the "Pace:" field. I would use:

Code: Select all

[h,MACRO("Get [email protected]"): "FieldName=Pace:; NextFieldList=Parry: ; Text="+MyText]

This actually won't work because you use semicolons in MyText. As you know, these are used for string list delimiters and would screw up the variable passage. That's why I remove them from the text before calling. Taking another example. here is how I would get "Powers:":

Code: Select all

[h,MACRO("Get [email protected]"): "FieldName= Powers:; NextFieldList= Shapeshift:, Speak with Nature:; Text="+MyText]

If we do things this way, we are protected against the condition when "Shapeshift" does not follow the Powers description. If "Speak with Nature:" can be reliably counted upon to follow Powers then you are good to go.

IOW, you need to look at all your race descriptions and account for variances in the inclusion of certain fields. Of course, they also need to retain their order otherwise this solution doesn't work so well. Hope this helps …
StarMan - The MacroPolicebox D&D 4E Framework: Import ANYTHING!

Post Reply

Return to “Macros”