How do I implement such template engine? - c#

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());

Related

Find comments in text and replace them using Regex

I currently go trought all my source files and read their text with File.ReadAllLines and i want to filter all comments with one regex. Basically all comment possiblities. I tried several regex solutions i found on the internet. As this one:
#"(#(?:""[^""]*"")+|""(?:[^""\n\\]+|\\.)*""|'(?:[^'\n\\]+|\\.)*')|//.*|/\*(?s:.*?)\*/"
And the top result when i google:
string blockComments = #"/\*(.*?)\*/";
string lineComments = #"//(.*?)\r?\n";
string strings = #"""((\\[^\n]|[^""\n])*)""";
string verbatimStrings = #"#(""[^""]*"")+";
See: Regex to strip line comments from C#
The second solution won't recognize any comments.
Thats what i currently do
public static List<string> FormatList(List<string> unformattedList, string dataType)
{
List<string> formattedList = unformattedList;
string blockComments = #"/\*(.*?)\*/";
string lineComments = #"//(.*?)\r?\n";
string strings = #"""((\\[^\n]|[^""\n])*)""";
string verbatimStrings = #"#(""[^""]*"")+";
string regexCS = blockComments + "|" + lineComments + "|" + strings + "|" + verbatimStrings;
//regexCS = #"(#(?:""[^""]*"")+|""(?:[^""\n\\]+|\\.)*""|'(?:[^'\n\\]+|\\.)*')|//.*|/\*(?s:.*?)\*/";
string regexSQL = "";
if (dataType.Equals("cs"))
{
for(int i = 0; i < formattedList.Count;i++)
{
string line = formattedList[i];
line = line.Trim(' ');
if(Regex.IsMatch(line, regexCS))
{
line = "";
}
formattedList[i] = line;
}
}
else if(dataType.Equals("sql"))
{
}
else
{
throw new Exception("Unknown DataType");
}
return formattedList;
}
The first Method recognizes the comments, but also finds things like
string[] bla = text.Split('\\\\');
Is there any solution to this problem? That the regex excludes the matches which are in a string/char? If you have any other links i should check out please let me know!
I tried a lot and can't figure out why this won't work for me.
[I also tried these links]
https://blog.ostermiller.org/find-comment
https://codereview.stackexchange.com/questions/167582/regular-expression-to-remove-comments
Regex to find comment in c# source file
Doing this with regexes will be very difficult, as stated in the comments. However, a fine way to eliminate comments would be by utilizing a CSharpSyntaxWalker. The syntaxwalker knows about all language constructs and won't make hard to investigate mistakes (as regexes do).
Add a reference to the Microsoft.CodeAnalysis.CSharp Nuget package and inherit from CSharpSyntaxWalker.
class CommentWalker : CSharpSyntaxWalker
{
public CommentWalker(SyntaxWalkerDepth depth = SyntaxWalkerDepth.Node) : base(depth)
{
}
public override void VisitTrivia(SyntaxTrivia trivia)
{
if (trivia.IsKind(SyntaxKind.MultiLineCommentTrivia)
|| trivia.IsKind(SyntaxKind.SingleLineCommentTrivia))
{
// Do something with the comments
// For example, find the comment location in the file, so you can replace it later.
// Make a List as a public property, so you can iterate the list of comments later on.
}
}
}
Then you can use it like so:
// Get the program text from your .cs file
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
var walker = new CommentWalker();
walker.Visit(root);
// Now iterate your list of comments (probably backwards) and remove them.
Further reading:
Syntax walkers
Checking for big blocks of comments in code (NDepend, Roslyn)

Superpower: match a string with parser only if it begins a line

When parsing in superpower, how to match a string only if it is the first thing in a line?
For example, I need to match the A colon in "A: Hello Goodbye\n" but not in "Goodbye A: Hello\n"
Using your example here, I would change your ActorParser and NodeParser definitions to this:
public readonly static TokenListParser<Tokens, Node> ActorParser =
from name in NameParser
from colon in Token.EqualTo(Tokens.Colon)
from text in TextParser
select new Node {
Actor = name + colon.ToStringValue(),
Text = text
};
public readonly static TokenListParser<Tokens, Node> NodeParser =
from node in ActorParser.Try()
.Or(TextParser.Select(text => new Node { Text = text }))
select node;
I feel like there is a bug with Superpower, as I'm not sure why in the NodeParser I had to put a Try() on the first parser when chaining it with an Or(), but it would throw an error if I didn't add it.
Also, your validation when checking input[1] is incorrect (probably just a copy paste issue). It should be checking against "Goodbye A: Hello" and not "Hello A: Goodbye"
Unless RegexOptions.Multiline is set, ^ matches the beginning of a string regardless of whether it is at the beginning of a line.
You can probably use inline (?m) to turn on multiline:
static TextParser<Unit> Actor { get; } =
from start in Span.Regex(#"(?m)^[A-Za-z][A-Za-z0-9_]+:")
select Unit.Value;
I have actually done something similar, but I do not use a Tokenizer.
private static string _keyPlaceholder;
private static TextParser<MyClass> Actor { get; } =
Span.Regex("^[A-Za-z][A-Za-z0-9_]*:")
.Then(x =>
{
_keyPlaceholder = x.ToStringValue();
return Character.AnyChar.Many();
}
))
.Select(value => new MyClass { Key = _keyPlaceholder, Value = new string(value) });
I have not tested this, just wrote it out by memory. The above parser should have the following:
myClass.Key = "A:"
myClass.Value = " Hello Goodbye"

Set String.Format at runtime

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);
}

Read Specific Strings from Text File

I'm trying to get certain strings out of a text file and put it in a variable.
This is what the structure of the text file looks like keep in mind this is just one line and each line looks like this and is separated by a blank line:
Date: 8/12/2013 12:00:00 AM Source Path: \\build\PM\11.0.64.1\build.11.0.64.1.FileServerOutput.zip Destination Path: C:\Users\Documents\.NET Development\testing\11.0.64.1\build.11.0.55.5.FileServerOutput.zip Folder Updated: 11.0.64.1 File Copied: build.11.0.55.5.FileServerOutput.zip
I wasn't entirely too sure of what to use for a delimiter for this text file or even if I should be using a delimiter so it could be subjected to change.
So just a quick example of what I want to happen with this, is I want to go through and grab the Destination Path and store it in a variable such as strDestPath.
Overall the code I came up with so far is this:
//find the variables from the text file
string[] lines = File.ReadAllLines(GlobalVars.strLogPath);
Yeah not much, but I thought perhaps if I just read one line at at a time and tried to search for what I was looking for through that line but honestly I'm not 100% sure if I should stick with that way or not...
If you are skeptical about how large your file is, you should come up using ReadLines which is deferred execution instead of ReadAllLines:
var lines = File.ReadLines(GlobalVars.strLogPath);
The ReadLines and ReadAllLines methods differ as follows:
When you use ReadLines, you can start enumerating the collection of strings before the whole collection is returned; when you use ReadAllLines, you must wait for the whole array of strings be returned before you can access the array. Therefore, when you are working with very large files, ReadLines can be more efficient.
As weird as it might sound, you should take a look to log parser. If you are free to set the file format you could use one that fits with log parser and, believe me, it will make your life a lot more easy.
Once you load the file with log parse you can user queries to get the information you want. If you don't care about using interop in your project you can even add a com reference and use it from any .net project.
This sample reads a HUGE csv file a makes a bulkcopy to the DB to perform there the final steps. This is not really your case, but shows you how easy is to do this with logparser
COMTSVInputContextClass logParserTsv = new COMTSVInputContextClass();
COMSQLOutputContextClass logParserSql = new COMSQLOutputContextClass();
logParserTsv.separator = ";";
logParserTsv.fixedSep = true;
logParserSql.database = _sqlDatabaseName;
logParserSql.server = _sqlServerName;
logParserSql.username = _sqlUser;
logParserSql.password = _sqlPass;
logParserSql.createTable = false;
logParserSql.ignoreIdCols = true;
// query shortened for clarity purposes
string SelectPattern = #"Select TO_STRING(UserName),TO_STRING(UserID) INTO {0} From {1}";
string query = string.Format(SelectPattern, _sqlTable, _csvPath);
logParser.ExecuteBatch(query, logParserTsv, logParserSql);
LogParser in one of those hidden gems Microsoft has and most people don't know about. I have use to read iis logs, CSV files, txt files, etc. You can even generate graphics!!!
Just check it here http://support.microsoft.com/kb/910447/en
Looks like you need to create a Tokenizer. Try something like this:
Define a list of token values:
List<string> gTkList = new List<string>() {"Date:","Source Path:" }; //...etc.
Create a Token class:
public class Token
{
private readonly string _tokenText;
private string _val;
private int _begin, _end;
public Token(string tk, int beg, int end)
{
this._tokenText = tk;
this._begin = beg;
this._end = end;
this._val = String.Empty;
}
public string TokenText
{
get{ return _tokenText; }
}
public string Value
{
get { return _val; }
set { _val = value; }
}
public int IdxBegin
{
get { return _begin; }
}
public int IdxEnd
{
get { return _end; }
}
}
Create a method to Find your Tokens:
List<Token> FindTokens(string str)
{
List<Token> retVal = new List<Token>();
if (!String.IsNullOrWhitespace(str))
{
foreach(string cd in gTkList)
{
int fIdx = str.IndexOf(cd);
if(fIdx > -1)
retVal.Add(cd,fIdx,fIdx + cd.Length);
}
}
return retVal;
}
Then just do something like this:
foreach(string ln in lines)
{
//returns ordered list of tokens
var tkns = FindTokens(ln);
for(int i=0; i < tkns.Length; i++)
{
int len = (i == tkns.Length - 1) ? ln.Length - tkns[i].IdxEnd : tkns[i+1].IdxBegin - tkns[i].IdxEnd;
tkns[i].value = ln.Substring(tkns[i].IdxEnd+1,len).Trim();
}
//Do something with the gathered values
foreach(Token tk in tkns)
{
//stuff
}
}

RegEx For Validating File Names

I have some MP3 files that are named with a particular syntax, for example:
1 - Sebastian Ingrosso - Calling (Feat. Ryan Tedder)
I have written a small program in C# that reads the Track, Artist and Title from the ID3 tags. What i would like to do is write a regex expression that can validate that the files are in fact named with the syntax listed above.
So i have a class called song:
//Properties
public string Filename
{
get { return _filename; }
set { _filename = value; }
}
public string Title
{
get { return _title; }
set { _title = value; }
}
public string Artist
{
get { return _artist; }
set { _artist = value; }
}
//Methods
public bool Parse(string strfile)
{
bool CheckFile;
Tags.ID3.ID3v1 Song = new Tags.ID3.ID3v1(strfile, true);
Filename = Song.FileName;
Title = Song.Title;
Artist = Song.Artist;
//Check File Name Formatting
string RegexBuilder = #"\d\s-\s" + Artist + #"\s-\s" + Title;
if (Regex.IsMatch(Filename, RegexBuilder))
{
CheckFile = true;
}
else
{
CheckFile = false;
}
return CheckFile;
}
So it works, MOST OF THE TIME. The minute i have a (Feat. ) in the title it fails. The closest i could come up with is:
\d\s-\s\Artist\s-\s.*
That's obviously not going to work as any text would pass the test, I have tried my very best but I have only been programming for two weeks.
tl;dr Would like song to pass a regex test whether it contains a featured artist or not, for example:
1 - Sebastian Ingrosso - Calling (Feat. Ryan Tedder)
and
1 - Flo Rida - Whistle
Should both pass the test.
The problem is that the "(" and ")" in your regex have meaning to the Regex engine. You should use the following code:
string RegexBuilder = #"\d\s-\s" + Regex.Escape(Artist) + #"\s-\s" + Regex.Escape(Title);
The Escape function will change "(Feat. )" to "\(Feat. \)", which will ensure that you are matching the parentheses and not grouping "Feat. ".
http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.escape.aspx

Categories