I have an XML File that I want to allow the end user to set the format of a string.
ex:
<Viewdata>
<Format>{0} - {1}</Format>
<Parm>Name(property of obj being formatted)</Parm>
<Parm>Phone</Parm>
</Viewdata>
So at runtime I would somehow convert that to a String.Format("{0} - {1}", usr.Name, usr.Phone);
Is this even possible?
Of course. Format strings are just that, strings.
string fmt = "{0} - {1}"; // get this from your XML somehow
string name = "Chris";
string phone = "1234567";
string name_with_phone = String.Format(fmt, name, phone);
Just be careful with it, because your end user might be able to disrupt the program. Do not forget to FormatException.
I agree with the other posters who say you probably shouldn't be doing this but that doesn't mean we can't have fun with this interesting question. So first of all, this solution is half-baked/rough but it's a good start if someone wanted to build it out.
I wrote it in LinqPad which I love so Dump() can be replaced with console writelines.
void Main()
{
XElement root = XElement.Parse(
#"<Viewdata>
<Format>{0} | {1}</Format>
<Parm>Name</Parm>
<Parm>Phone</Parm>
</Viewdata>");
var formatter = root.Descendants("Format").FirstOrDefault().Value;
var parms = root.Descendants("Parm").Select(x => x.Value).ToArray();
Person person = new Person { Name = "Jack", Phone = "(123)456-7890" };
string formatted = MagicFormatter<Person>(person, formatter, parms);
formatted.Dump();
/// OUTPUT ///
/// Jack | (123)456-7890
}
public string MagicFormatter<T>(T theobj, string formatter, params string[] propertyNames)
{
for (var index = 0; index < propertyNames.Length; index++)
{
PropertyInfo property = typeof(T).GetProperty(propertyNames[index]);
propertyNames[index] = (string)property.GetValue(theobj);
}
return string.Format(formatter, propertyNames);
}
public class Person
{
public string Name { get; set; }
public string Phone { get; set; }
}
XElement root = XElement.Parse (
#"<Viewdata>
<Format>{0} - {1}</Format>
<Parm>damith</Parm>
<Parm>071444444</Parm>
</Viewdata>");
var format =root.Descendants("Format").FirstOrDefault().Value;
var result = string.Format(format, root.Descendants("Parm")
.Select(x=>x.Value).ToArray());
What about specify your format string with parameter names:
<Viewdata>
<Format>{Name} - {Phone}</Format>
</Viewdata>
Then with something like this:
http://www.codeproject.com/Articles/622309/Extended-string-Format
you can do the work.
Short answer is yes but it depends on the variety of your formatting options how difficult it is going to be.
If you have some formatting strings that accept 5 parameter and some other that accept only 3 that you need to take that into account.
I’d go with parsing XML for params and storing these into array of objects to pass to String.Format function.
You can use System.Linq.Dynamic and make entire format command editable:
class Person
{
public string Name;
public string Phone;
public Person(string n, string p)
{
Name = n;
Phone = p;
}
}
static void TestDynamicLinq()
{
foreach (var x in new Person[] { new Person("Joe", "123") }.AsQueryable().Select("string.Format(\"{0} - {1}\", it.Name, it.Phone)"))
Console.WriteLine(x);
}
Related
I downloaded this package https://github.com/commandlineparser/commandline
and I wanted to perform parsing for strings like
string str = "file:xxxx\\xxxx\\xxxxx.sh val:-a nsdd m";
so
file = xxxx\\xxxx\\xxxxx.sh
val = -a nsdd m
I wanted to know if anyone had a library in mind or has used the specified library to obtain the parameters specified in the string.
I am having a hard time understanding the example on how to parse that string and obtain the file parameter and val parameter. I know i could do string manipulation but I rather use an existing tested durable solution for this.
I've used this library and it's a solid choice.
Here's a very basic sample using some of what you posted, see code comments for clarification.
class Program
{
static void Main(string[] args)
{
// args a space separated array so you should use an array for your test
// args are identified with the `-` so you should set args like `-f somefilenamehere`
// args specified are -f and -v
string[] arguments = new[] {"-f file:xxxx\\xxxx\\xxxxx.sh", "-v nsdd" };
string file = string.Empty;
string value = string.Empty;
// you would pull your args off the options, if they are successfully parsed
// and map them to your applications properties/settings
Parser.Default.ParseArguments<Options>(arguments)
.WithParsed<Options>(o =>
{
file = o.InputFile; // map InputFile arg to file property
value = o.Value; // map Value arg to value property
});
Console.WriteLine($"file = {file}");
Console.WriteLine($"value = {value}");
Console.ReadLine();
// output:
// file = file:xxxx\xxxx\xxxxx.sh
// value = nsdd
}
}
// the options class is used to define your arg tokens and map them to the Options property
class Options
{
[Option('f', "file", Required = true, HelpText = "Input files to be processed.")]
public string InputFile { get; set; }
[Option('v', "value", Required = true, HelpText = "Value to be used")]
public string Value { get; set; }
}
I'm trying to get some field value from a text file using a streamReader.
To read my custom value, I'm using split() method. My separator is a colon ':' and my text format looks like:
Title: Mytitle
Manager: Him
Thema: Free
.....
Main Idea: best idea ever
.....
My problem is, when I try to get the first field, which is title, I use:
string title= text.Split(:)[1];
I get title = MyTitle Manager
instead of just: title= MyTitle.
Any suggestions would be nice.
My text looks like this:
My mail : ........................text............
Manager mail : ..................text.............
Entity :.......................text................
Project Title :...............text.................
Principal idea :...................................
Scope of the idea : .........text...................
........................text...........................
Description and detail :................text.......
..................text.....
Cost estimation :..........
........................text...........................
........................text...........................
........................text...........................
Advantage for us :.................................
.......................................................
Direct Manager IM :................................
Updated per your post
//I would create a class to use if you haven't
//Just cleaner and easier to read
public class Entry
{
public string MyMail { get; set; }
public string ManagerMail { get; set; }
public string Entity { get; set; }
public string ProjectTitle { get; set; }
// ......etc
}
//in case your format location ever changes only change the index value here
public enum EntryLocation
{
MyMail = 0,
ManagerMail = 1,
Entity = 2,
ProjectTitle = 3
}
//return the entry
private Entry ReadEntry()
{
string s =
string.Format("My mail: test#test.com{0}Manager mail: test2#test2.com{0}Entity: test entity{0}Project Title: test project title", Environment.NewLine);
//in case you change your delimiter only need to change it once here
char delimiter = ':';
//your entry contains newline so lets split on that first
string[] split = s.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
//populate the entry
Entry entry = new Entry()
{
//use the enum makes it cleaner to read what value you are pulling
MyMail = split[(int)EntryLocation.MyMail].Split(delimiter)[1].Trim(),
ManagerMail = split[(int)EntryLocation.ManagerMail].Split(delimiter)[1].Trim(),
Entity = split[(int)EntryLocation.Entity].Split(delimiter)[1].Trim(),
ProjectTitle = split[(int)EntryLocation.ProjectTitle].Split(delimiter)[1].Trim()
};
return entry;
}
That is because split returns strings delimited by the sign you've specified. In your case:
Title
Mytitle Manager
Him
.1. You can change your data format to get the value you need, for example:
Title: Mytitle:Manager: Him
There each second element will be the value.
text.Split(:)[1] == " Mytitle";
text.Split(:)[3] == " Him";
.2. Or you can call text.Split(' ', ':') to get identical list of name-value pairs without format change.
.3. Also if your data is placed each on a new line in the file like:
Title: Mytitle
Manager: Him
And you content is streamed into single string then you can also do:
text.Split(new string[] {Environment.NewLine, ":"}, StringSplitOptions.None);
Is there any method that I can use that returns a fixed length array after spliting a string with some delimiter and fill the rest with a default string.
Eg.
string fullName = "Jhon Doe";
string[] names = fullName.SpecialSplit(some parameters); //This should always return string array of length 3 with the second elememnt set to empty if there is no middle name.
Next time specify the language you're asking about. We're no guessers.
In Java:
fullName.split(" ");
And anyway, no method will "return string array of length 3 with the second elememnt set to empty if there is no middle name". For the method, there are just two elements. You have to write that method yourself wrapping the standard split() method.
You should read over Jon Skeet's Writing the perfect question. It will be beneficial to you in the future when posting questions of StackOverflow.
There is no method in C# to do what you are asking, but you can easily write an extension method to do what I think you are asking.
here is a quick example:
public static class AbreviatorExtention
{
public static string[] GetInitials(this String str, char splitChar)
{
string[] initialArray = new string[3];
var nameArray = str.Split(new char[] { splitChar },
StringSplitOptions.RemoveEmptyEntries);
if (nameArray.Length == 2)
{
var charArrayFirstName = nameArray[0].ToCharArray();
var charArrayLastName = nameArray[1].ToCharArray();
initialArray[0] = charArrayFirstName[0].ToString().ToUpper();
initialArray[1] = string.Empty;
initialArray[2] = charArrayLastName[0].ToString().ToUpper();
}
else
{
for (int i = 0; i < nameArray.Length; i++)
{
initialArray[i] = (nameArray[i].ToCharArray())[1]
.ToString().ToUpper();
}
}
return initialArray;
}
}
class Program
{
static void Main(string[] args)
{
string FullName = "john doe";
//Extension method in use
string[] names = FullName.GetInitials(' ');
foreach (var item in names)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
Output:
J
D
I would set it up to split the string separate from the fixed array. If you still want a fixed array, then you set up the array to a size of three an populate. This is not the best method, however, as it has no meaning. Better, set up a person or user class and then populate, via rules, from the split string.
What I got:
I got a textual representation which my program converts to a much more readable format, especcially for forums, websites and so on.
Why do I need such templage engine
As there are many different forums and blogs, the syntax of each one might be different. Instead of hard-coding the different syntax I would like to generate one class for each of those syntax (preferable extandable with easy modified xml-files) to format my output with the desired syntax.
What I did imagine
For example I need something like
class xyz {
private string start_bold = "[B]";
private string end_bold = "[/B]";
public string bold(string s) {
return start_bold + s + end_bold;
}
}
How can I do that the most elegant way? Feel free to edit this question as I'm not entirely sure it's a template engine I need. Just don't got a better word for it now.
Thanks for any help.
Some additional information:
Andrew's answer was a great hint, but I don't understand how I could several different styles with this method. Currently I do it the hard way:
string s = String.Format("Output of [B]{0}[b] with number [i]{1}[/i]",
Data.Type,
Data.Number);
For this example, I want the output to be designed for a forum. In future I would like to do it like this:
Layout l = new Layout("html");
string s = String.Format("Output of {0} with number {1},
l.bold(Data.Type),
l.italic(Data.Number);
//desired output if style "html" is chosen:
"Output of <b>Name</b> with number <i>5</i>"
//desired output if style "phpbb" is chosen:
"Output of [b]Name[/b] with number [i]5[/i]"
I just don't know how this can be done in the most elegant way.
About the XML: Only the styling conventions should be derived by a xml-document, i.e. adding custom styles without using code.
I would use exension metods. Then you could call string.bold().
I think this would be the syntax:
class xyz {
private string start_bold = "[B]";
private string end_bold = "[/B]";
public static string bold(this string x) {
return start_bold + x + end_bold;
}
}
See: http://msdn.microsoft.com/en-us/library/bb383977.aspx
I'm leaving the code below as an example, but I think what you really need is something along the lines of a "token system"
Say you have a string as such:
string s = "I want {~b}this text to be bold{~~b} and {~i}this text to be italics{~~i}"
You XML document should contain these nodes (i think, my xml is kinda rusty)
<site>
<html>
<style value="{~b}">[b]</style>
<style value="{~~b}">[/b]</style>
<style value="{~i}">[i]</style>
<style value="{~~i}">[/i]</style>
</html>
<phpBBCode>
......
public class Layout {
//private string start_bold = "[B]";
//private string end_bold = "[/B]";
//private string start_italics = "[I]";
//private string end_italics = "[/I]";
private string _stringtoformat;
public string StringToFormat {set{ _stringtoformat = value;}};//syntax is wrong
private string _formattedString;
public string FormattedString {get return _formattedString;}
public Layout(string formattype, int siteid)
{
//get format type logic here
//if(formattype.ToLower() =="html")
//{ . . . do something . . . }
//call XML Doc for specific site, based upon formattype
if(!String.IsNullorEmpty(_stringtoformat))
{
//you will want to put another loop here to loop over all of the custom styles
foreach(node n in siteNode)
{
_stringtoformat.Replace(n.value, n.text);
}
}
//Sorry, can't write XML document parsing code off the top of my head
_formattedString = _stringtoformat;
}
public string bold(this string x) {
return start_bold + x + end_bold;
}
public string italics(this string x) {
return start_italics + x+ end_italics;
}
}
IMPLEMENTATION
Layout l = new Layout("html", siteidorsomeuniqeidentifier);
l.html = stringtoformat;
output = l.formattedstring;
The code can be better, but it should give you a kick in the right direction :)
EDIT 2: based upon further info.....
If you want to do this:
Layout l = new Layout("html");
string s = String.Format("Output of {0} with number {1},
l.bold(Data.Type),
l.italic(Data.Number);
and you are looking to change l.bold() and l.italic() based upon the blog engines specific mark up . . .
public class Layout {
private string start_bold = "[B]";
private string end_bold = "[/B]";
private string start_italics = "[I]";
private string end_italics = "[/I]";
public Layout(string formattype, int siteid)
{
//get format type logic here
//if(formattype.ToLower() =="html")
//{ . . . do something . . . }
//call XML Doc for specific site, based upon formattype
start_bold = Value.From.XML["bold_start"];
end_bold = Value.From.XML["bold_end"];
//Sorry, can't write XML document parsing code off the top of my head
}
public string bold(this string x) {
return start_bold + x + end_bold;
}
public string italics(this string x) {
return start_italics + x+ end_italics;
}
}
Layout l = new Layout("html", siteid);
string s = String.Format("Output of {0} with number {1},
ValueToBeBoldAsAstring.bold(),
ValueToBeItalicAsAstring.italic());
I have a string
string str ="Enter {0} patient name";
I am using string.format to format it.
String.Format(str, "Hello");
Now if i want patient also to be retrieved from some config then I need to change str to something like
"Enter {0} {1} name". So it will replace the {1} with second value. The problem is that I want instead of {1} some other format something like {pat}. But when I try to use, it throws an error. The reason I want a different format is that there are lot of files I need to change like this(which may contain {0},{1} etc). So I need a custom placeholder which can be replaced at run-time.
You might want to check out FormatWith 2.0 by James Newton-King. It allows you to use property names as formatting tokens such as this:
var user = new User()
{
Name = "Olle Wobbla",
Age = 25
};
Console.WriteLine("Your name is {Name} and your age is {Age}".FormatWith(user));
You can also use it with anonymous types.
UPDATE: There is also a similar solution by Scott Hanselman but it is implemented as a set of extension methods on Object instead of String.
UPDATE 2012: You can get Calrius Consulting's NETFx String.FormatWith Extension Method NuGet package on NuGet.org
UPDATE 2014: There is also StringFormat.NET and littlebit's StringFormat
Regex with a MatchEvaluator seems a good option:
static readonly Regex re = new Regex(#"\{([^\}]+)\}", RegexOptions.Compiled);
static void Main()
{
string input = "this {foo} is now {bar}.";
StringDictionary fields = new StringDictionary();
fields.Add("foo", "code");
fields.Add("bar", "working");
string output = re.Replace(input, delegate (Match match) {
return fields[match.Groups[1].Value];
});
Console.WriteLine(output); // "this code is now working."
}
object[] myInts = new int[] {8,9};
However you can get away with:
object[] myInts = new string[] { "8", "9" };
string bar = string.Format("{0} {1}", myInts);
I saw all the answers above, yet, couldn't get the question right :)
Is there any particular reason why the following code does not meet your requirement?
string myFirstStr = GetMyFirstStrFromSomewhere();
string mySecondStr = GetMySecondStrFromSomewhere();
string result = "Enter " + myFirstStr + " " + mySecondStr + " name";
Here's another version of this that I found here: http://www.reddit.com/r/programming/comments/bodml/beef_up_params_in_c_5_to_solve_lambda_abuse/c0nrsf1
Any solution to this is going to involve reflection, which is less than ideal, but here's his code with some of the other major performance issues resolved. (No error checking. Add it if you like.):
1) Uses direct runtime reflection, no DataBinder overhead
2) Doesn't use regular expressions, uses a single-pass parse and state.
3) Doesn't convert the string into an intermediate string and then convert it again to the final format.
4) Allocates and concatenates with a single StringBuilder instead of newing up strings all over the place and concatenating them into new strings.
5) Removes the stack overhead of calling a delegate for n replace operations.
6) In general is a single pass through that will scale in a relatively linear manner (still some cost for each prop lookup and nested prop lookup, but that's that.)
public static string FormatWith(this string format, object source)
{
StringBuilder sbResult = new StringBuilder(format.Length);
StringBuilder sbCurrentTerm = new StringBuilder();
char[] formatChars = format.ToCharArray();
bool inTerm = false;
object currentPropValue = source;
for (int i = 0; i < format.Length; i++)
{
if (formatChars[i] == '{')
inTerm = true;
else if (formatChars[i] == '}')
{
PropertyInfo pi = currentPropValue.GetType().GetProperty(sbCurrentTerm.ToString());
sbResult.Append((string)(pi.PropertyType.GetMethod("ToString", new Type[]{}).Invoke(pi.GetValue(currentPropValue, null), null)));
sbCurrentTerm.Clear();
inTerm = false;
currentPropValue = source;
}
else if (inTerm)
{
if (formatChars[i] == '.')
{
PropertyInfo pi = currentPropValue.GetType().GetProperty(sbCurrentTerm.ToString());
currentPropValue = pi.GetValue(source, null);
sbCurrentTerm.Clear();
}
else
sbCurrentTerm.Append(formatChars[i]);
}
else
sbResult.Append(formatChars[i]);
}
return sbResult.ToString();
}
You can also use the example from Marc Gravell and Extend the String class object:
public static class StringExtension
{
static readonly Regex re = new Regex(#"\{([^\}]+)\}", RegexOptions.Compiled);
public static string FormatPlaceholder(this string str, Dictionary<string, string> fields)
{
if (fields == null)
return str;
return re.Replace(str, delegate(Match match)
{
return fields[match.Groups[1].Value];
});
}
}
Example usage:
String str = "I bought a {color} car";
Dictionary<string, string> fields = new Dictionary<string, string>();
fields.Add("color", "blue");
str.FormatPlaceholder(fields));
You are probably better off using Replace for the custom field and Format for the rest, like:
string str = "Enter {0} {pat} name";
String.Format(str.Replace("{pat}", "Patient"), "Hello");
var user = new User()
{
Name = "John",
Age = 21
};
String result = $"Your name is {user.Name} and your age is {user.Age}";
I wanted something that worked more like Python's string formatting, so I wrote this:
https://gist.github.com/samwyse/b225b32ae1aea6fb27ad9c966b9ca90b
Use it like this:
Dim template = New FormatFromDictionary("{cat} vs {dog}")
Dim d = New Dictionary(Of String, Object) From {
{"cat", "Felix"}, {"dog", "Rex"}}
Console.WriteLine(template.Replace(d)) ' Felix vs Rex