Home > Enterprise >  String interpolation by user input
String interpolation by user input

Time:11-05

is possible to make interpolation based on user input?

For example something like this:

public string Foo(string input)
{
    string user = "random_user_name";
    string address = "random_address";
    string city = "random_city";

    return $"{input}";
}
  • Example input1: "{user}, {city}"
  • Expected output1: "random_user_name, random_city"
  • Example input2: "{address}"
  • Expected output2: "random_address"
  • Example input3: "{city}, {address}"
  • Expected output3: "random_city, random_address"

I need that user can define which variables want and their order. Btw user will know variable names.

CodePudding user response:

Taking @TheGeneral's suggestion, I did the following:

The first thing I did was to change your three strings into a Dictionary<string, string>

var tokens = new Dictionary<string, string> {
    { "user", "random_user_name" },
    { "address", "random_address" },
    { "city",  "random_city" }
};

Then I created a worker function that does the replacement in the way @TheGeneral described

public static string DoReplacement(string key, Dictionary<string, string> tokens)
{
    var result = new StringBuilder(key);
    foreach (var kvp in tokens)
    {
        result.Replace("{"   kvp.Key   "}", kvp.Value);
    }
    return result.ToString();
}

And finally, I wrapped it all together:

public static string Test(string input)
{
    var tokens = new Dictionary<string, string> {
        { "user", "random_user_name" },
        { "address", "random_address" },
        { "city",  "random_city" }
    };

    return $"{DoReplacement(input, tokens)}";
}

To exercise this, I called it with the OP's examples:

var tests = new List<string> {
    "{user}, {city}",
    "{address}",
    "{city}, {address}",
};
foreach (var s in tests)
{
    var replaced = StringReplacement.Test(s);
    Debug.WriteLine(replaced);
}

and got this output:

random_user_name, random_city
random_address
random_city, random_address

CodePudding user response:

You can simply use string.Format

public string Foo(string input)
{
    string user = "random_user_name";
    string address = "random_address";
    string city = "random_city";

    return string.Format(input, user, address, city);
}

Examples

Foo("{0}, {2}")  // "random_user_name, random_city"
Foo("{1}")       // "random_address"
Foo("{2}, {1}")  // "random_city, random_address"

dotnetfiddle

The downside is that the user of the function needs to know which order you will pass the parameters.

CodePudding user response:

Building on Charlieface's suggestion.. In the past I've taken the user's input and transformed it into a Format compatible input string because I know the order I will pass to format..

I know I call Format in weight, name, birthday order

I can do:

public class Formatter{

    private string _fmt;

    public Formatter(string theirInput){
      _fmt = theirInput;

      ord = new[] { "{weight", "{name", "{birthday" };
      for(int x = 0; x < ord.Length; x  )
        _fmt = _fmt.Replace(ord[x], "{" x);
    }

    public string Format(User u){
      return string.Format(_fmt, u.Weight, u.Name, u.Birthday);
    }
}

Suppose the user types {name} {weight} {birthday}

This code transforms it to {1} {0} {2} then stores the transformation

Why bother? Well, if it's only going to happen one time you might as well just use the string replace pattern in the constructor to straight replace the variables with the values. The advantage of doing this is if you have multiple things to format in the same way and, bonus, it allows the user to specify formats that might vary by culture

For example your user could pass "Mr {name} born on {birthday:yyyy-MM-dd} weighing {weight:F2} lbs to two decimal places" and string.Format would adjust the formatting of their birthday DateTime to follow yyyy-MM-did and double Weight (which is calculated from kilos and might have 15 decimal places) to two decimal places.

You can read more about string formatting and parameters To placeholders in the docs:

https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings

https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings

https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings

https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings

https://docs.microsoft.com/en-us/dotnet/api/system.string.format?view=net-5.0

CodePudding user response:

Completely different technique to my other answer, this uses a regex matchevaluator to supply replacement values

Your code has a dictionary of replacements:

var tokens = new Dictionary<string, string> {
    { "{user}", "random_user_name" },
    { "{address}", "random_address" },
    { "{city}",  "random_city" }
};

We make a Regex out of the keys:

var r = new Regex(string.Join("|", tokens.Keys.Select(Regex.Escape)));

We make a matchevaluator, which is basically a method that decides what replacement value to use when the Regex finds some matching value:

var me = new MatchEvaluator(m => tokens[m.Value]);

And we call it on their input

var output = r.Replace(sInput, me);

Having built a string from the keys, like {user}|{address}|{city} the Regex will find these in OR style in the input string. Every time it finds one, it asks the match evaluator e.g. "found 'user' what do I replace it with?" and the match evaluator lambda basically looks "user" up in the dictionary and gives back "random_user_name"

Note, if you enable case insensitive matching on the Regex you should do something like lowercasing the keys in the dictionary and calling ToLower in the match evaluator lookup otherwise you might get keyNotFound

  • Related