Is there any way to format a string by name rather than position in C#?
In python, I can do something like this example (shamelessly stolen from here):
>>> print '%(language)s has %(#)03d quote types.' % \
{'language': "Python", "#": 2}
Python has 002 quote types.
Is there any way to do this in C#? Say for instance:
String.Format("{some_variable}: {some_other_variable}", ...);
Being able to do this using a variable name would be nice, but a dictionary is acceptable too.
There is no built-in method for handling this.
Here's one method
string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);
Here's another
Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);
A third improved method partially based on the two above, from Phil Haack
Update: This is now built-in as of C# 6 (released in 2015).
String Interpolation
$"{some_variable}: {some_other_variable}"
I have an implementation I just posted to my blog here: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx
It addresses some issues that these other implementations have with brace escaping. The post has details. It does the DataBinder.Eval thing too, but is still very fast.
Interpolated strings were added into C# 6.0 and Visual Basic 14
Both were introduced through new Roslyn compiler in Visual Studio 2015.
C# 6.0:
return "\{someVariable} and also \{someOtherVariable}" OR
return $"{someVariable} and also {someOtherVariable}"
source: what's new in C#6.0
VB 14:
return $"{someVariable} and also {someOtherVariable}"
source: what's new in VB 14
Noteworthy features (in Visual Studio 2015 IDE):
syntax coloring is supported - variables contained in strings are highlighted
refactoring is supported - when renaming, variables contained in strings get renamed, too
actually not only variable names, but expressions are supported - e.g. not only {index} works, but also {(index + 1).ToString().Trim()}
Enjoy! (& click "Send a Smile" in the VS)
You can also use anonymous types like this:
public string Format(string input, object p)
{
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(p))
input = input.Replace("{" + prop.Name + "}", (prop.GetValue(p) ?? "(null)").ToString());
return input;
}
Of course it would require more code if you also want to parse formatting, but you can format a string using this function like:
Format("test {first} and {another}", new { first = "something", another = "something else" })
There doesn't appear to be a way to do this out of the box. Though, it looks feasible to implement your own IFormatProvider that links to an IDictionary for values.
var Stuff = new Dictionary<string, object> {
{ "language", "Python" },
{ "#", 2 }
};
var Formatter = new DictionaryFormatProvider();
// Interpret {0:x} where {0}=IDictionary and "x" is hash key
Console.WriteLine string.Format(Formatter, "{0:language} has {0:#} quote types", Stuff);
Outputs:
Python has 2 quote types
The caveat is that you can't mix FormatProviders, so the fancy text formatting can't be used at the same time.
The framework itself does not provide a way to do this, but you can take a look at this post by Scott Hanselman. Example usage:
Person p = new Person();
string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}");
Assert.AreEqual("$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo);
This code by James Newton-King is similar and works with sub-properties and indexes,
string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student));
James's code relies on System.Web.UI.DataBinder to parse the string and requires referencing System.Web, which some people don't like to do in non-web applications.
EDIT: Oh and they work nicely with anonymous types, if you don't have an object with properties ready for it:
string name = ...;
DateTime date = ...;
string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });
See https://stackoverflow.com/questions/271398?page=2#358259
With the linked-to extension you can write this:
var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());
and you'll get "foo 2 System.Object".
I think the closest you'll get is an indexed format:
String.Format("{0} has {1} quote types.", "C#", "1");
There's also String.Replace(), if you're willing to do it in multiple steps and take it on faith that you won't find your 'variables' anywhere else in the string:
string MyString = "{language} has {n} quote types.";
MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1");
Expanding this to use a List:
List<KeyValuePair<string, string>> replacements = GetFormatDictionary();
foreach (KeyValuePair<string, string> item in replacements)
{
MyString = MyString.Replace(item.Key, item.Value);
}
You could do that with a Dictionary<string, string> too by iterating it's .Keys collections, but by using a List<KeyValuePair<string, string>> we can take advantage of the List's .ForEach() method and condense it back to a one-liner:
replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});
A lambda would be even simpler, but I'm still on .Net 2.0. Also note that the .Replace() performance isn't stellar when used iteratively, since strings in .Net are immutable. Also, this requires the MyString variable be defined in such a way that it's accessible to the delegate, so it's not perfect yet.
My open source library, Regextra, supports named formatting (amongst other things). It currently targets .NET 4.0+ and is available on NuGet. I also have an introductory blog post about it: Regextra: helping you reduce your (problems){2}.
The named formatting bit supports:
Basic formatting
Nested properties formatting
Dictionary formatting
Escaping of delimiters
Standard/Custom/IFormatProvider string formatting
Example:
var order = new
{
Description = "Widget",
OrderDate = DateTime.Now,
Details = new
{
UnitPrice = 1500
}
};
string template = "We just shipped your order of '{Description}', placed on {OrderDate:d}. Your {{credit}} card will be billed {Details.UnitPrice:C}.";
string result = Template.Format(template, order);
// or use the extension: template.FormatTemplate(order);
Result:
We just shipped your order of 'Widget', placed on 2/28/2014. Your {credit} card will be billed $1,500.00.
Check out the project's GitHub link (above) and wiki for other examples.
private static Regex s_NamedFormatRegex = new Regex(#"\{(?!\{)(?<key>[\w]+)(:(?<fmt>(\{\{|\}\}|[^\{\}])*)?)?\}", RegexOptions.Compiled);
public static StringBuilder AppendNamedFormat(this StringBuilder builder,IFormatProvider provider, string format, IDictionary<string, object> args)
{
if (builder == null) throw new ArgumentNullException("builder");
var str = s_NamedFormatRegex.Replace(format, (mt) => {
string key = mt.Groups["key"].Value;
string fmt = mt.Groups["fmt"].Value;
object value = null;
if (args.TryGetValue(key,out value)) {
return string.Format(provider, "{0:" + fmt + "}", value);
} else {
return mt.Value;
}
});
builder.Append(str);
return builder;
}
public static StringBuilder AppendNamedFormat(this StringBuilder builder, string format, IDictionary<string, object> args)
{
if (builder == null) throw new ArgumentNullException("builder");
return builder.AppendNamedFormat(null, format, args);
}
Example:
var builder = new StringBuilder();
builder.AppendNamedFormat(
#"你好,{Name},今天是{Date:yyyy/MM/dd}, 这是你第{LoginTimes}次登录,积分{Score:{{ 0.00 }}}",
new Dictionary<string, object>() {
{ "Name", "wayjet" },
{ "LoginTimes",18 },
{ "Score", 100.4 },
{ "Date",DateTime.Now }
});
Output:
你好,wayjet,今天是2011-05-04, 这是你第18次登录,积分{ 100.40 }
Check this one:
public static string StringFormat(string format, object source)
{
var matches = Regex.Matches(format, #"\{(.+?)\}");
List<string> keys = (from Match matche in matches select matche.Groups[1].Value).ToList();
return keys.Aggregate(
format,
(current, key) =>
{
int colonIndex = key.IndexOf(':');
return current.Replace(
"{" + key + "}",
colonIndex > 0
? DataBinder.Eval(source, key.Substring(0, colonIndex), "{0:" + key.Substring(colonIndex + 1) + "}")
: DataBinder.Eval(source, key).ToString());
});
}
Sample:
string format = "{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var o = new { foo = 123, bar = true, baz = "this is a test", qux = 123.45, fizzle = DateTime.Now };
Console.WriteLine(StringFormat(format, o));
Performance is pretty ok compared to other solutions.
I doubt this will be possible. The first thing that comes to mind is how are you going to get access to local variable names?
There might be some clever way using LINQ and Lambda expressions to do this however.
Here's one I made a while back. It extends String with a Format method taking a single argument. The nice thing is that it'll use the standard string.Format if you provide a simple argument like an int, but if you use something like anonymous type it'll work too.
Example usage:
"The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })
Would result in "The Smith family has 4 children."
It doesn't do crazy binding stuff like arrays and indexers. But it is super simple and high performance.
public static class AdvancedFormatString
{
/// <summary>
/// An advanced version of string.Format. If you pass a primitive object (string, int, etc), it acts like the regular string.Format. If you pass an anonmymous type, you can name the paramters by property name.
/// </summary>
/// <param name="formatString"></param>
/// <param name="arg"></param>
/// <returns></returns>
/// <example>
/// "The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })
///
/// results in
/// "This Smith family has 4 children
/// </example>
public static string Format(this string formatString, object arg, IFormatProvider format = null)
{
if (arg == null)
return formatString;
var type = arg.GetType();
if (Type.GetTypeCode(type) != TypeCode.Object || type.IsPrimitive)
return string.Format(format, formatString, arg);
var properties = TypeDescriptor.GetProperties(arg);
return formatString.Format((property) =>
{
var value = properties[property].GetValue(arg);
return Convert.ToString(value, format);
});
}
public static string Format(this string formatString, Func<string, string> formatFragmentHandler)
{
if (string.IsNullOrEmpty(formatString))
return formatString;
Fragment[] fragments = GetParsedFragments(formatString);
if (fragments == null || fragments.Length == 0)
return formatString;
return string.Join(string.Empty, fragments.Select(fragment =>
{
if (fragment.Type == FragmentType.Literal)
return fragment.Value;
else
return formatFragmentHandler(fragment.Value);
}).ToArray());
}
private static Fragment[] GetParsedFragments(string formatString)
{
Fragment[] fragments;
if ( parsedStrings.TryGetValue(formatString, out fragments) )
{
return fragments;
}
lock (parsedStringsLock)
{
if ( !parsedStrings.TryGetValue(formatString, out fragments) )
{
fragments = Parse(formatString);
parsedStrings.Add(formatString, fragments);
}
}
return fragments;
}
private static Object parsedStringsLock = new Object();
private static Dictionary<string,Fragment[]> parsedStrings = new Dictionary<string,Fragment[]>(StringComparer.Ordinal);
const char OpeningDelimiter = '{';
const char ClosingDelimiter = '}';
/// <summary>
/// Parses the given format string into a list of fragments.
/// </summary>
/// <param name="format"></param>
/// <returns></returns>
static Fragment[] Parse(string format)
{
int lastCharIndex = format.Length - 1;
int currFragEndIndex;
Fragment currFrag = ParseFragment(format, 0, out currFragEndIndex);
if (currFragEndIndex == lastCharIndex)
{
return new Fragment[] { currFrag };
}
List<Fragment> fragments = new List<Fragment>();
while (true)
{
fragments.Add(currFrag);
if (currFragEndIndex == lastCharIndex)
{
break;
}
currFrag = ParseFragment(format, currFragEndIndex + 1, out currFragEndIndex);
}
return fragments.ToArray();
}
/// <summary>
/// Finds the next delimiter from the starting index.
/// </summary>
static Fragment ParseFragment(string format, int startIndex, out int fragmentEndIndex)
{
bool foundEscapedDelimiter = false;
FragmentType type = FragmentType.Literal;
int numChars = format.Length;
for (int i = startIndex; i < numChars; i++)
{
char currChar = format[i];
bool isOpenBrace = currChar == OpeningDelimiter;
bool isCloseBrace = isOpenBrace ? false : currChar == ClosingDelimiter;
if (!isOpenBrace && !isCloseBrace)
{
continue;
}
else if (i < (numChars - 1) && format[i + 1] == currChar)
{//{{ or }}
i++;
foundEscapedDelimiter = true;
}
else if (isOpenBrace)
{
if (i == startIndex)
{
type = FragmentType.FormatItem;
}
else
{
if (type == FragmentType.FormatItem)
throw new FormatException("Two consequtive unescaped { format item openers were found. Either close the first or escape any literals with another {.");
//curr character is the opening of a new format item. so we close this literal out
string literal = format.Substring(startIndex, i - startIndex);
if (foundEscapedDelimiter)
literal = ReplaceEscapes(literal);
fragmentEndIndex = i - 1;
return new Fragment(FragmentType.Literal, literal);
}
}
else
{//close bracket
if (i == startIndex || type == FragmentType.Literal)
throw new FormatException("A } closing brace existed without an opening { brace.");
string formatItem = format.Substring(startIndex + 1, i - startIndex - 1);
if (foundEscapedDelimiter)
formatItem = ReplaceEscapes(formatItem);//a format item with a { or } in its name is crazy but it could be done
fragmentEndIndex = i;
return new Fragment(FragmentType.FormatItem, formatItem);
}
}
if (type == FragmentType.FormatItem)
throw new FormatException("A format item was opened with { but was never closed.");
fragmentEndIndex = numChars - 1;
string literalValue = format.Substring(startIndex);
if (foundEscapedDelimiter)
literalValue = ReplaceEscapes(literalValue);
return new Fragment(FragmentType.Literal, literalValue);
}
/// <summary>
/// Replaces escaped brackets, turning '{{' and '}}' into '{' and '}', respectively.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
static string ReplaceEscapes(string value)
{
return value.Replace("{{", "{").Replace("}}", "}");
}
private enum FragmentType
{
Literal,
FormatItem
}
private class Fragment
{
public Fragment(FragmentType type, string value)
{
Type = type;
Value = value;
}
public FragmentType Type
{
get;
private set;
}
/// <summary>
/// The literal value, or the name of the fragment, depending on fragment type.
/// </summary>
public string Value
{
get;
private set;
}
}
}
here is a simple method for any object:
using System.Text.RegularExpressions;
using System.ComponentModel;
public static string StringWithFormat(string format, object args)
{
Regex r = new Regex(#"\{([A-Za-z0-9_]+)\}");
MatchCollection m = r.Matches(format);
var properties = TypeDescriptor.GetProperties(args);
foreach (Match item in m)
{
try
{
string propertyName = item.Groups[1].Value;
format = format.Replace(item.Value, properties[propertyName].GetValue(args).ToString());
}
catch
{
throw new FormatException("The format string is not valid");
}
}
return format;
}
And here how to use it:
DateTime date = DateTime.Now;
string dateString = StringWithFormat("{Month}/{Day}/{Year}", date);
output : 2/27/2012
I implemented this is a simple class that duplicates the functionality of String.Format (except for when using classes). You can either use a dictionary or a type to define fields.
https://github.com/SergueiFedorov/NamedFormatString
C# 6.0 is adding this functionality right into the language spec, so NamedFormatString is for backwards compatibility.
I solved this in a slightly different way to the existing solutions.
It does the core of the named item replacement (not the reflection bit that some have done). It is extremely fast and simple...
This is my solution:
/// <summary>
/// Formats a string with named format items given a template dictionary of the items values to use.
/// </summary>
public class StringTemplateFormatter
{
private readonly IFormatProvider _formatProvider;
/// <summary>
/// Constructs the formatter with the specified <see cref="IFormatProvider"/>.
/// This is defaulted to <see cref="CultureInfo.CurrentCulture">CultureInfo.CurrentCulture</see> if none is provided.
/// </summary>
/// <param name="formatProvider"></param>
public StringTemplateFormatter(IFormatProvider formatProvider = null)
{
_formatProvider = formatProvider ?? CultureInfo.CurrentCulture;
}
/// <summary>
/// Formats a string with named format items given a template dictionary of the items values to use.
/// </summary>
/// <param name="text">The text template</param>
/// <param name="templateValues">The named values to use as replacements in the formatted string.</param>
/// <returns>The resultant text string with the template values replaced.</returns>
public string FormatTemplate(string text, Dictionary<string, object> templateValues)
{
var formattableString = text;
var values = new List<object>();
foreach (KeyValuePair<string, object> value in templateValues)
{
var index = values.Count;
formattableString = ReplaceFormattableItem(formattableString, value.Key, index);
values.Add(value.Value);
}
return String.Format(_formatProvider, formattableString, values.ToArray());
}
/// <summary>
/// Convert named string template item to numbered string template item that can be accepted by <see cref="string.Format(string,object[])">String.Format</see>
/// </summary>
/// <param name="formattableString">The string containing the named format item</param>
/// <param name="itemName">The name of the format item</param>
/// <param name="index">The index to use for the item value</param>
/// <returns>The formattable string with the named item substituted with the numbered format item.</returns>
private static string ReplaceFormattableItem(string formattableString, string itemName, int index)
{
return formattableString
.Replace("{" + itemName + "}", "{" + index + "}")
.Replace("{" + itemName + ",", "{" + index + ",")
.Replace("{" + itemName + ":", "{" + index + ":");
}
}
It is used in the following way:
[Test]
public void FormatTemplate_GivenANamedGuid_FormattedWithB_ShouldFormatCorrectly()
{
// Arrange
var template = "My guid {MyGuid:B} is awesome!";
var templateValues = new Dictionary<string, object> { { "MyGuid", new Guid("{A4D2A7F1-421C-4A1D-9CB2-9C2E70B05E19}") } };
var sut = new StringTemplateFormatter();
// Act
var result = sut.FormatTemplate(template, templateValues);
//Assert
Assert.That(result, Is.EqualTo("My guid {a4d2a7f1-421c-4a1d-9cb2-9c2e70b05e19} is awesome!"));
}
Hope someone finds this useful!
Even though the accepted answer gives some good examples, the .Inject as well as some of the Haack examples do not handle escaping. Many also rely heavily on Regex (slower), or DataBinder.Eval which is not available on .NET Core, and in some other environments.
With that in mind, I've written a simple state machine based parser that streams through characters, writing to a StringBuilder output, character by character. It is implemented as String extension method(s) and can take both a Dictionary<string, object> or object with parameters as input (using reflection).
It handles unlimited levels of {{{escaping}}} and throws FormatException when input contains unbalanced braces and/or other errors.
public static class StringExtension {
/// <summary>
/// Extension method that replaces keys in a string with the values of matching object properties.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="injectionObject">The object whose properties should be injected in the string</param>
/// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, object injectionObject) {
return formatString.FormatWith(GetPropertiesDictionary(injectionObject));
}
/// <summary>
/// Extension method that replaces keys in a string with the values of matching dictionary entries.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
/// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, IDictionary<string, object> dictionary) {
char openBraceChar = '{';
char closeBraceChar = '}';
return FormatWith(formatString, dictionary, openBraceChar, closeBraceChar);
}
/// <summary>
/// Extension method that replaces keys in a string with the values of matching dictionary entries.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
/// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, IDictionary<string, object> dictionary, char openBraceChar, char closeBraceChar) {
string result = formatString;
if (dictionary == null || formatString == null)
return result;
// start the state machine!
// ballpark output string as two times the length of the input string for performance (avoids reallocating the buffer as often).
StringBuilder outputString = new StringBuilder(formatString.Length * 2);
StringBuilder currentKey = new StringBuilder();
bool insideBraces = false;
int index = 0;
while (index < formatString.Length) {
if (!insideBraces) {
// currently not inside a pair of braces in the format string
if (formatString[index] == openBraceChar) {
// check if the brace is escaped
if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
// add a brace to the output string
outputString.Append(openBraceChar);
// skip over braces
index += 2;
continue;
}
else {
// not an escaped brace, set state to inside brace
insideBraces = true;
index++;
continue;
}
}
else if (formatString[index] == closeBraceChar) {
// handle case where closing brace is encountered outside braces
if (index < formatString.Length - 1 && formatString[index + 1] == closeBraceChar) {
// this is an escaped closing brace, this is okay
// add a closing brace to the output string
outputString.Append(closeBraceChar);
// skip over braces
index += 2;
continue;
}
else {
// this is an unescaped closing brace outside of braces.
// throw a format exception
throw new FormatException($"Unmatched closing brace at position {index}");
}
}
else {
// the character has no special meaning, add it to the output string
outputString.Append(formatString[index]);
// move onto next character
index++;
continue;
}
}
else {
// currently inside a pair of braces in the format string
// found an opening brace
if (formatString[index] == openBraceChar) {
// check if the brace is escaped
if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
// there are escaped braces within the key
// this is illegal, throw a format exception
throw new FormatException($"Illegal escaped opening braces within a parameter - index: {index}");
}
else {
// not an escaped brace, we have an unexpected opening brace within a pair of braces
throw new FormatException($"Unexpected opening brace inside a parameter - index: {index}");
}
}
else if (formatString[index] == closeBraceChar) {
// handle case where closing brace is encountered inside braces
// don't attempt to check for escaped braces here - always assume the first brace closes the braces
// since we cannot have escaped braces within parameters.
// set the state to be outside of any braces
insideBraces = false;
// jump over brace
index++;
// at this stage, a key is stored in current key that represents the text between the two braces
// do a lookup on this key
string key = currentKey.ToString();
// clear the stringbuilder for the key
currentKey.Clear();
object outObject;
if (!dictionary.TryGetValue(key, out outObject)) {
// the key was not found as a possible replacement, throw exception
throw new FormatException($"The parameter \"{key}\" was not present in the lookup dictionary");
}
// we now have the replacement value, add the value to the output string
outputString.Append(outObject);
// jump to next state
continue;
} // if }
else {
// character has no special meaning, add it to the current key
currentKey.Append(formatString[index]);
// move onto next character
index++;
continue;
} // else
} // if inside brace
} // while
// after the loop, if all braces were balanced, we should be outside all braces
// if we're not, the input string was misformatted.
if (insideBraces) {
throw new FormatException("The format string ended before the parameter was closed.");
}
return outputString.ToString();
}
/// <summary>
/// Creates a Dictionary from an objects properties, with the Key being the property's
/// name and the Value being the properties value (of type object)
/// </summary>
/// <param name="properties">An object who's properties will be used</param>
/// <returns>A <see cref="Dictionary"/> of property values </returns>
private static Dictionary<string, object> GetPropertiesDictionary(object properties) {
Dictionary<string, object> values = null;
if (properties != null) {
values = new Dictionary<string, object>();
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
foreach (PropertyDescriptor prop in props) {
values.Add(prop.Name, prop.GetValue(properties));
}
}
return values;
}
}
Ultimately, all the logic boils down into 10 main states - For when the state machine is outside a bracket and likewise inside a bracket, the next character is either an open brace, an escaped open brace, a closed brace, an escaped closed brace, or an ordinary character. Each of these conditions is handled individually as the loop progresses, adding characters to either an output StringBuffer or a key StringBuffer. When a parameter is closed, the value of the key StringBuffer is used to look up the parameter's value in the dictionary, which then gets pushed into the output StringBuffer. At the end, the value of the output StringBuffer is returned.
string language = "Python";
int numquotes = 2;
string output = language + " has "+ numquotes + " language types.";
Edit:
What I should have said was, "No, I don't believe what you want to do is supported by C#. This is as close as you are going to get."
Related
The .NET Framework gives us the Format method:
string s = string.Format("This {0} very {1}.", "is", "funny");
// s is now: "This is very funny."
I would like an "Unformat" function, something like:
object[] params = string.Unformat("This {0} very {1}.", "This is very funny.");
// params is now: ["is", "funny"]
I know something similar exists in the ANSI-C library (printf vs scanf).
The question: is there something similiar in C#?
Update: Capturing groups with regular expressions are not the solution I need. They are also one way. I'm looking for a system that can work both ways in a single format. It's OK to give up some functionality (like types and formatting info).
There's no such method, probably because of problems resolving ambiguities:
string.Unformat("This {0} very {1}.", "This is very very funny.")
// are the parameters equal to "is" and "very funny", or "is very" and "funny"?
Regular expression capturing groups are made for this problem; you may want to look into them.
Regex with grouping?
/This (.*?) very (.*?)./
If anyone's interested, I've just posted a scanf() replacement for .NET. If regular expressions don't quite cut it for you, my code follows the scanf() format string quite closely.
You can see and download the code I wrote at http://www.blackbeltcoder.com/Articles/strings/a-sscanf-replacement-for-net.
You could do string[] parts = string.Split(' '), and then extract by the index position parts[1] and parts [3] in your example.
Yep. These are called "regular expressions". The one that will do the thing is
This (?<M0>.+) very (?<M1>.+)\.
#mquander: Actualy, PHP solves it even different:
$s = "This is very very funny.";
$fmt = "This %s very %s.";
sscanf($s, $fmt, $one, $two);
echo "<div>one: [$one], two: [$two]</div>\n";
//echo's: "one: [is], two: [very]"
But maybe your regular expression remark can help me. I just need to rewrite "This {0} very {1}." to something like: new Regex(#"^This (.*) very (.*)\.$"). This should be done programmatical, so I can use one format string on the public class interface.
BTW: I've already have a parser to find the parameters: see the Named Format Redux blog entry by Phil Haack (and yes, I also want named paramters to work both ways).
I came across the same problem, i belive that there is a elegante solution using REGEX... but a came up with function in C# to "UnFormat" that works quite well. Sorry about the lack of comments.
/// <summary>
/// Unformats a string using the original formating string.
///
/// Tested Situations:
/// UnFormat("<nobr alt=\"1\">1<nobr>", "<nobr alt=\"{0}\">{0}<nobr>") : "1"
/// UnFormat("<b>2</b>", "<b>{0}</b>") : "2"
/// UnFormat("3<br/>", "{0}<br/>") : "3"
/// UnFormat("<br/>4", "<br/>{0}") : "4"
/// UnFormat("5", "") : "5"
/// UnFormat("<nobr>6<nobr>", "<nobr>{0}<nobr>") : "6"
/// UnFormat("<nobr>2009-10-02<nobr>", "<nobr>{0:yyyy-MM-dd}<nobr>") : "2009-10-02"
/// UnFormat("<nobr><nobr>", "<nobr>{0}<nobr>") : ""
/// UnFormat("bla", "<nobr>{0}<nobr>") : "bla"
/// </summary>
/// <param name="original"></param>
/// <param name="formatString"></param>
/// <returns>If an "unformat" is not possible the original string is returned.</returns>
private Dictionary<int,string> UnFormat(string original, string formatString)
{
Dictionary<int, string> returnList = new Dictionary<int, string>();
try{
int index = -1;
// Decomposes Format String
List<string> formatDecomposed = new List<string> (formatString.Split('{'));
for(int i = formatDecomposed.Count - 1; i >= 0; i--)
{
index = formatDecomposed[i].IndexOf('}') + 1;
if (index > 0 && (formatDecomposed[i].Length - index) > 0)
{
formatDecomposed.Insert(i + 1, formatDecomposed[i].Substring(index, formatDecomposed[i].Length - index));
formatDecomposed[i] = formatDecomposed[i].Substring(0, index);
}
else
//Finished
break;
}
// Finds and indexes format parameters
index = 0;
for (int i = 0; i < formatDecomposed.Count; i++)
{
if (formatDecomposed[i].IndexOf('}') < 0)
{
index += formatDecomposed[i].Length;
}
else
{
// Parameter Index
int parameterIndex;
if (formatDecomposed[i].IndexOf(':')< 0)
parameterIndex = Convert.ToInt16(formatDecomposed[i].Substring(0, formatDecomposed[i].IndexOf('}')));
else
parameterIndex = Convert.ToInt16(formatDecomposed[i].Substring(0, formatDecomposed[i].IndexOf(':')));
// Parameter Value
if (returnList.ContainsKey(parameterIndex) == false)
{
string parameterValue;
if (formatDecomposed.Count > i + 1)
if (original.Length > index)
parameterValue = original.Substring(index, original.IndexOf(formatDecomposed[i + 1], index) - index);
else
// Original String not valid
break;
else
parameterValue = original.Substring(index, original.Length - index);
returnList.Add(parameterIndex, parameterValue);
index += parameterValue.Length;
}
else
index += returnList[parameterIndex].Length;
}
}
// Fail Safe #1
if (returnList.Count == 0) returnList.Add(0, original);
}
catch
{
// Fail Safe #2
returnList = new Dictionary<int, string>();
returnList.Add(0, original);
}
return returnList;
}
I reference earlier reply, wrote a sample see following
string sampleinput = "FirstWord.22222";
Match match = Regex.Match(sampleinput, #"(\w+)\.(\d+)$", RegexOptions.IgnoreCase);
if(match.Success){
string totalmatchstring = match.Groups[0]; // FirstWord.22222
string firstpart = match.Groups[1]; // FirstWord`
string secondpart = match.Groups[2]; // 22222
}
So in my program the user can choose a file with an OpenFileDialog and if he wants to save the file with a SaveFileDialog, the columns and rows of the csv file should change. For this I have already tried this
SaveFileDialog:
List<string> liste = new List<string>();
// Build the header row into the output:
liste.Add(String.Join(',', Enum.GetNames<CsvColumn>()));
CultureInfo ci = new CultureInfo("de-DE"); // neccessary only when running the code from other cultures.
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "CVS (*.cvs)|*.csv|All files (*.*)|*.*";
if (dialog.ShowDialog() == true)
{
string line;
// Read the file and display it line by line.
try
{
System.IO.StreamReader file = new System.IO.StreamReader(path);
while ((line = file.ReadLine()) != null)
{
var cellArray = Regex.Split(line, #"[\t,](?=(?:[^\""]|\""[^\""]*\"")*$)")
.Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")).ToArray();
// Check value of Betrag, only operate on it if there is a decimal value there
if (decimal.TryParse(cellArray[(int)CsvColumn.Betrag], NumberStyles.Any, ci, out decimal betrag))
{
if (betrag >= 0)
{
cellArray[(int)CsvColumn.Soll] = "42590";
cellArray[(int)CsvColumn.Haben] = "441206";
}
else
{
cellArray[(int)CsvColumn.Soll] = "441206";
cellArray[(int)CsvColumn.Haben] = "42590";
}
// Assuming we only write to the purple field when the green field was a decimal:
cellArray[(int)CsvColumn.Belegnummer] = "a dummy text";
}
// Make sure you escape the columns that have a comma
liste.Add(string.Join(",", cellArray.Select(x => x.Contains(',') ? $"\"{x}\"" : x)) + "\n");
}
File.WriteAllLines(dialog.FileName, liste);
file.Close();
}
catch
{
MessageBox.Show("Der Gewählte Prozess wird bereits von einem anderen verwendet,\n " +
" bitte versuchen sie es erneut");
}
}
With that I change the header now, but now I want that when you look at the picture here:
Operations I want to perform:
when the green field is in the positive area, 42590 should be in the blue field and 441206 in the orange field.
if the green value is negative then 441206 should be in the blue field and 42590 in the orange.
In the purple field, a dummy text should simply be automatically written.
So how do I use my C # code to fill in the fields that I have marked in the code?
EDIT
An Example from my Input CSV File in Text Format:
Datum;Wertstellung;Kategorie;Name;Verwendungszweck;Konto;Bank;Betrag;Währung
31.10.2019;01.11.2019;;Some Text;;;;-42,89;EUR
31.10.2019;01.11.2019;;Some Text;;;;-236,98;EUR
31.10.2019;31.10.2019;;Some Text;;;;-200;EUR
30.10.2019;29.10.2019;;Some Text;;;;204,1;EUR
30.10.2019;31.10.2019;;Some Text;;;;-646,98;EUR
The task itself is pretty simple, but your attempt shows many external influences and almost no documentation. This leads to many comments to your post, but a best-practise answer really needs to address many of the smaller elements that you have so far overlooked. You already have the file management sorted out, so I'll try to focus on the array logic.
Make sure you have run and debugged your code before posting, the output from the initial post has a few quirks:
Your input file uses a semi-colon, so you need to split the line by ONLY THAT CHARACTER in your regular expression:
var cellArray = Regex.Split(line, #"[;](?=(?:[^\""]|\""[^\""]*\"")*$)")
.Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")).ToArray();
You can't assume to split the string by multiple delimiters at the same time because only value that contain the file specific delimiter will be quote escaped.
This line is doing nothing, it looks like a previous attempt, .Split() and .ToArray() return new values, they do not manipulate the source value, as you are not using the result of this line of code just remove it:
//line.Split(new char[] { '\t' }).ToArray();
The header row is being written into the first cell of the first row, while it may look like it works, I challenge you to explain the intent. You have also used a semicolon as the delimiter, even though the rest of your output is using comma, so this is fixed too. You will also find it far simpler to write this header row first, before we even read the input file:
List<String> liste = new List<string>();
// Build the header row into the output:
liste.Add("Belegdatum,Buchungsdatum,Belegnummer,Buchungstext,Verwendungszweck,Soll,Haben,Betrag,Währung");
With the german decimal separator being a comma, you will also need to escape the Betrag decimal value in the output
liste.Add(string.Join(",", cellArray.Select(x => x.Contains(',') ? $"\"{x}\"" : x)) + "\n");
Alternatively, you could use a semi-colon like your input data however it is still good practise to test for and escape the values that might contain the delimiter character.
Do you really want the additional line break in the output?
It is not necessary to append each line with the "\n" line feed character because you are later using WriteAllLines(). This method accepts an array of lines and will inject the line break between each line for you. In file processing like this it is only necessary to manually include the line feed if you were storing the output as a single string variable and perhaps later using WriteAllText() to write the final output to file.
This is often not clear when referencing different guidance material on text file manipulations, be aware of this if you copy one technique from an article that maintains an array of the lines, and a separate example that uses a single string variable or StringBuilder or StringWriter approaches.
The line from above now becomes this, note the trailing \n has been removed:
liste.Add(string.Join(",", cellArray.Select(x => x.Contains(',') ? $"\"{x}\"" : x)));
tldr; - Show me the codez!
Simple Forward Processing with Enum for Cell References
Object Oriented Solution
A Simple forward processing approach
It makes for light-weight code but complex logic can be much harder to read, however as you parse each line into the array, you can simply manipulate the values based on your rules. We can refer to this as sequential, in-line or forward processing because we read the input, process and prepare the output one row at a time.
List<string> liste = new List<string>();
// Build the header row into the output:
liste.Add("Belegdatum,Buchungsdatum,Belegnummer,Buchungstext,Verwendungszweck,Soll,Haben,Betrag,Währung");
CultureInfo ci = new CultureInfo("de-DE"); // necessary only when running the code from other cultures.
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "CVS (*.cvs)|*.csv|All files (*.*)|*.*";
if (dialog.ShowDialog() == true)
{
string line;
// Read the file and display it line by line.
try
{
System.IO.StreamReader file = new System.IO.StreamReader(path);
int counter = 0;
while ((line = file.ReadLine()) != null)
{
counter++;
var cellArray = Regex.Split(line, #"[;](?=(?:[^\""]|\""[^\""]*\"")*$)")
.Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")).ToArray();
// Skip lines that fail for any reason
try
{
// Check value of Betrag, only operate on it if there is a decimal value there
if (decimal.TryParse(cellArray[7], NumberStyles.Any, ci, out decimal betrag))
{
if (betrag >= 0)
{
cellArray[5] = "42590";
cellArray[6] = "441206";
}
else
{
cellArray[5] = "441206";
cellArray[6] = "42590";
}
// Assuming we only write to the purple field when the green field was a decimal:
cellArray[2] = "a dummy text";
}
else
{
// Skip lines where the Betrag is not a decimal
// this will cover the case when or if the first line is the header.
continue;
}
}
catch(Exception ex)
{
// Construct a message box to help the user resolve the issue.
// You can use the MessageBox API to allow the user to cancel the process if you want to extend this.
// or remove the message altogether if you want it to silently skip the erroneous rows.
MessageBox.Show("Fehler beim Analysieren der Eingabezeile,\n" +
$"{ex.Message}\n\n " +
$"{counter}:> {line} \n " +
$"{new String(' ', counter.ToString().Length)} - {cellArray.Length} Cells\n " +
$"|{String.Join("|", cellArray)}|\n " +
"\n " +
" Zeile wird verworfen, weiter!");
continue; // advance to the next iteration of the while loop.
}
// Make sure you escape the columns that have a comma
liste.Add(string.Join(",", cellArray.Select(x => x.Contains(',') ? $"\"{x}\"" : x)));
}
File.WriteAllLines(dialog.FileName, liste);
file.Close();
}
catch
{
MessageBox.Show("Der Gewählte Prozess wird bereits von einem anderen verwendet,\n " +
" bitte versuchen sie es erneut");
}
}
Use Named Constants
If you are trying to avoid an OO approach, then it can make the code easier to read by introducing some constants to refer to the indexes, there are many variations to this, but making the code more human readable will assist in future maintenance and understanding of the code.
Define the constants, I recommend doing this inside a static class definition to group these values together, rather than just defining them a local or instance variables.
An enum is another way to do this if you simply need to map strings to ints, or just want to give integer values a name.
public enum CsvColumn
{
Belegdatum = 0,
Buchungsdatum = 1,
Belegnummer = 2,
Buchungstext = 3,
Verwendungszweck = 4,
Soll = 5,
Haben = 6,
Betrag = 7,
Währung = 8
}
Enums have the added benefit of simple commands to retrive all the names of the columns, now we can use this to build the header line AND as the index references in the code:
List<string> liste = new List<string>();
// Build the header row into the output:
liste.Add(String.Join(',', Enum.GetNames<CsvColumn>()));
In previous versions of .Net the generic overload for Enum functions were not defined, in that case you will need to cast the type of the enum:
liste.Add(String.Join(',', Enum.GetNames(typeof(CsvColumn))));
https://learn.microsoft.com/en-us/dotnet/api/system.enum.getnames?view=netframework-4.7.2
In the following logic using the enum references we need to explicitly cast the enum values to int. If you were using int constants instead, then the (int) explicit cast is not required. Either way now we can immediately understand the intent of the logic, wihtout having to remember what the columns at index 5 and 6 are supposed to mean.
if (decimal.TryParse(cellArray[(int)CsvColumn.Betrag], NumberStyles.Any, ci, out decimal betrag))
{
if (betrag >= 0)
{
cellArray[(int)CsvColumn.Soll] = "42590";
cellArray[(int)CsvColumn.Haben] = "441206";
}
else
{
cellArray[(int)CsvColumn.Soll] = "441206";
cellArray[(int)CsvColumn.Haben] = "42590";
}
// Assuming we only write to the purple field when the green field was a decimal:
cellArray[(int)CsvColumn.Belegnummer] = "a dummy text";
}
View a fiddle of this implementation: https://dotnetfiddle.net/Cd10Cd
Of course a similar technique could be used for the "42590" and "441206" values, these must have some sort of business relevance/significance. So store them again as constant named string variables.
What you have here I refer to as Magic Strings, they have no meaning and could easily be corrupted during code refactoring, if the discrete value has a specific business meaning, then it should also have a specific name in the code.
OO Approach
Using an Object-Oriented approach can mean a lot of things, in this context there are 3 different concerns or processes that we want to separate, Parsing the input, executing business logic, Formatting the output. You could simply make 3 methods that accept an array of strings, but this code becomes hard to understand, by using a structured object to model our business domain concept of a row from the CSV file we can remove many auumptions, for instance, which element in the array is the Betrag (Value).
View the OO Fiddle here: https://dotnetfiddle.net/tjxcQN
You could use this Object-Oriented concept in the above code directly, parsing each line into the object, processing and serializing back to a string value all in one code block, however that makes it hard to gain a higer level view of the process which is necessary to understand the code itself. Even if you do this in your head, when we look at our peer's code, we break it down into blocks or discrete steps. So to be a good coding citizen, modularise your logic into functional methods where you can, it will assist you in the future when you need to write unit tests and it will help to keep your code clean, but also to allow us to extend your code in the future.
For this example we will create a simple model to represent each line. Note that this example takes the extra step of parsing the date fields into DateTime properties even though you do not need them for this example. I am deliberately using constants instead of an enum to show you a different concept. You use what ever makes sense on the day, this is still a first principals approach, there are different libraries you can use to manage serialization to and from CSV, XML, JSON and other text formats.
If your business needs are to display this information in an application, rather than just reading a file and writing directly back out to another file, then this information may be helpful to you, otherwise it is a good habit to get into if you are just practising because larger applications or larger teams will require this level of modularisation, which itself is not specifically an OO concept... The OO part comes from where we define the processing logic, in this example the BankRecord contains the logic to parse the CSV string input and how to serialize back to a CSV output.
public class BankRecord
{
/// <summary> Receipt Date </summary>
public DateTime Belegdatum { get; set; }
/// <summary> Entry Date </summary>
public DateTime Buchungsdatum { get; set; }
/// <summary>Sequence number</summary>
public string Belegnummer { get; set; }
/// <summary>Memo - Description</summary>
public string Buchungstext { get; set; }
/// <summary>Purpose</summary>
public string Verwendungszweck { get; set; }
/// <summary>Debit</summary>
public string Soll { get; set; }
/// <summary>Credit</summary>
public string Haben { get; set; }
/// <summary>Amount</summary>
public decimal Betrag { get; set; }
/// <summary>Currency</summary>
public string Währung { get; set; }
/// <summary> Column Index Definitions to simplify the CSV parsing</summary>
public static class Columns
{
public const int Belegdatum = 0;
public const int Buchungsdatum = 1;
public const int Belegnummer = 2;
public const int Buchungstext = 3;
public const int Verwendungszweck = 4;
public const int Soll = 5;
public const int Haben = 6;
public const int Betrag = 7;
public const int Währung = 8;
/// <summary>
/// Construct a CSV Header row from these column definitions
/// </summary>
public static string BuildCsvHeader()
{
return String.Join(',',
nameof(Belegdatum),
nameof(Buchungsdatum),
nameof(Belegnummer),
nameof(Buchungstext),
nameof(Verwendungszweck),
nameof(Soll),
nameof(Haben),
nameof(Betrag),
nameof(Währung));
}
}
/// <summary>
/// Parse a CSV string using the <see cref="Columns"/> definitions as the index for each of the named properties in this class
/// </summary>
/// <param name="csvLine">The CSV Line to parse</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <returns>BankRecord populated from the input string</returns>
public static BankRecord FromCSV(string csvLine, IFormatProvider provider)
{
var cellArray = Regex.Split(csvLine, #"[\t,](?=(?:[^\""]|\""[^\""]*\"")*$)")
.Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")).ToArray();
// TODO: add in some validation, today we'll just check the number of cells.
if (cellArray.Length != 9)
throw new NotSupportedException("Input CSV did not contain the expected number of columns. (Expected 9)");
// The following is redimentary and doesn't perform any active error checking, the good news is that when it fails you
// will atleast know that it was in this specific method. Proper production level error handling is out of scope for this issue.
var transaction = new BankRecord();
transaction.Belegdatum = DateTime.Parse(cellArray[Columns.Belegdatum], provider);
transaction.Buchungsdatum = DateTime.Parse(cellArray[Columns.Buchungsdatum], provider);
transaction.Belegnummer = cellArray[Columns.Belegnummer];
transaction.Buchungstext = cellArray[Columns.Buchungstext];
transaction.Verwendungszweck = cellArray[Columns.Verwendungszweck];
transaction.Soll = cellArray[Columns.Soll];
transaction.Haben = cellArray[Columns.Haben];
transaction.Betrag = Decimal.Parse(cellArray[Columns.Betrag], provider);
transaction.Währung = cellArray[Columns.Währung];
return transaction;
}
/// <summary>
/// Write this object out to a CSV string that can be interpreted using the <see cref="Columns"/> definitions as the index for each of the named properties in this class
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <returns>CSV string that represents this record./returns>
public string ToCSV(IFormatProvider provider)
{
return String.Join(',',
CsvEscape(Belegdatum, provider),
CsvEscape(Buchungsdatum, provider),
CsvEscape(Belegnummer, provider),
CsvEscape(Buchungstext, provider),
CsvEscape(Verwendungszweck, provider),
CsvEscape(Soll, provider),
CsvEscape(Haben, provider),
CsvEscape(Betrag, provider),
CsvEscape(Währung, provider));
}
/// <summary>
/// Simple routine to format a value for CSV output
/// </summary>
/// <param name="value">The value to serialize</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <returns>Value escaped and safe for direct inclusion in a CSV output</returns>
private string CsvEscape(object value, IFormatProvider provider)
{
if (value == null)
return string.Empty;
string stringValue = String.Format(provider, "{0}", value);
if (stringValue.Contains(','))
return $"\"{stringValue}\"";
else
return stringValue;
}
/// <summary>
/// Format a Date value for CSV output
/// </summary>
/// <param name="value">The value to serialize</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <remarks>Simple override to allow for common syntax between types, removes the need to the caller to understand the differences</remarks>
/// <returns>Value escaped and safe for direct inclusion in a CSV output</returns>
private string CsvEscape(DateTime value, IFormatProvider provider)
{
string stringValue = String.Format(provider, "{0:d}", value);
if (stringValue.Contains(','))
return $"\"{stringValue}\"";
else
return stringValue;
}
}
The following is the process logic:
CultureInfo ci = new CultureInfo("de-DE"); // neccessary only when running the code from other cultures.
// I'll leave this in, but don't call your list, "liste" instead give it some context or meaing, like "records" or "transactions"
List<BankRecord> liste = new List<BankRecord>();
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "CVS (*.cvs)|*.csv|All files (*.*)|*.*";
if (dialog.ShowDialog() == true)
{
string line;
// Read the file line by line.
try
{
#region Parse the input File
System.IO.StreamReader file = new System.IO.StreamReader(path);
while ((line = file.ReadLine()) != null)
{
try
{
liste.Add(BankRecord.FromCSV(line, ci));
}
catch
{
// TODO: re-raise or otherwise handle this error if you want.
// today we will simply ignore erroneous entries and will suppress this error
}
}
#endregion Parse the input File
#region Evaluate your business rules
// Evaluate your business rules here, natively in C#, no arrays or indexes, just manipulate the business domain object.
// assuming that Belegnummer is a sequencing number, not sure if it is from the start of this file or a different context...
// This just demonstrates a potential reason for NOT encapsulating the processing logic inside the BankRecord class.
int previousLineNumber = 47; // aribrary start...
foreach (var transaction in liste)
{
// Check value of Betrag, only operate on it if there is a decimal value there
if (transaction.Betrag >= 0)
{
transaction.Soll = "42590";
transaction.Haben = "441206";
}
else
{
transaction.Soll = "441206";
transaction.Haben = "42590";
}
transaction.Belegnummer = $"#{++previousLineNumber}";
}
#endregion Evaluate your business rules
#region Now write to the output
List<string> outputLines = new List<string>();
outputLines.Add(BankRecord.Columns.BuildCsvHeader());
outputLines.AddRange(liste.Select(x => x.ToCSV(ci)));
File.WriteAllLines(dialog.FileName, outputLines);
file.Close();
#endregion Now write to the output
}
catch
{
MessageBox.Show("Der Gewählte Prozess wird bereits von einem anderen verwendet,\n " +
" bitte versuchen sie es erneut");
}
}
Final Output:
Belegdatum,Buchungsdatum,Belegnummer,Buchungstext,Verwendungszweck,Soll,Haben,Betrag,Währung
31.10.2019,01.11.2019,#48,Some Text,,42590,441206,"50,43",EUR
31.10.2019,01.11.2019,#49,Some Text,,441206,42590,"-239,98",EUR
31.10.2019,31.10.2019,#50,Some Text,,441206,42590,-500,EUR
Belegdatum
Buchungsdatum
Belegnummer
Buchungstext
Verwendungszweck
Soll
Haben
Betrag
Währung
31.10.2019
01.11.2019
#48
Some Text
42590
441206
50,43
EUR
31.10.2019
01.11.2019
#49
Some Text
441206
42590
-239,98
EUR
31.10.2019
31.10.2019
#50
Some Text
441206
42590
-500
EUR
my input is abc#xyz.com
I want to replace xyz.com with mnop.com
So the final result will be abc#mnop.com
The present way
var input = "abc#xyz.com";
var output = input.Split('#')[0] + "#mnop.com";
What is the appropriate way?Any regular expression?
Replace function sample (with correct exception handle)
/// <summary>
/// replace email domain
/// </summary>
/// <param name="email"> email </param>
/// <param name="newDomain"> new domain </param>
/// <returns></returns>
private string ReplaceMailDomain(string email, string newDomain)
{
if (email == null) throw new ArgumentNullException("email");
int pos = email.IndexOf('#');
if (pos < 0)
{
throw new ArgumentException("Invalid email", "email");
}
else
{
return email.Substring(0, pos + 1) + newDomain;
}
}
Usage:
string email = ReplaceMailDomain("abc#xyz.com", "mnop.com");
If you know before hand the string you are looking for, you can simply use the normal Replace(string toFind, string replacement). You can use indexof and substring to get the domain name.
Given the below:
string input = "abc#xyz.com";
string domain = input.Substring(input.IndexOf('#') + 1);
Console.WriteLine(input.Replace(domain, "mnop.com"));
It yields
abc#mnop.com
If you just want to "take something before # sign and append some domain name", then one of possible approaches is
var input = "abc#xyz.com";
var output = input.Substring(0, input.IndexOf('#')) + "#mnop.com";
It is slightly "lightweight" than using Split because it doesn't creates array (and in fact you doesn't need array since you're using only its first element).
Assuming the address will be valid, meaning there is only one # character, you can use the simple Replace() function
var domain = "xyz.com";
var newdomain = "xyz.com";
var input = "abc#xyz.com";
var output = input.Replace("#" + domain, "#" + newdomain);
Regexps would be overkill for this.
If you want to remove any domain name and replace it, then use Substring() and IndexOf() for that
var output = input.Substring(0, input.IndexOf('#') + 1) + newdomain;
Note that this will throw an exception if the string doesn't contain the # character.
I'm writing automatic e-mailer. It has to scan database every X minutes and email people with reminders etc.
I have all underlying code ready. All I need now is to format emails.
Is there any predefined templating system in C# so I can create a folder with different templates and eg. tags such as {NAME} so I just find those and replace it.
I can do it manually with opening a *.txt document and replacing those specific tags etc, however is there anything smarter? I wouldn't want to reinvent the wheel.
I'd look at using StringTemplate: http://www.stringtemplate.org/
You can do it with MVC 3's Razor templates, even in non-web applications.
An Internet search for Razor templates non-web will turn up many examples.
It's not too difficult to write from scratch. I wrote this quick utility to do exactly what you described. It looks for tokens in the pattern {token} and replaces them with the value that it retrieves from the NameValueCollection. Tokens in the string correspond to keys in the collection which get replaced out for the value of the key in the collection.
It also has the added bonus of being simple enough to customize exactly as you need it.
public static string ReplaceTokens(string value, NameValueCollection tokens)
{
if (tokens == null || tokens.Count == 0 || string.IsNullOrEmpty(value)) return value;
string token = null;
foreach (string key in tokens.Keys)
{
token = "{" + key + "}";
value = value.Replace(token, tokens[key]);
}
return value;
}
USAGE:
public static bool SendEcard(string fromName, string fromEmail, string toName, string toEmail, string message, string imageUrl)
{
var body = GetEmailBody();
var tokens = new NameValueCollection();
tokens["sitedomain"] = "http://example.com";
tokens["fromname"] = fromName;
tokens["fromemail"] = fromEmail;
tokens["toname"] = toName;
tokens["toemail"] = toEmail;
tokens["message"] = message;
tokens["image"] = imageUrl;
var msg = CreateMailMessage();
msg.Body = StringUtility.ReplaceTokens(body, tokens);
//...send email
}
You can use the nVelocity
string templateDir = HttpContext.Current.Server.MapPath("Templates");
string templateName = "SimpleTemplate.vm";
INVelocityEngine fileEngine =
NVelocityEngineFactory.CreateNVelocityFileEngine(templateDir, true);
IDictionary context = new Hashtable();
context.Add(parameterName , value);
var output = fileEngine.Process(context, templateName);
If you are using ASP.NET 4 you can download RazorMail from the Nuget Gallery. It allows creation of emails using the Razor View Engine outwith the context of an MVC http request.
More details can be found via the following links...
http://www.nuget.org/List/Packages/RazorMail
https://github.com/wduffy/RazorMail/
I use this alot, plug in a Regex and a method that selects the replacement value based on the match.
/// </summary>
/// <param name="input">The text to perform the replacement upon</param>
/// <param name="pattern">The regex used to perform the match</param>
/// <param name="fnReplace">A delegate that selects the appropriate replacement text</param>
/// <returns>The newly formed text after all replacements are made</returns>
public static string Transform(string input, Regex pattern, Converter<Match, string> fnReplace)
{
int currIx = 0;
StringBuilder sb = new StringBuilder();
foreach (Match match in pattern.Matches(input))
{
sb.Append(input, currIx, match.Index - currIx);
string replace = fnReplace(match);
sb.Append(replace);
currIx = match.Index + match.Length;
}
sb.Append(input, currIx, input.Length - currIx);
return sb.ToString();
}
Example Usage
Dictionary<string, string> values = new Dictionary<string, string>();
values.Add("name", "value");
TemplateValues tv = new TemplateValues(values);
Assert.AreEqual("valUE", tv.ApplyValues("$(name:ue=UE)"));
/// <summary>
/// Matches a makefile macro name in text, i.e. "$(field:name=value)" where field is any alpha-numeric + ('_', '-', or '.') text identifier
/// returned from group "field". the "replace" group contains all after the identifier and before the last ')'. "name" and "value" groups
/// match the name/value replacement pairs.
/// </summary>
class TemplateValues
{
static readonly Regex MakefileMacro = new Regex(#"\$\((?<field>[\w-_\.]*)(?<replace>(?:\:(?<name>[^:=\)]+)=(?<value>[^:\)]*))+)?\)");
IDictionary<string,string> _variables;
public TemplateValues(IDictionary<string,string> values)
{ _variables = values; }
public string ApplyValues(string template)
{
return Transform(input, MakefileMacro, ReplaceVariable);
}
private string ReplaceVariable(Match m)
{
string value;
string fld = m.Groups["field"].Value;
if (!_variables.TryGetValue(fld, out value))
{
value = String.Empty;
}
if (value != null && m.Groups["replace"].Success)
{
for (int i = 0; i < m.Groups["replace"].Captures.Count; i++)
{
string replace = m.Groups["name"].Captures[i].Value;
string with = m.Groups["value"].Captures[i].Value;
value = value.Replace(replace, with);
}
}
return value;
}
}
(Even though you've ticked an answer, for future reference) - I got some amazing responses on my question of a similar nature: Which approach to templating in C sharp should I take?
I need to create a very long string in a program, and have been using String.Format. The problem I am facing is keeping track of all the numbers when you have more than 8-10 parameters.
Is it possible to create some form of overload that will accept a syntax similar to this?
String.Format("You are {age} years old and your last name is {name} ",
{age = "18", name = "Foo"});
How about the following, which works both for anonymous types (the example below), or regular types (domain entities, etc):
static void Main()
{
string s = Format("You are {age} years old and your last name is {name} ",
new {age = 18, name = "Foo"});
}
using:
static readonly Regex rePattern = new Regex(
#"(\{+)([^\}]+)(\}+)", RegexOptions.Compiled);
static string Format(string pattern, object template)
{
if (template == null) throw new ArgumentNullException();
Type type = template.GetType();
var cache = new Dictionary<string, string>();
return rePattern.Replace(pattern, match =>
{
int lCount = match.Groups[1].Value.Length,
rCount = match.Groups[3].Value.Length;
if ((lCount % 2) != (rCount % 2)) throw new InvalidOperationException("Unbalanced braces");
string lBrace = lCount == 1 ? "" : new string('{', lCount / 2),
rBrace = rCount == 1 ? "" : new string('}', rCount / 2);
string key = match.Groups[2].Value, value;
if(lCount % 2 == 0) {
value = key;
} else {
if (!cache.TryGetValue(key, out value))
{
var prop = type.GetProperty(key);
if (prop == null)
{
throw new ArgumentException("Not found: " + key, "pattern");
}
value = Convert.ToString(prop.GetValue(template, null));
cache.Add(key, value);
}
}
return lBrace + value + rBrace;
});
}
As of C#6, this kind of string interpolation is now possible using the new string interpolation syntax:
var formatted = $"You are {age} years old and your last name is {name}";
not quite the same but sort of spoofing it... use an extension method, a dictionary and a little code:
something like this...
public static class Extensions {
public static string FormatX(this string format, params KeyValuePair<string, object> [] values) {
string res = format;
foreach (KeyValuePair<string, object> kvp in values) {
res = res.Replace(string.Format("{0}", kvp.Key), kvp.Value.ToString());
}
return res;
}
}
What about if age/name is an variable in your application. So you would need a sort syntax to make it almost unique like {age_1}?
If you have trouble with 8-10 parameters: why don't use
"You are " + age + " years old and your last name is " + name + "
primitive implementation:
public static class StringUtility
{
public static string Format(string pattern, IDictionary<string, object> args)
{
StringBuilder builder = new StringBuilder(pattern);
foreach (var arg in args)
{
builder.Replace("{" + arg.Key + "}", arg.Value.ToString());
}
return builder.ToString();
}
}
Usage:
StringUtility.Format("You are {age} years old and your last name is {name} ",
new Dictionary<string, object>() {{"age" = 18, "name" = "Foo"}});
You could also use a anonymous class, but this is much slower because of the reflection you'll need.
For a real implementation you should use regular expression to
allow escaping the {}
check if there are placeholders that where not replaced, which is most probably a programming error.
Although C# 6.0 can now do this with string interpolation, it's sometimes necessary to do this with dynamic format strings at runtime. I've been unable to use other methods that require DataBinder.Eval due to them not being available in .NET Core, and have been dissatisfied with the performance of Regex solutions.
With that in mind, here's a Regex free, state machine based parser that I've written up. It handles unlimited levels of {{{escaping}}} and throws FormatException when input contains unbalanced braces and/or other errors. Although the main method takes a Dictionary<string, object>, the helper method can also take an object and use its parameters via reflection.
public static class StringExtension {
/// <summary>
/// Extension method that replaces keys in a string with the values of matching object properties.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="injectionObject">The object whose properties should be injected in the string</param>
/// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, object injectionObject) {
return formatString.FormatWith(GetPropertiesDictionary(injectionObject));
}
/// <summary>
/// Extension method that replaces keys in a string with the values of matching dictionary entries.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
/// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, IDictionary<string, object> dictionary) {
char openBraceChar = '{';
char closeBraceChar = '}';
return FormatWith(formatString, dictionary, openBraceChar, closeBraceChar);
}
/// <summary>
/// Extension method that replaces keys in a string with the values of matching dictionary entries.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
/// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, IDictionary<string, object> dictionary, char openBraceChar, char closeBraceChar) {
string result = formatString;
if (dictionary == null || formatString == null)
return result;
// start the state machine!
// ballpark output string as two times the length of the input string for performance (avoids reallocating the buffer as often).
StringBuilder outputString = new StringBuilder(formatString.Length * 2);
StringBuilder currentKey = new StringBuilder();
bool insideBraces = false;
int index = 0;
while (index < formatString.Length) {
if (!insideBraces) {
// currently not inside a pair of braces in the format string
if (formatString[index] == openBraceChar) {
// check if the brace is escaped
if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
// add a brace to the output string
outputString.Append(openBraceChar);
// skip over braces
index += 2;
continue;
}
else {
// not an escaped brace, set state to inside brace
insideBraces = true;
index++;
continue;
}
}
else if (formatString[index] == closeBraceChar) {
// handle case where closing brace is encountered outside braces
if (index < formatString.Length - 1 && formatString[index + 1] == closeBraceChar) {
// this is an escaped closing brace, this is okay
// add a closing brace to the output string
outputString.Append(closeBraceChar);
// skip over braces
index += 2;
continue;
}
else {
// this is an unescaped closing brace outside of braces.
// throw a format exception
throw new FormatException($"Unmatched closing brace at position {index}");
}
}
else {
// the character has no special meaning, add it to the output string
outputString.Append(formatString[index]);
// move onto next character
index++;
continue;
}
}
else {
// currently inside a pair of braces in the format string
// found an opening brace
if (formatString[index] == openBraceChar) {
// check if the brace is escaped
if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
// there are escaped braces within the key
// this is illegal, throw a format exception
throw new FormatException($"Illegal escaped opening braces within a parameter - index: {index}");
}
else {
// not an escaped brace, we have an unexpected opening brace within a pair of braces
throw new FormatException($"Unexpected opening brace inside a parameter - index: {index}");
}
}
else if (formatString[index] == closeBraceChar) {
// handle case where closing brace is encountered inside braces
// don't attempt to check for escaped braces here - always assume the first brace closes the braces
// since we cannot have escaped braces within parameters.
// set the state to be outside of any braces
insideBraces = false;
// jump over brace
index++;
// at this stage, a key is stored in current key that represents the text between the two braces
// do a lookup on this key
string key = currentKey.ToString();
// clear the stringbuilder for the key
currentKey.Clear();
object outObject;
if (!dictionary.TryGetValue(key, out outObject)) {
// the key was not found as a possible replacement, throw exception
throw new FormatException($"The parameter \"{key}\" was not present in the lookup dictionary");
}
// we now have the replacement value, add the value to the output string
outputString.Append(outObject);
// jump to next state
continue;
} // if }
else {
// character has no special meaning, add it to the current key
currentKey.Append(formatString[index]);
// move onto next character
index++;
continue;
} // else
} // if inside brace
} // while
// after the loop, if all braces were balanced, we should be outside all braces
// if we're not, the input string was misformatted.
if (insideBraces) {
throw new FormatException("The format string ended before the parameter was closed.");
}
return outputString.ToString();
}
/// <summary>
/// Creates a Dictionary from an objects properties, with the Key being the property's
/// name and the Value being the properties value (of type object)
/// </summary>
/// <param name="properties">An object who's properties will be used</param>
/// <returns>A <see cref="Dictionary"/> of property values </returns>
private static Dictionary<string, object> GetPropertiesDictionary(object properties) {
Dictionary<string, object> values = null;
if (properties != null) {
values = new Dictionary<string, object>();
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
foreach (PropertyDescriptor prop in props) {
values.Add(prop.Name, prop.GetValue(properties));
}
}
return values;
}
}
Ultimately, all the logic boils down into 10 main states - For when the state machine is outside a bracket and likewise inside a bracket, the next character is either an open brace, an escaped open brace, a closed brace, an escaped closed brace, or an ordinary character. Each of these conditions is handled individually as the loop progresses, adding characters to either an output StringBuffer or a key StringBuffer. When a parameter is closed, the value of the key StringBuffer is used to look up the parameter's value in the dictionary, which then gets pushed into the output StringBuffer.
EDIT:
I've turned this into a full on project at https://github.com/crozone/FormatWith