I am reading the The Pragmatic programmer and doing the following exercise in .net world (Chapter 3 - Code Generators)
The Exercise
"Write a code generator that takes the input in Listing 1, and generates output in two languages of your choice. Try to make it easy to add new languages."
Listing 1
# Add a product
# to the 'on-order' list
M AddProduct
F id int
F name char[30]
F order_code int
E
How would you implement the solution in T4 or anything else in .net world (CodeDOM is too complex) so that we can generate the code in c# and in one other .net language (visual basic or IronRuby etc.)
I know you say CodeDOM is too complex, but I would suggest using CodeDOM =). Here's a short example to start with: http://asp.dotnetheaven.com/howto/doc/listbuilder.aspx. For your example, you probably want to add a CodeMemberMethod to CodeTypeDeclaration's members - MSDN has some examples.
T4 could work, but it I don't think it's really ideal for this situation.
I don't think this is intended to be an exercise in working with existing code generators. Actually there's much more to it. The goal, I believe, is to build your own code generator, domain-specific language and deal with concepts like parsing and extensibility/pluggability. Perhaps I am reading too much into the exercise, but perhaps it's more about developing core developer skills/knowledge than educating oneself on existing tools.
Taking Ben Griswold advice, I think it is a good idea to implement it myself. And while just a little into implementing code generator in C#, I realized few things -
1. Need text manipulation language like Python etc.
2. Need to learn Regular expressions
I do intend to implement it in Ruby but for now, I implemented it in C# as -
static void Main(string[] args)
{
CodeGenerator gen = new CodeGenerator();
gen.ReadFile("Input.txt");
}
public class CodeGenerator
{
public void ReadFile(string filename)
{
StreamReader fs = new StreamReader(filename);
string line;
CSharpCode CG = new CSharpCode();
while ((line = fs.ReadLine()) != null)
{
line = line.TrimEnd('\n');
if (Regex.IsMatch(line, #"^\s*S"))
CG.BlankLine();
else if (Regex.IsMatch(line, #"^\#(.*)")) // match comments
CG.Comment(line.TrimStart('#'));
else if (Regex.IsMatch(line, #"^M\s*(.+)")) // start msg
CG.StartMsg(line.Split(' ')[1]);
else if (Regex.IsMatch(line, #"^E")) // end msg
CG.EndMsg();
else if (Regex.IsMatch(line, #"^F\s*(\w+)")) // simple type
CG.SimpleType(Regex.Split(line, #"^F\s*(\w+)")[1], Regex.Split(line, #"^F\s*(\w+)")[2]);
else
Console.WriteLine("Invalid line " + line);
}
}
}
// Code Generator for C#
public class CSharpCode
{
public void BlankLine() { Console.WriteLine(); }
public void Comment(string comment) { Console.WriteLine("//" + comment); }
public void StartMsg(string name) { Console.WriteLine("public struct " + name + "{"); }
public void EndMsg() { Console.WriteLine("}"); }
public void SimpleType(string name, string type)
{
if(type.Contains("char["))
type = "string";
Console.WriteLine(string.Format("\t{0} {1};", type.Trim(), name));
}
}
Related
So I want to build this little code sandbox in Unity, which would allow me to teach students the basics of algorithmics and coding.
The idea would be for them to enter (very basic) code in a text box or something of the kind, and to observe the effects of their code onto objects present in a Unity scene. I'm pretty sure this has been done a million times, but I'd love to try my hand at this. The rub is, I have no idea where to start...
I guess the idea is that the string would be compiled into code & executed at runtime, at the press of a button.
I've read about numerous other questions on SO, and have come up with very diverse solutions such as using a C# parser, reflection, expression trees, CodeDom, etc.
From what I understood of all these (i.e., not much), CodeDom seemed more appropriate, but then I read that it only ran inside of Visual Studio and generated errors in public builds. So does that mean that this is going to be a problem within Unity3D (as it is based on Mono?)
Thank you for your help,
In the following case, you look for an existing method of the given name on the same script (you can easily convert it to another script or any script in the assembly (not recommended though)):
string actionStr = inputField.text;
Type t = this.GetType();
MethodInfo mi = t.GetMethod(actionStr);
if(mi == null)
{
ErrorMethod(actionStr + " method could not be found");
}else
{
mi.Invoke(this);
}
Another way would be to store all the methods in a dictionary (faster):
Dictionary<string, Action>dict = null;
void Start()
{
this.dict = new Dictionary<string, Action>();
this.dict.Add("dosomething", DoSomething);
}
void DoSomething(){}
public void OnActionCall(string inputFieldStr)
{
string str = inputFieldStr.ToLower();
if(this.dict.Contains(str) == false)
{
ErrorMethod(actionStr + " method could not be found");
return;
}
this.dict[str]();
}
This may be a noob question, but I need some help. I have written two simple methods in C#: ReadCsv_IT and GetTranslation. The ReadCsv_IT method reads from a csv file. The GetTransaltion method calls the ReadCsv_IT method and returns the translated input (string key).
My problem is that in the future I will have to request a lot of times GetTranslation, but I obviously don't want to read the .csv files every time. So I was thinking about ways to use cache Memory to optimize my program, so that I don't have to read the .csv file on every request. But I am not sure how to do it and what I could do to optimize my program. Can anyone please help ?
public string ReadCsv_IT(string key)
{
string newKey = "";
using (var streamReader = new StreamReader(#"MyResource.csv"))
{
CsvReader csv = new CsvReader(streamReader);
csv.Configuration.Delimiter = ";";
List<DataRecord> rec = csv.GetRecords<DataRecord>().ToList();
DataRecord record = rec.FirstOrDefault(a => a.ORIGTITLE == key);
if (record != null)
{
//DOES THE LOCALIZATION with the help of the .csv file.
}
}
return newKey;
}
Here is the GetTranslation Method:
public string GetTranslation(string key, string culture = null)
{
string result = "";
if (culture == null)
{
culture = Thread.CurrentThread.CurrentCulture.Name;
}
if (culture == "it-IT")
{
result = ReadCsv_IT(key);
}
return result;
}
Here is also the class DataRecord.
class DataRecord
{
public string ORIGTITLE { get; set; }
public string REPLACETITLE { get; set; }
public string ORIGTOOLTIP { get; set; }
}
}
Two options IMO:
Turn your stream into an object?
In other words:
Make a class stream so you can refer to that object of the class stream.
Second:
Initialize your stream in the scope that calls for GetTranslation, and pass it on as an attribute to GetTranslation and ReadCSV_IT.
Brecht C and Thom Hubers have already given you good advice. I would like to add one more point, though: using csv files for localization in .NET is not really a good idea. Microsoft recommends using a resource-based approach (this article is a good starting point). It seems to me that you are trying to write code for something that is already built into .NET.
From a translation point of view csv files are not the best possible format either. First of all, they are not really standardized: many tools have slightly different ways to handle commas, quotes, and line breaks that are part of the translated text. Besides, translators will be tempted to open them in Excel, and -unless handled with caution- Excel will write out translations in whatever encoding it deems best.
If the project you are working on is for learning please feel free to go ahead with it, but if you are developing software that will be used by customers, updated, translated into several target languages, and redeployed, I would recommend to reconsider your internationalization approach.
#Brecht C is right, use that answer to start. When a variable has to be cached to be used by multiple threads or instances: take a look at InMemoryCache or Redis when perfomance and distribution over several clients gets an issue.
I've heard that many people had problems using the language localization features, plus you can only translate the interface (I can't translate messages from a messagebox as far as I know).
So I just wanted to know what do you think about my approach to translate my winform app.
namespace Tris
{
class Translations
{
public string draws;
public string language;
public string help;
public string newgame;
public bool lang_en;
public bool lang_it;
public bool lang_ru;
namespace Tris
{
class TranslationStrings
{
public void engTranslation(Translations TS)
{
TS.draws = "Draws";
TS.language = "Language";
TS.help = "Help";
TS.newgame = "New Game";
TS.lang_en = true;
TS.lang_it = false;
TS.lang_ru = false;
public void rusTranslation(Translations TS)
{
TS.draws = "Ничья";
TS.language = "Язык";
TS.help = "Помощь";
TS.newgame = "Новая Игра";
TS.lang_en = false;
TS.lang_it = false;
TS.lang_ru = true;
When I need something I just call the right method:
case 2:
pic = Properties.Resources.end3;
if (lang_en)
{
strings.winTranslationsEng(TS);
phrase = loser + TS.win3;
End1 end3 = new End1(lang_en, lang_it, lang_ru, winner, loser, phrase, pic);
end3.ShowDialog();
}
if (lang_it)
{
strings.winTranslationsIta(TS);
phrase = loser + TS.win3;
End1 end3 = new End1(lang_en, lang_it, lang_ru, winner, loser, phrase, pic);
end3.ShowDialog();
}
if (lang_ru)
{
strings.winTranslationsRus(TS);
phrase = winner + TS.win1;
End1 end1 = new End1(lang_en, lang_it, lang_ru, winner, loser, phrase, pic);
end1.ShowDialog();
}
break;
Before all the code was like:
if(lang_en)
{
phrase="hi";
}
if(lang_it)
{
phrase="ciao";
}
etc
Now it's way better than before, but I'm not still satisfied.
This way of managing translations makes me use a boolean, so I have to add a new "if else" on which translation method I want to pick for every new language I add.
Do you have any suggestions about how to implement a better translation system?
Do you think mine is legit or inappropriate?
edit:
The application should be in English, then I can press a button to use either the Italian or the Russian language
Your implementation of localization will work, but it will involve heavy maintenance, lots and lots of if statements and your entire codebase will need to be revisited when you want to add another language option to your application. You can imagine the headache that that will make for yourself or a future maintainer of the application.
Instead of rolling a self made, very heavy localization implementation, I would recommend that you familiarize yourself with how to do localization and globalization within C# on an application scale, utilizing resource files. Full information obtainable here
This method will even allow for localization of message dialogue texts, menu text, form headers and most importantly numerical representations of values
(Eg. US = $23 432.22 vs DE = $23.432,22 note the difference in thousand and decimal seperators)
Here is also a very good explanation from a similar question on SO:
How to use localization in C#
I'm developing a little C# application for the fun. I love this language but something disturb me ...
Is there any way to do a #define (C mode) or a symbol (ruby mode).
The ruby symbol is quite useful. It's just some name preceded by a ":" (example ":guy") every symbol is unique and can be use any where in the code.
In my case I'd like to send a flag (connect or disconnect) to a function.
What is the most elegant C# way to do that ?
Here is what i'd like to do :
BgWorker.RunWorkersAsync(:connect)
//...
private void BgWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (e.Arguement == :connect)
//Do the job
}
At this point the my favorite answer is the enum solution ;)
In your case, sending a flag can be done by using an enum...
public enum Message
{
Connect,
Disconnect
}
public void Action(Message msg)
{
switch(msg)
{
case Message.Connect:
//do connect here
break;
case Message.Disconnect:
//disconnect
break;
default:
//Fail!
break;
}
}
You could use a string constant:
public const string Guy = "guy";
In fact strings in .NET are special. If you declare two string variable with the same value they actually point to the same object:
string a = "guy";
string b = "guy";
Console.WriteLine(object.ReferenceEquals(a, b)); // prints True
C# doesn't support C-style macros, although it does still have #define. For their reasoning on this take a look at the csharp FAQ blog on msdn.
If your flag is for conditional compilation purposes, then you can still do this:
#define MY_FLAG
#if MY_FLAG
//do something
#endif
But if not, then what you're describing is a configuration option and should perhaps be stored in a class variable or config file instead of a macro.
Similar to #Darin but I often create a Defs class in my project to put all such constants so there is an easy way to access them from anywhere.
class Program
{
static void Main(string[] args)
{
string s = Defs.pi;
}
}
class Defs
{
public const int Val = 5;
public const string pi = "3.1459";
}
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
What's the best way to import a CSV file into a strongly-typed data structure?
Microsoft's TextFieldParser is stable and follows RFC 4180 for CSV files. Don't be put off by the Microsoft.VisualBasic namespace; it's a standard component in the .NET Framework, just add a reference to the global Microsoft.VisualBasic assembly.
If you're compiling for Windows (as opposed to Mono) and don't anticipate having to parse "broken" (non-RFC-compliant) CSV files, then this would be the obvious choice, as it's free, unrestricted, stable, and actively supported, most of which cannot be said for FileHelpers.
See also: How to: Read From Comma-Delimited Text Files in Visual Basic for a VB code example.
Use an OleDB connection.
String sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\InputDirectory\\;Extended Properties='text;HDR=Yes;FMT=Delimited'";
OleDbConnection objConn = new OleDbConnection(sConnectionString);
objConn.Open();
DataTable dt = new DataTable();
OleDbCommand objCmdSelect = new OleDbCommand("SELECT * FROM file.csv", objConn);
OleDbDataAdapter objAdapter1 = new OleDbDataAdapter();
objAdapter1.SelectCommand = objCmdSelect;
objAdapter1.Fill(dt);
objConn.Close();
If you're expecting fairly complex scenarios for CSV parsing, don't even think up of rolling our own parser. There are a lot of excellent tools out there, like FileHelpers, or even ones from CodeProject.
The point is this is a fairly common problem and you could bet that a lot of software developers have already thought about and solved this problem.
I agree with #NotMyself. FileHelpers is well tested and handles all kinds of edge cases that you'll eventually have to deal with if you do it yourself. Take a look at what FileHelpers does and only write your own if you're absolutely sure that either (1) you will never need to handle the edge cases FileHelpers does, or (2) you love writing this kind of stuff and are going to be overjoyed when you have to parse stuff like this:
1,"Bill","Smith","Supervisor", "No Comment"
2 , 'Drake,' , 'O'Malley',"Janitor,
Oops, I'm not quoted and I'm on a new line!
Brian gives a nice solution for converting it to a strongly typed collection.
Most of the CSV parsing methods given don't take into account escaping fields or some of the other subtleties of CSV files (like trimming fields). Here is the code I personally use. It's a bit rough around the edges and has pretty much no error reporting.
public static IList<IList<string>> Parse(string content)
{
IList<IList<string>> records = new List<IList<string>>();
StringReader stringReader = new StringReader(content);
bool inQoutedString = false;
IList<string> record = new List<string>();
StringBuilder fieldBuilder = new StringBuilder();
while (stringReader.Peek() != -1)
{
char readChar = (char)stringReader.Read();
if (readChar == '\n' || (readChar == '\r' && stringReader.Peek() == '\n'))
{
// If it's a \r\n combo consume the \n part and throw it away.
if (readChar == '\r')
{
stringReader.Read();
}
if (inQoutedString)
{
if (readChar == '\r')
{
fieldBuilder.Append('\r');
}
fieldBuilder.Append('\n');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
records.Add(record);
record = new List<string>();
inQoutedString = false;
}
}
else if (fieldBuilder.Length == 0 && !inQoutedString)
{
if (char.IsWhiteSpace(readChar))
{
// Ignore leading whitespace
}
else if (readChar == '"')
{
inQoutedString = true;
}
else if (readChar == ',')
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
else
{
fieldBuilder.Append(readChar);
}
}
else if (readChar == ',')
{
if (inQoutedString)
{
fieldBuilder.Append(',');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
}
else if (readChar == '"')
{
if (inQoutedString)
{
if (stringReader.Peek() == '"')
{
stringReader.Read();
fieldBuilder.Append('"');
}
else
{
inQoutedString = false;
}
}
else
{
fieldBuilder.Append(readChar);
}
}
else
{
fieldBuilder.Append(readChar);
}
}
record.Add(fieldBuilder.ToString().TrimEnd());
records.Add(record);
return records;
}
Note that this doesn't handle the edge case of fields not being deliminated by double quotes, but meerley having a quoted string inside of it. See this post for a bit of a better expanation as well as some links to some proper libraries.
I was bored so i modified some stuff i wrote. It try's to encapsulate the parsing in an OO manner whle cutting down on the amount of iterations through the file, it only iterates once at the top foreach.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// usage:
// note this wont run as getting streams is not Implemented
// but will get you started
CSVFileParser fileParser = new CSVFileParser();
// TO Do: configure fileparser
PersonParser personParser = new PersonParser(fileParser);
List<Person> persons = new List<Person>();
// if the file is large and there is a good way to limit
// without having to reparse the whole file you can use a
// linq query if you desire
foreach (Person person in personParser.GetPersons())
{
persons.Add(person);
}
// now we have a list of Person objects
}
}
public abstract class CSVParser
{
protected String[] deliniators = { "," };
protected internal IEnumerable<String[]> GetRecords()
{
Stream stream = GetStream();
StreamReader reader = new StreamReader(stream);
String[] aRecord;
while (!reader.EndOfStream)
{
aRecord = reader.ReadLine().Split(deliniators,
StringSplitOptions.None);
yield return aRecord;
}
}
protected abstract Stream GetStream();
}
public class CSVFileParser : CSVParser
{
// to do: add logic to get a stream from a file
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class CSVWebParser : CSVParser
{
// to do: add logic to get a stream from a web request
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class Person
{
public String Name { get; set; }
public String Address { get; set; }
public DateTime DOB { get; set; }
}
public class PersonParser
{
public PersonParser(CSVParser parser)
{
this.Parser = parser;
}
public CSVParser Parser { get; set; }
public IEnumerable<Person> GetPersons()
{
foreach (String[] record in this.Parser.GetRecords())
{
yield return new Person()
{
Name = record[0],
Address = record[1],
DOB = DateTime.Parse(record[2]),
};
}
}
}
}
There are two articles on CodeProject that provide code for a solution, one that uses StreamReader and one that imports CSV data using the Microsoft Text Driver.
A good simple way to do it is to open the file, and read each line into an array, linked list, data-structure-of-your-choice. Be careful about handling the first line though.
This may be over your head, but there seems to be a direct way to access them as well using a connection string.
Why not try using Python instead of C# or VB? It has a nice CSV module to import that does all the heavy lifting for you.
I had to use a CSV parser in .NET for a project this summer and settled on the Microsoft Jet Text Driver. You specify a folder using a connection string, then query a file using a SQL Select statement. You can specify strong types using a schema.ini file. I didn't do this at first, but then I was getting bad results where the type of the data wasn't immediately apparent, such as IP numbers or an entry like "XYQ 3.9 SP1".
One limitation I ran into is that it cannot handle column names above 64 characters; it truncates. This shouldn't be a problem, except I was dealing with very poorly designed input data. It returns an ADO.NET DataSet.
This was the best solution I found. I would be wary of rolling my own CSV parser, since I would probably miss some of the end cases, and I didn't find any other free CSV parsing packages for .NET out there.
EDIT: Also, there can only be one schema.ini file per directory, so I dynamically appended to it to strongly type the needed columns. It will only strongly-type the columns specified, and infer for any unspecified field. I really appreciated this, as I was dealing with importing a fluid 70+ column CSV and didn't want to specify each column, only the misbehaving ones.
I typed in some code. The result in the datagridviewer looked good. It parses a single line of text to an arraylist of objects.
enum quotestatus
{
none,
firstquote,
secondquote
}
public static System.Collections.ArrayList Parse(string line,string delimiter)
{
System.Collections.ArrayList ar = new System.Collections.ArrayList();
StringBuilder field = new StringBuilder();
quotestatus status = quotestatus.none;
foreach (char ch in line.ToCharArray())
{
string chOmsch = "char";
if (ch == Convert.ToChar(delimiter))
{
if (status== quotestatus.firstquote)
{
chOmsch = "char";
}
else
{
chOmsch = "delimiter";
}
}
if (ch == Convert.ToChar(34))
{
chOmsch = "quotes";
if (status == quotestatus.firstquote)
{
status = quotestatus.secondquote;
}
if (status == quotestatus.none )
{
status = quotestatus.firstquote;
}
}
switch (chOmsch)
{
case "char":
field.Append(ch);
break;
case "delimiter":
ar.Add(field.ToString());
field.Clear();
break;
case "quotes":
if (status==quotestatus.firstquote)
{
field.Clear();
}
if (status== quotestatus.secondquote)
{
status =quotestatus.none;
}
break;
}
}
if (field.Length != 0)
{
ar.Add(field.ToString());
}
return ar;
}
If you can guarantee that there are no commas in the data, then the simplest way would probably be to use String.split.
For example:
String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);
There may be libraries you could use to help, but that's probably as simple as you can get. Just make sure you can't have commas in the data, otherwise you will need to parse it better.