Skip to main content
Open

Bring back StringPairReplacer

Related products:FME FormTransformers
  • February 28, 2026
  • 3 replies
  • 153 views

takashi
Celebrity

StringPairReplacer has been deprecated at FME 2025.2.

However, there are use cases where StringPairReplacer would be farther convenient than StringReplacer.

For instance, sometimes we have to replace multi-byte alpha-numeric characters with single-byte ones. In StringPairReplacer, it's easy to set replacement pairs, as in:

A A B B C C D D E E F F G G H H I I J J K K L L M M N N O O P P Q Q R R S S T T U U V V W W X X Y Y Z Z a a b b c c d d e e f f g g h h i i j j k k l l m m n n o o p p q q r r s s t t u u v v w w x x y y z z 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

How can you perform the same replacement with StringReplacer? Not impossible but you will have to define 62 replace strings rows. Too tedious!

I strongly hope StringPairReplacer will be back!

 

[Addition] Alternatively, it would be great if StringReplacer would have "Replace String Pair" mode, in order to perform the pair replacement with this setting.

Text to Replace: 0 1 2 3 4 5 6 7 8 9 こんにちは! さようなら。
Replacement Text: 0 1 2 3 4 5 6 7 8 9 Hello! "Good Bye."

 

3 replies

andreaatsafe
Safer
Forum|alt.badge.img+15
NewOpen

lenabagh
Contributor
Forum|alt.badge.img+3
  • Contributor
  • April 16, 2026

I wish I was able to up-vote more than once. ​@takashi I have a case very similar to yours, where it would take 33 StringReplacer pairs added and maintained between several workspaces (It used to be a StringPairReplacer with a text user parameter shared between workspaces). 

If StringPairReplacer can not be un-deprecated, adding a mode that allows defining text to replace and replacement text as a single textstring as it used to be in StringPairReplacer or as two textstrings as Takashi suggested would be very much appreciated.

Generally speaking, StringReplacer with the new table parameter looks sleek and visually appealing, however my main concerns are:

  • will all my workspaces with StringPairReplacers eventually stop working?
  • will I be able to set up text replacement using an attribute? 

david_r
Celebrity
  • April 17, 2026

I’m sure the StringReplacer is easier for beginners, but it’s very click-intensive (for lack of a better word) if you have a lot of values. I find the GUI rather tedious and cumbersome to use.

I’ve made a small Python class that emulates the functionality of the StringPairReplacer, and it also works with words. Copy the StringPairReplacer class and helper function in the workspace startup script, and you can then reference it wherever in the workspace:

import re


class StringPairReplacer:
"""Precompiled multi-pattern string replacer.

Builds a compiled regex once from search/replace pairs, then applies it
efficiently to any number of input strings via ``.apply()``.

Search and replace terms are extracted by splitting on whitespace, so
individual terms cannot contain whitespace. Terms are matched literally
(regex metacharacters are escaped). When terms overlap in the input,
the longest match wins.

Edge cases:
- If both *search* and *replace* are empty (or contain only
whitespace), the replacer becomes a no-op — ``.apply()`` returns
the input unchanged.
- If a search term appears more than once, the last corresponding
replace term wins (standard ``dict(zip(...))`` behaviour).
- Raises ``ValueError`` when the number of search terms does not
equal the number of replace terms.
"""

def __init__(self, search: str, replace: str) -> None:
"""Compile the search/replace pairs into an internal regex.

Args:
search: Space-delimited search terms.
replace: Space-delimited replacement terms. Must contain the
same number of terms as *search*.

Raises:
ValueError: If *search* and *replace* have different term counts.
"""
search_terms = search.split()
replace_terms = replace.split()

if len(search_terms) != len(replace_terms):
raise ValueError(
f"search has {len(search_terms)} terms but replace has "
f"{len(replace_terms)} terms — they must match"
)

if not search_terms:
self._pattern = None
self._lookup = {}
return

# Map each search term to its replacement
self._lookup = dict(zip(search_terms, replace_terms))

# Sort by length descending so longest match wins in alternation
sorted_terms = sorted(search_terms, key=len, reverse=True)
pattern = "|".join(re.escape(term) for term in sorted_terms)
self._pattern = re.compile(pattern)

def apply(self, input: str) -> str:
"""Replace all occurrences of the search terms in *input*.

Args:
input: The string to transform.

Returns:
A new string with every search-term occurrence replaced by its
corresponding replace term. If no search terms were provided,
*input* is returned unchanged.
"""
if self._pattern is None:
return input
return self._pattern.sub(lambda m: self._lookup[m.group()], input)


def string_pair_replacer(input: str, search: str, replace: str) -> str:
"""Convenience wrapper — creates a `StringPairReplacer` and applies it once.

Useful for one-shot replacements where keeping a compiled replacer around
is unnecessary. For repeated replacements with the same pairs, prefer
instantiating `StringPairReplacer` directly to avoid recompiling the
regex on every call.

Args:
input: The string to transform.
search: Space-delimited search terms.
replace: Space-delimited replacement terms (must match *search* in
term count).

Returns:
The transformed string.

Raises:
ValueError: If *search* and *replace* have different term counts.
"""
return StringPairReplacer(search, replace).apply(input)

Example use in a PythonCaller:

import fme
import fmeobjects


class FeatureProcessor:
def __init__(self):
self._spr = StringPairReplacer(
fme.macroValues["SEARCH_CHARS"],
fme.macroValues["REPLACE_CHARS"],
)

def input(self, feature: fmeobjects.FMEFeature):
input_attribute_name = "my_string"
value = feature.getAttribute(input_attribute_name) or ""
feature.setAttribute(input_attribute_name, self._spr.apply(value))
self.pyoutput(feature)

def close(self):
pass

The example uses a pre-compiled regex based on the parameters SEARCH_CHARS and REPLACE_CHARS to optimize for speed if there are a lot of features with the same rules.

It’s also possible to use the string_pair_replacer() function for smaller datasets, or if the search and replace characters varies between features.

Example:

If the input is “the lazy fox jumps over the brown dog”, the result is “the eager f0X jumps 0ver the br0wn d0g”.