• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[Snippet] String Replacer

This simple library uses a hashtable to replace instances of a string (case insensitive) with another string given by a function. Yes, this is coded in plain old JASS. You'll have to integrate the globals into your map (for instance by replacing the hashtable with your own hashtable variable). This is designed to work well with any map that uses a hashtable for storing data. Looping over the string is done in a method to reduce the number of strings created to as low as possible.
JASS:
//===========================================================================
//
// [Snippet] String Replacement Library
//
//   Author: Zeatherann
//   Description: Allows replacement of words found within an opening and closing deliminator.
//
//   Notes: The strings registered to functions with this system use StringHash and therefore are case insensitive.
//===========================================================================
//***************************************************************************
//*
//*  Global Variables
//*
//***************************************************************************
globals
    constant integer    udg_FilterWordIndex     =0 // This is the index to use in the hashtable to store registered words in.
    hashtable           udg_Hash                =InitHashtable() // This is the hashtable to store registered words in.
    string              udg_FilterStringReturn  ="" // This is the variable functions registered to words needs to set to act as a return value.
    constant string     udg_OpeningCharacter    ="[" // This is the opening deliminator to signal a word's beginning (has to be a single character).
    constant string     udg_ClosingCharacter    ="]" // This is the closing deliminator to signal a word's end (has to be a single character).
endglobals                                              // NOTE: These can be set to the same thing and still function properly.
//***************************************************************************
//*
//*  String Filter system
//*
//***************************************************************************
//====================================================================================================
// RegisterWord
//   takes   string     Word - The string to register to the function.
//           boolexpr   Function - The function to call when the string is found.
//   returns nothing
//
//   This function registers a string to a function to call should that string (case-insensitive) be found within the deliminators udg_OpeningCharacter and udg_ClosingCharacter.
//====================================================================================================
function RegisterWord takes string Word,boolexpr Function returns nothing
    local trigger FilterFunction=CreateTrigger()
    call TriggerAddCondition(FilterFunction,Function)
    call SaveTriggerHandle(udg_Hash,udg_FilterWordIndex,StringHash(Word),FilterFunction)
    set FilterFunction=null
endfunction
//====================================================================================================
// FilterString
//   takes   string  Source    - The string to replace parts of.
//
//   returns string: A new string with parts replaced from Source.
//
//   This function is takes a string and returns a new string that has pieces of it replaced by looking up the registered words.
//
//   NOTE: If a string found within deliminators doesn't match any registered strings then it will not be replaced and will appear in the returned string as it is in Source.
//====================================================================================================
function FilterString takes string Source returns string
    local integer I=0
    local integer Last=0
    local integer Length=StringLength(Source)
    local string Char
    local boolean ParsingToken=false
    local string Return=""
    local trigger FilterFunction
    loop
        exitwhen I==Length
        set Char=SubString(Source,I,I+1)
        if(ParsingToken)then
            if(Char==udg_ClosingCharacter)then
                set FilterFunction=LoadTriggerHandle(udg_Hash,udg_FilterWordIndex,StringHash(SubString(Source,Last+1,I)))
                if(FilterFunction!=null)then
                    call TriggerEvaluate(FilterFunction)
                    set Return=Return+udg_FilterStringReturn
                else
                    set Return=Return+SubString(Source,Last,I+1)
                endif
                set ParsingToken=false
                set Last=I+1
            endif
        elseif(Char==udg_OpeningCharacter)then
            set ParsingToken=true
            set Return=Return+SubString(Source,Last,I)
            set Last=I
        endif
        set I=I+1
    endloop
    return Return+SubString(Source,Last,I)
endfunction
JASS:
//***************************************************************************
//*
//*  Example Using the String Filter system
//*
//***************************************************************************
//====================================================================================================
// Registered words
//====================================================================================================
function Example_FilteredWord_Adjative takes nothing returns nothing
    local integer I=GetRandomInt(0,3)
    if(I==0)then
        set udg_FilterStringReturn="quick"
    elseif(I==1)then
        set udg_FilterStringReturn="slow"
    elseif(I==2)then
        set udg_FilterStringReturn="fast"
    else
        set udg_FilterStringReturn="sluggish"
    endif
endfunction
function Example_FilteredWord_Color takes nothing returns nothing
    local integer I=GetRandomInt(0,3)
    if(I==0)then
        set udg_FilterStringReturn="brown"
    elseif(I==1)then
        set udg_FilterStringReturn="red"
    elseif(I==2)then
        set udg_FilterStringReturn="orange"
    else
        set udg_FilterStringReturn="white"
    endif
endfunction
function Example_FilteredWord_Adjative2 takes nothing returns nothing
    local integer I=GetRandomInt(0,3)
    if(I==0)then
        set udg_FilterStringReturn="lazy"
    elseif(I==1)then
        set udg_FilterStringReturn="barking"
    elseif(I==2)then
        set udg_FilterStringReturn="sleeping"
    else
        set udg_FilterStringReturn="eating"
    endif
endfunction
//====================================================================================================
// Register the example words
//====================================================================================================
function RegisterExamples takes nothing returns nothing
    call RegisterWord("speed",Filter(function Example_FilteredWord_Adjative))
    call RegisterWord("color",Filter(function Example_FilteredWord_Color))
    call RegisterWord("action",Filter(function Example_FilteredWord_Adjative2))
endfunction
//====================================================================================================
// Test the example
//====================================================================================================
function Test_Example_Filtering takes nothing returns nothing
    call BJDebugMsg(FilterString("The [speed] [color] fox jumped over the [action] dog.",0))
    // Possible output: "The quick brown fox jumped over the lazy dog."
endfunction
Editions
  • Prefixed all three global variables with 'udg_'.
  • Added a tad more documentation.
  • Added two new configurable options: udg_OpeningCharacter and udg_ClosingCharacter which determine the opening and closing deliminators. Also added more documentation and changed presentation a bit more.
  • Added support for recursive parsing, with the number of recursions controlled via a second argument.
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
As long as you have globals block, and especially global variables that dont start with udg,its not Jass. Try implementing this in jass, you would have to search-replace all occurences of your variable. You could have just put the global variables to a comment and if they are not copied with the script, user will create them
 
Heh, I've missunderstood "replace" behaviour function with another method.
Idea is nice. However, I'm not liking the "replace" word here. Even tho it is the final effect, this associates a specified object with given string so that outcome can vary.

You should add better delimiter handling, similar to one from tokenizers.
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
i actually like it, the example shows what the code can do, and I think it is simple enough and quite usable if you want synonyms to be printed on places like cinematics so you dont play the same cinematic all over again.
 
I don't expect anyone to simply copy and paste it, but to adapt it to their map. If that means turning this into a vJASS library then alright, if that means making it use their own hashtable variable, then alrighty, etc. It is as portable as can be realistically expected from a non-vJASS resource. Also, if someone turned this into a vJASS library (even if they don't change the code) and repost it then it'll replace my resource because of vJASS. Honestly, I'm surprized it's lasted this long.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
This and your random name generating seem to be bordering on NLP. I wonder if anyone's done an actual sentence generator in a war3 map (and no, I don't mean just replacing adjectives/nouns but actual generation).

Is this the fastest/leakless method possible for generating strings based on templates/text macros?
 
Another Example

Naw, it isn't anywhere near to natural language processing, nor does do any syntax generation. However, using a more complex setup for the functions mapped to the strings one can reparse to build up more and more of the desired string. Also, this is probably the fastest and least leaky method for doing this, as it avoids creating strings. Below is a bit of test code with this system (it omits a lot for simplicity). It shows how parsing a string twice could be meaningful. If you parse a string twice when it doesn't have any tokens in it, then nothing happens.
JASS:
function Str_Object takes nothing returns nothing
    local integer I=GetRandomInt(0,5)
    if I==0then
        set udg_FilterStringReturn="chair"
    elseif I==1then
        set udg_FilterStringReturn="table"
    elseif I==2then
        set udg_FilterStringReturn="candle"
    elseif I==3then
        set udg_FilterStringReturn="clock"
    elseif I==4then
        set udg_FilterStringReturn="door"
    else
        set udg_FilterStringReturn="window"
    endif
endfunction

function Str_ShowObject takes nothing returns nothing
    local integer I=GetRandomInt(0,3)
    if I==0then
        set udg_FilterStringReturn="the big [Object]"
    elseif I==1then
        set udg_FilterStringReturn="a [Object] with a dent"
    elseif I==2then
        set udg_FilterStringReturn="a single [Object] that hums"
    else
        set udg_FilterStringReturn="a [Object]"
    endif
endfunction

function DisplayRoom takes nothing returns nothing
    call DisplayTextToPlayer(udg_SomePlayer,0,0,FilterString(FilterString("A room with a [ShowObject].")))
endfunction
I might make it recursive aware and automatically parse tokens 'returned' by other tokens.
 
For the last freaking time; I know free global block declarations is a 'vJASS feature'. I'm including it here so a user that wishes to use my resource knows what global variables to create, or the option of dropping it into jngp. While this is 'inefficient' it runs in O(n) doesn't it? Kind of like all string manipulation operations. How is it not portable though? Does that stem from the fact that I declare my global variables like that?
 
Sorry I didn't realize you kept the udg_ for vJass users.

Again maybe I was wrong about portability.

udg_Opening/ClosingCharacter doesn't seem necessary to me. It would be easier for the user to edit the string directly rather than using the variable editor.

udg_FilterWordIndex seems useless as well since you're already storing viaStringHash
 
It doesn't work because you haven't initialized the global variables (or inlined them).
JASS:
globals
    constant integer    udg_FilterWordIndex     =0 // This is the index to use in the hashtable to store registered words in.
    hashtable           udg_Hash                =InitHashtable() // This is the hashtable to store registered words in.
    string              udg_FilterStringReturn  ="" // This is the variable functions registered to words needs to set to act as a return value.
    constant string     udg_OpeningCharacter    ="[" // This is the opening deliminator to signal a word's beginning (has to be a single character).
    constant string     udg_ClosingCharacter    ="]" // This is the closing deliminator to signal a word's end (has to be a single character).
endglobals
 
My bad I forgot to initialize them when creating a separate demo map.

It still doesn't work.

  • Melee Initialization
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • Set ClosingCharacter = ]
      • Set OpeningCharacter = [
      • Set FilterWordIndex = 0
      • Set FilterStringReturn = <Empty String>
      • Custom script: set udg_Hash=InitHashtable()
      • Custom script: call RegisterExamples()
      • Custom script: call BJDebugMsg(FilterString("The [speed] [color] fox jumped over the [action] dog.",0))
 

Attachments

  • stringreplacer.w3x
    18.9 KB · Views: 48
Top