Guru Classic: A Bevy of BIFs, %XLATE and %REPLACE
March 6, 2019 Jon Paris
Author’s Note: I’m revisiting this classic tip since the original was written back in 2009, long before the introduction of free-form data declarations. In addition, I’ve updated this tip to point to the new %SCANRPL BIF, which impacts this scenario. And, of course, I still regularly encounter RPGers who are confused by the differences between the %XLATE and %REPLACE built-in functions (BIFs). Part of that confusion of course is the result of wishful thinking on the part of those frustrated by the limitations of %XLATE!
The first thing to remember when deciding which function to use is that %XLATE operates on individual characters and %REPLACE operates on strings. In this regard they are similar to the %SCAN and %CHECK BIFs that I discussed in this tip.
This story contains code, which you can download here.
Before we study %REPLACE, let’s quickly review the function of %XLATE.
%XLATE( from : to : input {: startpos} )
The function of %XLATE is to search the input string for any instances of the individual characters contained in the from string and to replace them with the corresponding character in the to string. So in this example:
result = %XLATE( 'ABC': 'XYZ': source);
Any occurrences of the character ‘A’ in the source string will be replaced by ‘X’, ‘B’ by ‘Y’, and ‘C’ by ‘Z’. So if we assume that source contains ‘123ABC789’ before the operation, afterward the result will contain ‘123XYZ789’.
The mistake that some programmers make is to think that %XLATE is replacing the combination of characters. In other words, in the above example, the string ‘ABC’ is being replaced. It is, but only as the result of the characters ‘A’, ‘B’, and ‘C’ being individually replaced. Had the input contained ‘ABD’, it would have been converted to ‘XYD’. This misconception can result in code being written that will “almost” work and that may under test conditions appear to work correctly.
Suppose that we want to replace any occurrence of the character string ‘/*’ with ‘&&’. The programmer might code:
result = %Xlate( '/*': '&&': source);
But while this may appear at first glance to work, it will not only convert all instances of ‘/*’ but, because it is operating on a character by character basis, it will also translate individual ‘/’ or ‘*’ characters – probably not what was wanted. The following code demonstrates this effect:
Dcl-S from varchar(5) Inz('/*'); Dcl-S to varchar(5) Inz('&&'); source = '123ABC789'; result = %XLATE( 'ABC': 'XYZ': source); dsply ('Source: ' + source); dsply ('Result: ' + result); dsply ('Convert /* to && the wrong way with %Xlate'); source = '/* Leave /these/ and *these* /*'; result = %Xlate(from: to: source); // - Result contains '&& Leave &these& and &these& &&' dsply ('Source: ' + source); dsply ('%XLate from ' + from + ' to ' + to ); dsply ('Result is: ' + result);
At the time the original version of this tip was published, it actually required a combination of the %SCAN and %REPLACE BIFs to achieve this task. Since that time however, RPG has added the built-in function %SCANRPL and this operates as desired in this example (i.e., to replace every occurrence of one string with another). But suppose you only wanted to replace the first (or last or third) occurrence of the string. In that case, you would still need to use the scan/replace combination. So I am still covering the example of using the combination here. In addition I have included an example of the %SCANRPL equivalent in the associated source file as well. If you want the full details of %SCANRPL you can find them in this article: A Bevy Of BIFs: %ScanRpl (Scan And Replace)
%REPLACE
The basic syntax for %REPLACE is:
%REPLACE( replacement : source { :startpos {: length to replace}} )
The code below demonstrates how the %SCAN and %REPLACE BIFs can be used in combination to achieve the desired functionality. The start position for the string to be replaced is obtained by an initial %SCAN (A). It is then used to specify the start position for the %REPLACE operation (B). On the subsequent scan operations, the optional start parameter (C) is used to skip past the portion of the string that has already been processed.
Dcl-S from Varchar(5) Inz('/*'); Dcl-S to Varchar(5) Inz('&&'); Dcl-S position Int(5); Dcl-S start Int(5); Dcl-S custName varchar(12); Dcl-S marker varchar(10); // This is a valid approach to handling character strings // - It will result in '&& Leave /these/ and *these* &&' dsply ('Convert /* to && the "right" way with %Scan/%Replace'); result = '/* Leave /these/ and *these* /*'; (A) position = %Scan( from: result); DoW position > 0; // If replacing all occurrences %SCANRPL works better // But logic could be added here to check for which // occurrence(s) should be replaced (B) result = %Replace( to: result: position); start = position + 1; (C) position = %Scan( from: result: start); EndDo; dsply ('Source: ' + source); dsply ('%Replace ' + from + ' With ' + to ); dsply ('Result is: ' + result);
Although less useful than it was before the advent of %SCANRPL, %REPLACE does have a number of nice features. For one, the length of the replacement string does not have to match the length of the text to be replaced. In such cases, the optional fourth parameter is used to specify the number of characters to be replaced. This makes the BIF very useful in mail-merge type operations where you want to replace a text marker with a variable amount of text. For example, the base text might include a marker like “&Name”, which we are going to replace with the customer’s name. So we might code the %REPLACE operation like this:
marker = '&Name'; position = %Scan(marker: source); result = %Replace( custName: source: position: %Len(marker));
This will cause the BIF to replace “&Name” (five characters) with the content of custName. The above code assumes that custName is a variable length field; if it were not we would have had to code %TrimR(custName) as the parameter to avoid inserting potentially large numbers of blanks.
There is a lot more we could say about %REPLACE, but the intent of this tip is to clarify the differences between %XLATE and %REPLACE. Plus, as I noted earlier, %SCANRPL has replaced the use of %REPLACE in many cases.
If there are any other BIFs you’d like me to cover in future tips please let me know via the comments.
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.