Format a Resource string into another one? - c#

What would be the best way to accomplish something like this?
Suppose I have the following pair of Resource strings.
BadRequestParameter: Potential bad request aborted before execution.
RequiredParameterConstraint: {0} parameter requires a value. {1}
And suppose I want to set {1} on the second one, to the value of BadRequestParameter. I could easily do that using string.Format. But now suppose I have lots of Resource strings like the second one, all of which include some other Resource string in them.
What would be the best way to code this? Is using string.Format repeateadly in each case really all that I can do?
Update
I'll try to explain myself better. These are the resource strings I actually have:
BadRequestParameter Potential bad request aborted before execution.
EmptyVector Vectorized requests require at least one element. {0}
OverflownVector Vectorized requests can take at most one hundred elements. {0}
RequiredParamConstraint {0} parameter requires a value. {1}
SortMinMaxConstraint {0} parameter value '{1}' does not allow Min or Max parameters in this query. {2}
SortRangeTypeConstraint Expected {0} parameter Type '{1}'. Actual: '{2}'. {3}
SortValueConstraint {0} parameter does not allow '{1}' as a value in this query. {2}
I'd like to avoid writing the string in BadRequestParameter at the end of each of those lines. Therefore, I added a format at the end of those strings. The issue now is that I'd like to somehow auto-reference {x} to BadRequestParameter, in order to avoid having to make calls like
string.Format(Error.EmptyVector, Error.BadRequestParameter);

I have lots of Resource strings like the second one, all of which include some other Resource string in them.
Instead of storing pre-made format strings ready for use, you could store raw material for building real format strings, and add code to expand them pro grammatically before use. For example, you could store strings like this:
BadRequestParameter: Potential bad request aborted before execution.
SupportNumber: (123)456-7890
CallTechSupport: You need to call technical support at {SupportNumber}.
RequiredParameterConstraint: {{0}} parameter requires a value. {BadRequestParameter} {CallTechSupport}
Of course passing these strings to string.Format as-is is not going to work. You need to parse these strings, for example with RegExps, and find all instances where you have a word between curly braces, instead of a number. You could then replace each word with its sequence number, and produce an array of parameters based on the names that you find between curly braces. In this case, you will get these two values (pseudocode):
formatString = "{{0}} parameter requires a value. {0} {1}";
// You replaced {BadRequestParameter} with {0} and {CallTechSupport} with {1}
parameters = {
"Potential bad request aborted before execution."
, "You need to call technical support at (123)456-7890."
};
Note: Of course, producing this array of parameters required recursion.
At this point, you can invoke string.Format to produce your final string:
var res = string.Format(formatString, parameters);
This returns the string that has resource strings pre-replaced for your callers:
"{0} parameter requires a value. Potential bad request aborted before execution. You need to call technical support at (123)456-7890."
The callers can now use this string for formatting, without bothering with other resource values.

Yes :-) unless you want to make a helper method that is shorter, but that would really just be for convenience sake
public static string f(string format, params object[] p)
{
return string.Format(format, p);
}

IF you treat the argument indicators {#} as wild cards then why would it make sense for you to pre-fill them inside of your resource.
I see absolutely nothing wrong with
String.Format(RequiredParamterConstraint, "something", BadRequestParameter);

Related

Console WriteLine(var, var); not displaying second variable?

I'm writing a little text-based adventure in the console as one of my first projects in C#.
At a certain point, I want to do the following:
Console.WriteLine(intro);
var name = Console.ReadLine();
Console.Clear();
Console.WriteLine(replyOne, name, replyTwo);
However, on that last line, only the first variable (replyOne) is displayed. How can I display all the values?
Depends on what's on replyOne, but you are using a Console.WriteLine overload that takes a format string as the first argument and a number of objects for substitution of that format string (this one). That's what's called Composite Formatting in .NET
If what you want to do is concatenate the strings, you can do it in several ways:
Pass only one string to Console.WriteLine:
Console.WriteLine(replyOne + name + replyTwo);
Use a format string... this would use the same overload you are using now, but passing a formatting string for substitution on the first argument:
Console.WriteLine("{0}{1}{2}", replyOne, name, replyTwo);
Use an interpolated string (C# 6 and up only)
Console.WriteLine($"{replyOne}{name}{replyTwo}");
In the multi-argument overloads of the Console.WriteLine the first parameter is supposed to be a format string, and everything else as a substitution values.
See Console.WriteLine Method (String, Object) for details.
If you want Console.WriteLine to output a formatted string the first argument has to be the string that contains the placeholders that define the formatting. So assuming you just want to output the three strings consecutively, you'll need something like this:
Console.WriteLine("{0} {1} {2}", replyOne, name, replyTwo);
which will output the three strings separated by spaces.
You can replace the spaces by commas, newlines (\n) or tabs (\t) to get the formatting you need.
Try this instead:
Console.WriteLine(replyOne + name + replyTwo);
Or something similar.
As you're calling the method right now, it's treating the replyOne value as the format for the output, not an individual output item. And of course, your format doesn't have a {0} or {1} for the format arguments in the call, name and replyTwo, so they are omitted.
You can just concatenate the output text yourself, as above, as use that as the entire format (without any format arguments at all).
There are, of course, lots of other options for formatting the output. The above fits what you've posted so far.
Console.WriteLine("{0},{1},{2}",replyOne,name,replyTwo);

Strange behaviour of String.Format when (mis-)using placeholders

When I learned about the String.Format function, I did the mistake to think that it's acceptable to name the placeholders after the colon, so I wrote code like this:
String.Format("A message: '{0:message}'", "My message");
//output: "A message: 'My message'"
I just realized that the string behind the colon is used to define the format of the placeholder and may not be used to add a comment as I did.
But apparently, the string behind the colon is used for the placeholder if:
I want to fill the placeholder with an integer and
I use an unrecognized formating-string behind the colon
But this doesn't explain to me, why the string behind the colon is used for the placeholder if I provide an integer.
Some examples:
//Works for strings
String.Format("My number is {0:number}!", "10")
//output: "My number is 10!"
//Works without formating-string
String.Format("My number is {0}!", 10)
//output: "My number is 10!"
//Works with recognized formating string
String.Format("My number is {0:d}!", 10)
//output: "My number is 10!"
//Does not work with unrecognized formating string
String.Format("My number is {0:number}!", 10)
//output: "My number is number!"
Why is there a difference between the handling of strings and integers? And why is the fallback to output the formating string instead of the given value?
Just review the MSDN page about composite formatting for clarity.
A basic synopsis, the format item syntax is:
{ index[,alignment][:formatString]}
So what appears after the : colon is the formatString. Look at the "Format String Component" section of the MSDN page for what kind of format strings are predefined. You will not see System.String mentioned in that list. Which is no great surprise, a string is already "formatted" and will only ever appear in the output as-is.
Composite formatting is pretty lenient to mistakes, it won't throw an exception when you specify an illegal format string. That the one you used isn't legal is already pretty evident from the output you get. And most of all, the scheme is extensible. You can actually make a :message format string legal, a class can implement the ICustomFormatter interface to implement its own custom formatting. Which of course isn't going to happen on System.String, you cannot modify that class.
So this works as expected. If you don't get the output you expected then this is pretty easy to debug, you've just go two mistakes to consider. The debugger eliminates one (wrong argument), your eyes eliminates the other.
String.Format article on MSDN has following description:
A format item has this syntax: { index[,alignment][ :formatString] }
...
formatString Optional.
A string that specifies the format of the
corresponding argument's result string. If you omit formatString, the
corresponding argument's parameterless ToString method is called to
produce its string representation. If you specify formatString, the
argument referenced by the format item must implement the IFormattable
interface.
If we directly format the value using the IFormattable we will have the same result:
String garbageFormatted = (10 as IFormattable).ToString("garbage in place of int",
CultureInfo.CurrentCulture.NumberFormat);
Console.WriteLine(garbageFormatted); // Writes the "garbage in place of int"
So it seems that it is something close to the "garbage in, garbage out" problem in the implementation of the IFormattable interface on Int32 type(and possibly on other types as well). The String class does not implement IFormattable, so any format specifier is left unused and .ToString(IFormatProvider) is called instead.
Also:
Ildasm shows that Int32.ToString(String, INumberFormat) internally calls
string System.Number::FormatInt32(int32,
string,
class System.Globalization.NumberFormatInfo)
But it is the internalcall method (extern implemented somewhere in native code), so Ildasm is of no use if we want to determine the source of the problem.
EDIT - CULPRIT:
After reading the How to see code of method which marked as MethodImplOptions.InternalCall? I've used the source code from Shared Source Common Language Infrastructure 2.0 Release (it is .NET 2.0 but nonetheless) in attempt to find a culprit.
Code for the Number.FormatInt32 is located in the ...\sscli20\clr\src\vm\comnumber.cpp file.
The culprit could be deduced from the default section of the format switch statement of the FCIMPL3(Object*, COMNumber::FormatInt32, INT32 value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE):
default:
NUMBER number;
Int32ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
The fmt var is 0, so the NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt); is being called.
It leads us to nothing else than to the second switch statement default section in the NumberToStringFormat method, that is located in the loop that enumerates every format string character. It is very simple:
default:
*dst++ = ch;
It just plain copies every character from the format string into the output array, that's how the format string ends repeated in the output.
From one point of view it allows to really use garbage format strings that will output nothing useful, but from other point of view it will allow you to use something like:
String garbageFormatted = (1234 as IFormattable).ToString("0 thousands and ### in thousand",
CultureInfo.CurrentCulture.NumberFormat);
Console.WriteLine(garbageFormatted);
// Writes the "1 thousands and 234 in thousand"
that can be handy in some situations.
Interesting behavior indeed BUT NOT unaccounted for.
Your last example works when
if String.Format("My number is {0:n}!", 10)
but revert to the observed beahvior when
if String.Format("My number is {0:nu}!", 10)`.
This prompts to search about the Standard Numeric Format Specifier article on MSDN where you can read
Standard numeric format strings are used to format common numeric
types. A standard numeric format string takes the form Axx, where:
A is a single alphabetic character called the format specifier. Any
numeric format string that contains more than one alphabetic
character, including white space, is interpreted as a custom numeric
format string. For more information, see Custom Numeric Format
Strings.
The same article explains: if you have a SINGLE letter that is not recognized you get an exception.
Indeed
if String.Format("My number is {0:K}!", 10)`.
throws the FormatException as explained.
Now looking in the Custom Numeric Format Strings chapter you will find a table of eligible letters and their possible mixings, but at the end of the table you could read
Other
All other characters
The character is copied to the result string unchanged.
So I think that you have created a format string that cannot in any way print that number because there is no valid format specifier where the number 10 should be 'formatted'.
No it's not acceptable to place anything you like after the colon. Putting anything other than a recognized format specifier is likely to result in either an exception or unpredictable behaviour as you've demonstrated. I don't think you can expect string.Format to behave consistently when you're passing it arguments which are completely inconsistent with the documented formatting types

custom string format puzzler

We have a requirement to display bank routing/account data that is masked with asterisks, except for the last 4 numbers. It seemed simple enough until I found this in unit testing:
string.Format("{0:****1234}",61101234)
is properly displayed as: "****1234"
but
string.Format("{0:****0052}",16000052)
is incorrectly displayed (due to the zeros??): "****1600005252""
If you use the following in C# it works correctly, but I am unable to use this because DevExpress automatically wraps it with "{0: ... }" when you set the displayformat without the curly brackets:
string.Format("****0052",16000052)
Can anyone think of a way to get this format to work properly inside curly brackets (with the full 8 digit number passed in)?
UPDATE: The string.format above is only a way of testing the problem I am trying to solve. It is not the finished code. I have to pass to DevExpress a string format inside braces in order for the routing number to be formatted correctly.
It's a shame that you haven't included the code which is building the format string. It's very odd to have the format string depend on the data in the way that it looks like you have.
I would not try to do this in a format string; instead, I'd write a method to convert the credit card number into an "obscured" string form, quite possibly just using Substring and string concatenation. For example:
public static string ObscureFirstFourCharacters(string input)
{
// TODO: Argument validation
return "****" + input.Substring(4);
}
(It's not clear what the data type of your credit card number is. If it's a numeric type and you need to convert it to a string first, you need to be careful to end up with a fixed-size string, left-padded with zeroes.)
I think you are looking for something like this:
string.Format("{0:****0000}", 16000052);
But I have not seen that with the * inline like that. Without knowing better I probably would have done:
string.Format("{0}{1}", "****", str.Substring(str.Length-4, 4);
Or even dropping the format call if I knew the length.
These approaches are worthwhile to look through: Mask out part first 12 characters of string with *?
As you are alluding to in the comments, this should also work:
string.Format("{0:****####}", 16000052);
The difference is using the 0's will display a zero if no digit is present, # will not. Should be moot in your situation.
If for some reason you want to print the literal zeros, use this:
string.Format("{0:****\0\052}", 16000052);
But note that this is not doing anything with your input at all.

How can I format arguments in a string.format operation

This isn't a serious problem, I was just curious.
I am formatting a string, the data in the output string reuses argument data several times, but changes the case (for example).
string data = "TEST";
string s = string.Format("{0} - {1}", data, data.ToLower());
// REQUIRED OUTPUT
// TEST - test
But can I achieve this somehow...
// ****PSEUDO-code****
//string s = string.Format("{0} - {0}.ToLower()", data);
There are many specifiers that allow you to format the data that replaces the formatting token (this article is a good place to learn about those).
Unfortunately there is no specifier that lets you do a ToLower on the string - you will have to do that yourself before you pass it to String.Format.
What you want to do is not possible.
When you pass in a string argument to string.Format it is left unchanged (barring alignment/width). There is no way to specify composite formatting that will manipulate a passed in string to change its case.

How to include Variables in Localized Strings?

I'm trying to display a message to the user along the lines of:
"User 5 could not be added"
But how can I add variables to a string that is being placed in a .resx file? I've trying searching for things like "Variables in Localization" "Globalization with Variables" etc, but came up dry.
If I weren't localizing I would write:
Console.Write("User " + userNum + " could not be added");
How can this be accomplished with resources?
You can't do this directly.
What you can do is place a token - a specific string that can be replaced with string.Replace with the value of the variable.
A good candidate for this would be the built in string formatting:
Console.Write(string.Format("User {0} could not be added", userNum));
Assuming userNum has the value 5, the result would be:
User 5 could not be added
You can localize this string with the composite format specifiers.
In teams where I've done internationalization, we generally also created a resource for the format string, something like USER_COULD_NOT_BE_ADDED_FORMAT, and called String.Format (or your environment's equivalent) by passing that resource's value as the format pattern.
Then you'll do Console.Write(String.Format(resourceManager.GetString("USER_COULD_NOT_BE_ADDED_FORMAT"), userNum));
Most localizers either have training in the format strings used by the system they are localizing, or they are provided with guidance in the localization kit that you provide them. So this is not, for example, as high a barrier as making them modify code directly.
You generally need to add a loc comment to the resource ID to explain the positional parameters.
Use Composite Formatting like so:
Console.Write("User {0} could not be added", userNum);
This way you would localize "User {0} could not be added".
you can do that its simple
new lets see how
String.Format(Resource_en.PhoneNumberForEmployeeAlreadyExist,letterForm.EmployeeName[i])
this will gave me dynamic message every time
by the way I'm useing ResXManager
I would use string.Format
http://msdn.microsoft.com/en-us/library/system.string.format.aspx
Console.Write(string.Format("User {0} could not be added", userNum));

Categories