Guru Classic: A Bevy Of BIFs – %ScanRpl (Scan And Replace)
January 8, 2020 Jon Paris
This gem of a BIF was introduced with the V7.1 release and so was not available some five years ago when I wrote my original Bevy series of tips. Now that most active development shops are running V7.1 and later it seems a good candidate for a “Classic” to remind readers of its capabilities. The intervening years have not dimmed its utility and it remains one of my all-time favorite BIFs.
Simply put, %ScanRpl will search a target string for a given character sequence and replace it with another. Not only that but it will then continue to search through the target string and perform the replacement as many additional times as needed. Prior to the arrival of this new BIF, performing this kind of “mail merge” operation required you to manually code a loop comprising a %Scan and a %Replace operation.
The basic syntax for the BIF is:
%SCANRPL(scan string : replacement : source { : scan start { : scan length } )
Most of the time you will only need to use the first three parameters to do the job. But as you can see you can also optionally control the search start position and length of the string to be scanned if desired.
For example, the following code will replace all instances of &Name in the string baseText with the content of the custName variable. The resulting string is placed in the variable outputText.
outputText = %ScanRpl( '&Name' : custName : baseText );
This example assumes that custName is a variable length field with no trailing spaces. But if it were a fixed length field we could deal with that by simply trimming the field when passing it, like so:
outputText = %ScanRpl( '&Name': %TrimR( custName ): baseText );
It is important to note that just as with its predecessor, the %Replace BIF, %ScanRpl can replace a short string with a longer one, or vice versa. In fact it can even be used to remove unwanted characters completely from a string by replacing them with a null string. In the example below all commas in baseText are removed (i.e. they are replaced by a null string).
outputText = %ScanRpl( ',' : '' : baseText );
An “Interesting” Example
At first glance, you might think that %SCANRPL could be used to replace a sequence of consecutive spaces by a single space. Certainly that will work if you want to replace two spaces by one, like this:
// First parm is 2 spaces, second parm is a single space outputText = %ScanRpl( ' ' : ' ' : baseText );
But typically you won’t know in advance exactly how many spaces you’ll need to replace. Using this method, if there were three or more spaces – you would still end up with more spaces than you want. Why? Because a single space will replace each pair of spaces in the original string. So three or four consecutive spaces would still result in two spaces in the output. You could put the statement above in a loop and implement some method to determine when to stop looping, but that would be even less efficient than other techniques such as simply using %Scan followed by %Replace in a loop.
The following code, while almost certainly not the most efficient way to do it, will achieve the task. Sometimes I just like to explore different coding techniques and when I first came upon this approach I thought it would make an interesting example of how %ScanRpl could be used, so here it is.
The task is to replace all instances of two or more spaces in a string leaving only single spaces between “words.” Here’s the %SCANRPL code to achieve this:
dcl-s baseString char(38) Inz('3 spc 4 spc 5 spc End'); dcl-s result varChar(38); (a) result = %ScanRpl( ' ': '<>': baseString ); // 1st parm is 1 space dsply ('Contents: ' + result ); (b) result = %ScanRpl( '><': '': result ); // 2nd parm is null string dsply ('Contents: ' + result ); (c) result = %ScanRpl( '<>': ' ': result ); dsply ('Contents: ' + result ); dsply ('Length of result is ' + %Char(%Len(result)));
As you can see, I have interspersed dsply operations so that when you run the code you can see the effect of each stage. At the first step (a) every single space is replaced by the characters ‘<>’. Next (b) we remove all occurrences of ‘><‘ and replace them with nothing (i.e. a null string). At this point, no matter how many spaces we had to start with, we will be left with the string ‘<>’ in their place. So at (c) we replace that string with a single space. That’s all there is to it.
Jon Paris is one of the world’s foremost experts on programming on the IBM i platform. A frequent author, forum contributor, and speaker at User Groups and technical conferences around the world, he is also an IBM Champion and a partner at Partner400 and System i Developer. He hosts the RPG & DB2 Summit twice per year with partners Susan Gantner and Paul Tuohy.
Regarding the 3-step consecutive spaces example. Might be worth mentioning that a. it may behave unexpectedly if your string already contains characters; and b. for more complex replacements like this, you can use the sql regexp_replace() function to replace based on a regexp pattern. This example will replace blocks of 2 or more consecutive spaces with one space: “exec sql set :result = regexp_replace(:result,’ {2,}’,’ ‘);”
Agreed. But I was trying to stay away from RegEx because the article was about %ScanRpl. And of course compiled Regex is probably more efficient that the SQL version so I’d have to have mentioned that …
As it says in text “while almost certainly not the most efficient way to do it” … it was just intended as a thinking-outside-the-box approach just to make people think about less obvious solutions to problems.