How to do a multiple case insensitive replace using a StringBuilder - c#

I have a (large) template and want to replace multiple values. The replacement needs to be done case insensitive. It must also be possible to have keys that does not exist in the template.
For example:
[TestMethod]
public void ReplaceMultipleWithIgnoreCaseText()
{
const string template = "My name is #Name# and I like to read about #SUBJECT# on #website#, tag #subject#";
const string expected = "My name is Alex and I like to read about C# on stackoverflow.com, tag C#";
var replaceParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("#name#","Alex"),
new KeyValuePair<string, string>("#subject#","C#"),
new KeyValuePair<string, string>("#website#","stackoverflow.com"),
// Note: The next key does not exist in template
new KeyValuePair<string, string>("#country#","The Netherlands"),
};
var actual = ReplaceMultiple(template, replaceParameters);
Assert.AreEqual(expected, actual);
}
public string ReplaceMultiple(
string template,
IEnumerable<KeyValuePair<string, string>> replaceParameters)
{
throw new NotImplementedException(
"Implementation needed for many parameters and long text.");
}
Note that if I have a list of 30 parameters and a large template, I do not want 30 large strings in memory. Using a StringBuilder seems to be an option, but other solutions are also welcome.
Solution I tried but did not work
Solution found here (C# String replace with dictionary) throws an exception when a key is not in the colletion, but our users makes mistakes and in that case I want to just leave the wromg key in the text. Example:
static readonly Regex re = new Regex(#"\$(\w+)\$", RegexOptions.Compiled);
static void Main2()
{
// "Name" is accidentally typed by a user as "nam".
string input = #"Dear $nam$, as of $date$ your balance is $amount$";
var args = new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase) {
{"name", "Mr Smith"},
{"date", "05 Aug 2009"},
{"amount", "GBP200"}};
// Works, but not case insensitive and
// uses a lot of memory when using a large template
// ReplaceWithDictionary many args
string output1 = input;
foreach (var arg in args)
{
output1 = output1.Replace("$" + arg.Key +"$", arg.Value);
}
// Throws a KeyNotFoundException + Only works when data is tokenized
string output2 = re.Replace(input, match => args[match.Groups[1].Value]);
}

Using a StringBuilder seems to be an option, but other solutions are also welcome.
Since you want case insensitive, I'd suggest (non StringBuilder):
public static string ReplaceMultiple(
string template,
IEnumerable<KeyValuePair<string, string>> replaceParameters)
{
var result = template;
foreach(var replace in replaceParameters)
{
var templateSplit = Regex.Split(result,
replace.Key,
RegexOptions.IgnoreCase);
result = string.Join(replace.Value, templateSplit);
}
return result;
}
DotNetFiddle Example

This is based off of Marc's answer the only real change is the check during the replacement and the boundary regex rule:
static readonly Regex re = new Regex(#"\b(\w+)\b", RegexOptions.Compiled);
static void Main(string[] args)
{
string input = #"Dear Name, as of dAte your balance is amounT!";
var replacements = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{"name", "Mr Smith"},
{"date", "05 Aug 2009"},
{"amount", "GBP200"}
};
string output = re.Replace(input, match => replacements.ContainsKey(match.Groups[1].Value) ? replacements[match.Groups[1].Value] : match.Groups[1].Value);
}
And here is a 5000 iterations test benchmark, have not looked at memory or anything else.
Replacement function is the one you have checked as the accepted answer.

I think I might have something you could try. I used something similar to it for email templates
public string replace()
{
string appPath = Request.PhysicalApplicationPath;
StreamReader sr = new StreamReader(appPath + "EmailTemplates/NewMember.txt");
string template = sr.ReadToEnd();
template = template.Replace("<%Client_Name%>",
first_name.Text + " " + middle_initial.Text + " " + last_name.Text);
//Add Customer data
template = template.Replace("<%Client_First_Name%>", first_name.Text);
template = template.Replace("<%Client_MI%>", middle_initial.Text);
template = template.Replace("<%Client_Last_Name%>", last_name.Text);
template = template.Replace("<%Client_DOB%>", dob.Text);
return template;
}
Inside of your template you can have tags such as <% %> as place holders for the values you want
Hope this helps!

The answer of Marc Gravell: C# String replace with dictionary can be changed an little bit so it does not throws an exception when the match can not be found. In this case it simply does not replace the match.
In case the string to be replace is tokenized, this is the solution:
static readonly Regex RegExInstance = new Regex(#"\$(\w+)\$", RegexOptions.Compiled);
public string ReplaceWithRegEx(string template, Dictionary<string, string> parameters)
{
return RegExInstance.Replace(template, match => GetNewValue(parameters, match));
}
private string GetNewValue(Dictionary<string, string> parameters, Match match)
{
var oldValue = match.Groups[1].Value;
string newValue;
var found = parameters.TryGetValue(oldValue, out newValue);
if (found)
{
return newValue;
}
var originalValue = match.Groups[0].Value;
return originalValue;
}
I have tested the solution with a 100.000 bytes string, 7 keys and hundreds of replacements. It uses 7 times more memory then the lenght of the string. And it took only 0.002 seconds.

Related

replacing data in between specific characters

I have a string which has some keys between <<>>.
string s = "<p>Hi <<USER>>,<br/>How are you doing<br/>Regards,<br/><<SENDER>></p>";
I want to first fetch the key names USER and SENDER in a list which i did by:
var keys = new List<string>();
foreach (Match match in Regex.Matches(s, #"<<(.*?)>>"))
{
keys.Add(match.Groups[1].Value);
}
List<string> values= new List<string>(){"John","Team"};
After we get the keys,i want to replace these keys by another list(named values here) which has the values for these keys and want the result as:
string s = "<p>Hi John,<br/>How are you doing<br/>Regards,<br/>Team</p>";
The string s can be anything and the no of keys and their values could also vary but the keys will always be enclosed in between <<>>
As suggested by #AlexBell, you could simply use the String.Replace() method.
Further, it's more convenient to declare a collection of placeholder/value pairs, like so:
string s = "<p>Hi <<USER>>,<br/>How are you doing<br/>Regards,<br/><<SENDER>></p>";
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("<<USER>>", "Jhon");
dictionary.Add("<<SENDER>>", "Team");
StringBuilder text = new StringBuilder(s);
foreach (var entry in dictionary)
{
text.Replace(entry.Key, entry.Value);
}
Console.WriteLine(text.ToString());
This function will perform the replacements that you ask for, using Regex.Replace:
public static string ParseTemplate(string template, string username, string senderName)
{
template = Regex.Replace(template, #"<<USER>>", username);
return Regex.Replace(template, #"<<SENDER>>", senderName);
}
Example:
string s = "<p>Hi <<USER>>,<br/>How are you doing<br/>Regards,<br/><<SENDER>></p>";
ParseTemplate(s, "John", "Team").Dump();
Output:
<p>Hi John,<br/>How are you doing<br/>Regards,<br/>Team</p>
You can call this in a loop over your dictionary or list of names.
Your business logic is a bit unclear, so based on just qualified guess, you can apply standard .NET/C# String.Replace Method (String, String)
(re:https://msdn.microsoft.com/en-us/library/fk49wtc1%28v=vs.110%29.aspx), for example:
string s = "<p>Hi <<USER>>,<br/>How are you doing<br/>Regards,<br/><<SENDER>></p>".Replace("<USER>", "John").Replace("<SENDER>", "Team");
Hope this may help.
//First we have lists of values (users) and senders
List<string> values= new List<string>(){"John","Team"};
List<string> senders = new List<string>(){"John","Team"};
//Then we can join that list using string.join
var allUsers = string.Join(",", values);
var allSender = string.Join(",", senders);
//Next we will be replacing it in our string
var namedString = Regex.Replace(string, #"<<USER>>", allUsers);
var output = Regex.Replace(namedString , #"<<SENDER>>", allSender);

RegEx not being able to match data

i am using RegEx to find and replace data.
suppose my code is
Dictionary<string, string> data=new Dictionary<string, string>();
data.Add("JID","421587");
string template ="Your unique job number is {JID} .Your part will be collected by us"
string result = Regex.Replace(template, #"{([^{}]+)}", delegate(Match match)
{
string key = match.Groups[1].Value;
return data[key];
});
_body = result;
return this;
actually i am trying to find out this character {JID} and want to replace {JID} with the value which is store in dictionary but i guess the patter i used here #"{([^{}]+)} is not right to find out the data and replace it with {JID} .
so please help me to how to find {JID} from string and replace. thanks
EDIT
actually the text i was trying to replace that was huge html text and may that is why problem occur.
so now i manage the whole thing this way.
public MailTemplate ParseTemplate(Dictionary<string, string> data)
{
string template = GetBody();
foreach(KeyValuePair<string, string> item in data)
{
if (item.Key.ToUpper() != "ATTACHEDFILEPATH")
{
template = template.Replace("{" + item.Key + "}", item.Value);
}
}
_body = template;
return this;
}

Replace {x} tokens in strings

We have a template URL like:
http://api.example.com/sale?auth_user=xxxxx&auth_pass=xxxxx&networkid={networkid}&category=b2c&country=IT&pageid={pageid}&programid=133&saleid=1&m={master}&optinfo={optinfo}&publisher={publisher}&msisdn={userId}
and I have values for these constant tokens. How can replace all these tokens in C#?
A simple approach is to use a foreach and a Dictionary with a String.Replace:
var values = new Dictionary<string, string> {
{ "{networkid}", "WHEEE!!" }
// etc.
};
var url = "http://api.example.com/sale?auth_user=xxxxx&auth_pass=xxxxx&networkid={networkid}&category=b2c&country=IT&pageid={pageid}&programid=133&saleid=1&m={master}&optinfo={optinfo}&publisher={publisher}&msisdn={userId}";
foreach(var key in values.Keys){
url = url.Replace(key,values[key]);
}
There is no standard way to "replace with dictionary values" in .NET. While there are a number of template engines, it's not very hard to write a small solution for such an operation. Here is an example which runs in LINQPad and utilizes a Regular Expression with a Match Evaluator.
As the result is a URL,
it is the callers responsibility to make sure all the supplied values are correctly encoded. I recommend using Uri.EscapeDataString as appropriate .. but make sure to not double-encode, if it is processed elsewhere.
Additionally, the rules of what to do when no replacement is found should be tailored to need. If not-found replacements should be eliminated entirely along with the query string key, the following can expand the regular expression to #"\w+=({\w+})" to also capture the parameter key in this specific template situation.
string ReplaceUsingDictionary (string src, IDictionary<string, object> replacements) {
return Regex.Replace(src, #"{(\w+)}", (m) => {
object replacement;
var key = m.Groups[1].Value;
if (replacements.TryGetValue(key, out replacement)) {
return Convert.ToString(replacement);
} else {
return m.Groups[0].Value;
}
});
}
void Main()
{
var replacements = new Dictionary<string, object> {
{ "networkid", "WHEEE!!" }
// etc.
};
var src = "http://api.example.com/sale?auth_user=xxxxx&auth_pass=xxxxx&networkid={networkid}&category=b2c&country=IT&pageid={pageid}&programid=133&saleid=1&m={master}&optinfo={optinfo}&publisher={publisher}&msisdn={userId}";
var res = ReplaceUsingDictionary(src, replacements);
// -> "http://api.example.com/sale?..&networkid=WHEEE!!&..&pageid={pageid}&..
res.Dump();
}
More advanced techniques, like reflection and transforms, are possible - but those should be left for the real template engines.
I am guessing you are trying to replace parameters in url with your values. This can be done using C# HttpUtility.ParseQueryString
Get the CurrentURL from
var _myUrl = System.Web.HttpUtility.ParseQueryString(Request.RawUrl);
Read Parameter from your Query string
string value1 = _myUrl ["networkid"];
Write a value into the QueryString object.
_myUrl ["networkid"] = "Your Value";
and then finally turn it back into URL
var _yourURIBuilder= new UriBuilder(_myUrl );
_myUrl = _yourURIBuilder.ToString();
You can use this alos using LinQ
Dictionary<string, string> myVal = new Dictionary<string, string>();
myVal.Add("networkid", "1");
myVal.Add("pageid", "2");
myVal.Add("master", "3");
myVal.Add("optinfo", "4");
myVal.Add("publisher", "5");
myVal.Add("userId", "6");
string url = #"http://api.example.com/sale?auth_user=xxxxx&auth_pass=xxxxx&networkid={networkid}&category=b2c&country=IT&pageid={pageid}&programid=133&saleid=1&m={master}&optinfo={optinfo}&publisher={publisher}&msisdn={userId}";
myVal.Select(a => url = url.Replace(string.Concat("{", a.Key, "}"), a.Value)).ToList();
this line can do your required functionlity
myVal.Select(a => url = url.Replace(string.Concat("{", a.Key, "}"), a.Value)).ToList();
There is a Nuget called StringTokenFormatter that does this well
https://www.nuget.org/packages/StringTokenFormatter/
Regex.Replace makes a single pass over a template string, offering you an opportunity to replace matched expressions. Use it by creating an regular expression that matches any token. Then look up replacement values for the tokens in a dictionary.
static string ReplaceTokens(string template, Dictionary<string, string> replacements) =>
Regex.Replace(template, #"{(\w+)}",
match => replacements.TryGetValue(match.Groups[1].Value, out string replacement) ? replacement : match.Value);
The algorithm completes in time linear with the size of the template string and the replacement strings, so O(t + r). Beware of algorithms that make multiple passes. They run slowly in time O(t * r) and will give incorrect results if one of the replacement values contains a token for later replacement. This unit test shows the pitfall:
public void TestReplaceTokens() {
var replacements = new Dictionary<string, string> {
["Movement"] = "drive {DontReplace}",
["DontReplace"] = "Should not appear"
};
string withReplacements = ReplaceTokens("I always {Movement} in {Direction}.", replacements);
Assert.AreEqual("I always drive {DontReplace} in {Direction}.", withReplacements);
}

c# - regex to find a string that comes after = [duplicate]

This question already has answers here:
regex to find a string that comes after =
(6 answers)
Closed 9 years ago.
So I asked this question before and said I wanted it in javascript but realized later on that it's unecessary data being sent. So it would be great if anybody could help me solve the same thing in C#
What I need is to get several properties out of a string.
The string will look something like:
str = "car[brand=saab][wheels=4]";
There can be more or fewer properties.
I need everything before the first [] in 1 variable.
Then I need each property and its value in a variable.
Easiest way to understand what I want is probably to check my previous question and the answer that solved it :)
I used the regex(slightly different) in your previous question.
string input = "car[brand=saab][wheels=4]";
string product = "";
Dictionary<string, string> props = new Dictionary<string, string>();
foreach (Match m in Regex.Matches(input, #"^(\w+)|\[(\w+)=(.+?)\]"))
{
if (String.IsNullOrEmpty(product))
product = m.Groups[1].Value;
else
props.Add(m.Groups[2].Value, m.Groups[3].Value);
}
try this regex:
(.+?)(\[.+?\])+
and a sample code:
var inputString = "car[brand=saab][wheels=4]";
var pattern = #"(?<v1>.+?)(?<v2>\[.+?\])+";
var v1 = Regex.Match(inputString, pattern).Groups["v1"].Value;
Dictionary<String, String> list = new Dictionary<String, String>();
foreach (Capture capture in Regex.Match(inputString, pattern).Groups["v2"].Captures)
{
var sp = capture.Value.Split('=');
list.Add(sp[0], sp[1]);
}
explain:
(?<name>subexpression)
Captures the matched subexpression into a named group.
You can do this
var lst=Regex.Matches(input,#"(\w+)((?:\[.*?\])+)")
.Cast<Match>()
.Select(x=>new
{
name=x.Groups[1].Value,
value=Regex.Matches(x.Groups[2].Value,#"(?<=\[).*?(?=\])")
.Cast<Match>()
.Select(x=>new
{
name=x.Groups[0].Value.Split('=')[0],
value=x.Groups[0].Value.Split('=')[1]
})
});
Now you can iterate over lst like this
foreach(var parent in lst)
{
parent.name;//car
foreach(var pairs in parent.value)
{
pairs.name;//brand,wheels
pairs.value;//ferrari,4
}
}
So,for input car[brand=a][wheels=4]cycle[brand=b][wheels=2]
Output would be like
car
brand,a
wheels,4
cycle
brand,b
wheels,2
Without regex:
string input = "car[brand=saab][wheels=4]";
var query = from s in input.Replace("]", "").Split('[')
let vars = s.Split('=')
let name = vars[0]
let value = vars.Length > 1 ? vars[1] : ""
select new {Name = name, Value = value};
string firstVar = query.First().Name;
Dictionary<string, string> otherVars = query
.Skip(1)
.ToDictionary(v => v.Name, v => v.Value);
You can access your variables in the dictionary like this string brand = otherVars["brand"]
Since you already have an answer using regex and your comments state it doesn't have to be with a regex, I'll offer an alternative:
The code is
string str = ("car[brand=saab][wheels=4]");
int i = str.IndexOf("[");
string[] details =str.Substring(i).Replace("]","").Split('[');
string name = str.Substring(0, i);
string brand = details[1].Split('=')[1];
string wheels = details[2].Split('=')[1];
This approach assumes the data is always going to be in the same format though; you may need some validation in there depending on your needs...

How can you change a ";" seperated string to some kind of dictionary?

I have a string like this:
"user=u123;name=Test;lastname=User"
I want to get a dictionary for this string like this:
user "u123"
name "Test"
lastname "User"
this way I can easely access the data within the string.
I want to do this in C#.
EDIT:
This is what I have so far:
public static Dictionary<string, string> ValueToDictionary(string value)
{
Dictionary<string, string> result = null;
result = new Dictionary<string, string>();
string[] values = value.Split(';');
foreach (string val in values)
{
string[] valueParts = val.Split('=');
result.Add(valueParts[0], valueParts[1]);
}
return result;
}
But to be honest I really think there is a better way to do this.
Cheers,
M.
You can use LINQ:
var text = "user=u123;name=Test;lastname=User";
var dictionary = (from t in text.Split( ";".ToCharArray() )
let pair = t.Split( "=".ToCharArray(), 2 )
select pair).ToDictionary( p => p[0], p => p[1] );
Split the string by ";".
Iterate over every element in the resulting array and split every element by "=".
Now;
dictionary.add(element[0], element[1]);
I Hope I made it clear enough.
Dictionary<string, string> d = new Dictionary<string, string>();
string s1 = "user=u123;name=Test;lastname=User";
foreach (string s2 in s1.Split(';'))
{
string[] split = s2.Split('=');
d.Add(split[0], split[1]);
}
var dictionary = new Dictionary<string, string>();
var linedValue = "user=u123;name=Test;lastname=User";
var kvps = linedValue.Split(new[] { ';' }); // you may use StringSplitOptions.RemoveEmptyEntries
foreach (var kvp in kvps)
{
var kvpSplit = kvp.Split(new[] { '=' });
var key = kvpSplit.ElementAtOrDefault(0);
var value = kvpSplit.ElementAtOrDefault(1);
dictionary.Add(key, value);
// you may check with .ContainsKey if key is already persistant
// you may check if key and value with string.IsNullOrEmpty
}
If you know for sure that there are no separator chars in your input data, the following works
string input = "user=u123;name=Test;lastname=User";
string[] fragments = input.Split(";=".ToArray());
Dictionary<string,string> result = new Dictionary<string,string>()
for(int i=0; i<fragments.Length-1;i+=2)
result.Add(fragments[i],fragments[i+1]);
It might perform slightly better than some of the other solutions, since it only calls Split() once. Usually I would go for any of the other solutions here, especially if readability of the code is of any value to you.
I think I would do it like this...
String s = "user=u123;name=Test;lastname=User";
Dictionary<string,string> dict = s.ToDictionary();
The implementation of ToDictonary is the same as yours except that I would implement it as an extension method. It does look more natural.

Categories