I'm trying to figure out how to parse a string in this format into a tree like data structure of arbitrary depth.
and after that make random sentences.
"{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}"
where
, means or
{ means expand
} means collapse up to parent
for example, i want to get output like this:
1) hello world planet.
2) hi earth globe!
3) goodby planet.
and etc.
The input string must be parsed. Since it can contain nested braces, we need a recursive parser. But to begin with, we need a data model to represent the tree structure.
We can have three different types of items in this tree: text, a list representing a sequence and a list representing a choice. Let's derive three classes from this abstract base class:
abstract public class TreeItem
{
public abstract string GetRandomSentence();
}
The TextItem class simply returns its text as "random sentence":
public class TextItem : TreeItem
{
public TextItem(string text)
{
Text = text;
}
public string Text { get; }
public override string GetRandomSentence()
{
return Text;
}
}
The sequence concatenates the text of its items:
public class SequenceItem : TreeItem
{
public SequenceItem(List<TreeItem> items)
{
Items = items;
}
public List<TreeItem> Items { get; }
public override string GetRandomSentence()
{
var sb = new StringBuilder();
foreach (var item in Items) {
sb.Append(item.GetRandomSentence());
}
return sb.ToString();
}
}
The choice item is the only one using randomness to pick one random item from the list:
public class ChoiceItem : TreeItem
{
private static readonly Random _random = new();
public ChoiceItem(List<TreeItem> items)
{
Items = items;
}
public List<TreeItem> Items { get; }
public override string GetRandomSentence()
{
int index = _random.Next(Items.Count);
return Items[index].GetRandomSentence();
}
}
Note that the sequence and choice items both call GetRandomSentence() recursively on their items to descend the tree recursively.
This was the easy part. Now lets create a parser.
public class Parser
{
enum Token { Text, LeftBrace, RightBrace, Comma, EndOfString }
int _index;
string _definition;
Token _token;
string _text; // If token is Token.Text;
public TreeItem Parse(string definition)
{
_index = 0;
_definition = definition;
GetToken();
return Choice();
}
private void GetToken()
{
if (_index >= _definition.Length) {
_token = Token.EndOfString;
return;
}
switch (_definition[_index]) {
case '{':
_index++;
_token = Token.LeftBrace;
break;
case '}':
_index++;
_token = Token.RightBrace;
break;
case ',':
_index++;
_token = Token.Comma;
break;
default:
int startIndex = _index;
do {
_index++;
} while (_index < _definition.Length & !"{},".Contains(_definition[_index]));
_text = _definition[startIndex.._index];
_token = Token.Text;
break;
}
}
private TreeItem Choice()
{
var items = new List<TreeItem>();
while (_token != Token.EndOfString && _token != Token.RightBrace) {
items.Add(Sequence());
if (_token == Token.Comma) {
GetToken();
}
}
if (items.Count == 0) {
return new TextItem("");
}
if (items.Count == 1) {
return items[0];
}
return new ChoiceItem(items);
}
private TreeItem Sequence()
{
var items = new List<TreeItem>();
while (true) {
if (_token == Token.Text) {
items.Add(new TextItem(_text));
GetToken();
} else if (_token == Token.LeftBrace) {
GetToken();
items.Add(Choice());
if (_token == Token.RightBrace) {
GetToken();
}
} else {
break;
}
}
if (items.Count == 0) {
return new TextItem("");
}
if (items.Count == 1) {
return items[0];
}
return new SequenceItem(items);
}
}
It consists of a lexer, i.e., a low level mechanism to split the input text into tokens. We have have four kinds of tokens: text, "{", "}" and ",". We represent these tokens as
enum Token { Text, LeftBrace, RightBrace, Comma, EndOfString }
We also have added a EndOfString token to tell the parser that the end of the input string was reached. When the token is Text we store this text in the field _text. The lexer is implemented by the GetToken() method which has no return value and instead sets the _token field, to make the current token available in the two parsing methods Choice() and Sequence().
One difficulty is that when we encounter an item, we do not know whether it is a single item or whether it is part of a sequence or a choice. We assume that the whole sentence definition is a choice consisting of sequences, which gives sequences precedence over choices (like "*" has precedence over "+" in math).
Both Choice and Sequence gather items in a temporary list. If this list contains only one item, then this item will be returned instead of a choice list or a sequence list.
You can test this parser like this:
const string example = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}";
var parser = new Parser();
var tree = parser.Parse(example);
for (int i = 0; i < 20; i++) {
Console.WriteLine(tree.GetRandomSentence());
}
The output might look like this:
Goodbye rock
Hi earth
Goodbye globe.
Hey world
Goodbye rock
Hi earth
Hey earth
farewell planet
Goodbye globe.
Hey world
Goodbye planet
Hello world
Hello world
Goodbye planet
Hey earth
farewell globe!
Goodbye globe.
Goodbye globe.
Goodbye planet
farewell rock
I think that can be a complicated job, for that I used this tutorial, I strongly advice you to read the entire page to understand how this works.
First, you have to pass this "tree" as an array. You can parse the string, manually set the array or whatever. That's important because there isn't a good model for that tree model so it's better if you use a already available one. Also, it's important that if you want to set a correct grammar, you'll need to add "weight" to those words and tell the code how to correctly set and in what order.
Here is the code snippet:
using System;
using System.Text;
namespace App
{
class Program
{
static void Main(string[] args)
{
string tree = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}";
string[] words = { "Hello", "Hi", "Hey", "world", "earth", "Goodbye", "farewell", "planet", "rock", "globe" };
RandomText text = new RandomText(words);
text.AddContentParagraphs(12, 1, 3, 3, 3);
string content = text.Content;
Console.WriteLine(content);
}
}
public class RandomText
{
static Random _random = new Random();
StringBuilder _builder;
string[] _words;
public RandomText(string[] words)
{
_builder = new StringBuilder();
_words = words;
}
public void AddContentParagraphs(int numberParagraphs, int minSentences,
int maxSentences, int minWords, int maxWords)
{
for (int i = 0; i < numberParagraphs; i++)
{
AddParagraph(_random.Next(minSentences, maxSentences + 1),
minWords, maxWords);
_builder.Append("\n\n");
}
}
void AddParagraph(int numberSentences, int minWords, int maxWords)
{
for (int i = 0; i < numberSentences; i++)
{
int count = _random.Next(minWords, maxWords + 1);
AddSentence(count);
}
}
void AddSentence(int numberWords)
{
StringBuilder b = new StringBuilder();
// Add n words together.
for (int i = 0; i < numberWords; i++) // Number of words
{
b.Append(_words[_random.Next(_words.Length)]).Append(" ");
}
string sentence = b.ToString().Trim() + ". ";
// Uppercase sentence
sentence = char.ToUpper(sentence[0]) + sentence.Substring(1);
// Add this sentence to the class
_builder.Append(sentence);
}
public string Content
{
get
{
return _builder.ToString();
}
}
}
}
If the question is how to parse the text. I think maybe you can use the stack to parse it.
"{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}"
Basically, you push char in the stack when you read a char is not '}'. And when you get a '}', you pop from stack many time, until you reach a '{'.
But it has more details, because you have a rule ',' for OR.
The parsing is like do the calculation by stack. This is the way how you handle parenthesis for equation.
Related
I am working on a C# application, and I would like the ability to execute code from a string, where that string contains a variable in scope outside the string. For example:
using Microsoft.CodeAnalysis.CSharp.Scripting;
///...
List<int> myNumbers = new List<int>();
//do something here to populate myNumbers
//userProvidedExpression will be a string that contains curNumber and represents a statement that would evaluate to a bool
string userProvidedExpression = "curNumber == 4";
foreach(int curNumber in myNumbers)
{
if( await CSharpScript.EvaluateAsync<bool>(userProvidedExpression) )
{
Console.WriteLine("curNumber MATCHES user-provided condition");
}
else
{
Console.WriteLine("curNumber DOES NOT MATCH user-provided condition");
}
}
Obviously the key difficulty I am having is getting the "curNumber" from userProvidedExpression to be recognized as the same curNumber from the foreach loop. Is there any straightforward way to accomplish this?
As the documentation says, you need to add a globals, like that:
public class Globals
{
public int curNumber;
}
async static void Main(string[] args)
{
List<int> myNumbers = new List<int>();
myNumbers.Add(4);
//userProvidedExpression will be a string that contains curNumber and represents a statement that would evaluate to a bool
string userProvidedExpression = "curNumber == 4";
foreach (int curNumber in myNumbers)
{
var globals = new Globals
{
curNumber = curNumber
};
if (await CSharpScript.EvaluateAsync<bool>(userProvidedExpression, globals: globals))
{
Console.WriteLine("curNumber MATCHES user-provided condition");
}
else
{
Console.WriteLine("curNumber DOES NOT MATCH user-provided condition");
}
}
Console.ReadLine();
}
I'm creating a program to generate schematics based off of user input. This has to be done dynamically/by hand due to the sheer volume of different possibilities (6.8M, growing exponentially). Right now I'm working on importing some data via CSV.
Example data:
Type,TIN_pos,TIN_ID,Desc
Elect, 0, X, Manual Regulator
Elect, 0, A, Electronic Regulator
Import code:
List<TIN_Fields> values = File.ReadAllLines("C:\\Users\\User\\Desktop\\Visual Basic\\CSV_Test_1.csv")
.Skip(1)
.Select(v => TIN_Fields.FromCsv(v))
.ToList();
public class TIN_Fields
{
public string Type;
public int TIN_pos;
public string TIN_ID;
public string Desc;
public static TIN_Fields FromCsv(string csvLine)
{
string[] values = csvLine.Split(',');
TIN_Fields _Fields = new TIN_Fields();
_Fields.Type = Convert.ToString(values[0]);
_Fields.TIN_pos = Convert.ToInt16(values[1]);
_Fields.TIN_ID = Convert.ToString(values[2]);
_Fields.Desc = Convert.ToString(values[3]);
return _Fields;
}
}
Once that data is Imported, I need to do two things with it,
display the raw csv data in a ListView table, just so users can see if anything in the list needs updating.
be able to compare the items in the list to various characters in a 10-digit hexadecimal code, and spit out some results.
First and foremost, i need to run through the list that was created with the above code, make sure that:
TIN_pos value = 0
because that is the character position of the input box.
Then, with the remaining options, look for the character represented in the input in the TIN_ID field.
Once found, it should then output the Desc field.
Everywhere I have looked says to use foreach, but that requires the array name, which is the part that is confusing me. I've tried filling in basically all of the variables in the FromCSV Method and usually get an error that the class doesn't have a definition.
to hopefully clear up confusion with my explanation, here is the code I created that does the same thing, but with the CSV data hard coded into it, using switch cases and if statements.
public partial class Form1 : Form
{
public string Model_Chassis;
public string Model_Test_Type;
public int ChannelNumberVar => Convert.ToInt32(TextBox_TIN[2]);
public string Tester_Type_Selector;
public string TextBox_TIN
{
get { return TIN_Entry_TextBox.Text; }
set { TIN_Entry_TextBox.Text = value; }
}
public string Model_Data_D
{
get { return Model_Data.Text; }
set { Model_Data.Text = value; }
}
public Form1()
{
InitializeComponent();
}
//Method grabs TIN Box data and decodes it to model information.
public void Model_Select()
{
//Picks Model Chassis
switch (char.ToUpper(TextBox_TIN[0]))
{
case 'H':
{
Model_Chassis = Coding.Model1.description;
}
break;
default:
{
Model_Data_D = "INVALID TIN";
}
break;
}
//Picks Test Type
switch (char.ToUpper(TextBox_TIN[3]))
{
case '0':
{
Model_Test_Type = Test_Types.TT_PD.TT_tt;
}
break;
case '1':
{
Model_Test_Type = Test_Types.TT_PV.TT_tt;
}
break;
default:
{
Model_Test_Type = "";
}
break;
}
//Puts chassis and Test Type together
if (Model_Data_D.Equals("INVALID TIN"))
{
;
}
else if (char.ToUpper(TextBox_TIN[2]).Equals(Coding.Num_Chan_1_2.tin_id))
{
Model_Data_D = $"{Model_Chassis}-{Model_Test_Type}";
}
else
{
Model_Data_D = $"{Model_Chassis}-{TextBox_TIN[2]}{Model_Test_Type}";
}
}
public class Coding
{
public char tin_id;
public string description;
public Coding(char TIN_ID, string Desc)
{
tin_id = TIN_ID;
description = Desc;
}
public static Coding Model1 = new Coding('H', "Model1");
public static Coding Num_Chan_1_2 = new Coding('X', "Single Channel");
public static Coding Elect_Reg_F_1 = new Coding('X', "Manual Regulator");
}
}
INPUT:
HXX0X
OUTPUT
Model1-PD
Thanks in advance for the help!
You're asking quite a few questions, and providing a lot of extra details in here, but for this:
"First and foremost, i need to run through the list that was created with the above code, make sure that:
TIN_pos value = 0
because that is the character position of the input box."
(seeing as you say you need to do this 'first and foremost').
In your FromCsv method, check the value as you create the record, and throw an error if it is invalid. Like this:
public static TIN_Fields FromCsv(string csvLine)
{
string[] values = csvLine.Split(',');
TIN_Fields _Fields = new TIN_Fields();
_Fields.Type = Convert.ToString(values[0]);
_Fields.TIN_pos = Convert.ToInt16(values[1]);
if(_Fields.TIN_pos != 0){
throw new Exception("TIN_pos must be 0");
}
_Fields.TIN_ID = Convert.ToString(values[2]);
_Fields.Desc = Convert.ToString(values[3]);
return _Fields;
}
Assuming you've read in your CSV correctly, which it seems you have, then selecting the appropriate TIN from the list is a simple LINQ statement. The following code assumes that TIN IDs are unique and only a single character in length.
static void Main(string[] args)
{
string testCsv = #"C:\Users\User\Desktop\Visual Basic\CSV_Test_1.csv";
List<TIN_Fields> values = File.ReadAllLines(testCsv)
.Skip(1)
.Select(v => TIN_Fields.FromCsv(v))
.ToList();
// Simulates input received from form
string input = "HXX0X";
TIN_Fields selectedTIN = values.First(x => x.TIN_ID == Convert.ToString(input[0]));
// Insert the description as needed in your ouput.
string output = $"{ selectedTIN.Desc }-";
}
Hopefully that answers another part of the problem. The Convert.ToString() is required because the output of input[0] is a char.
I don't really understand arrays and I need to create a variable of type 'array of songs' then initialize it to a new Array so it can store 4 references to Songs. How would I then create a loop that would run enough times to fill the array whilst calling the InputSOngDetails() method and store the return value in that method?
namespace Songs
{
class Program
{
static void Main(string[] args) {
InputSongDetails();
}
static Song InputSongDetails()
{
Console.WriteLine("What is the name of your song");
string name = Console.ReadLine();
Console.WriteLine("What is the artists name");
string artist = Console.ReadLine();
int records;
Console.WriteLine("How many records did it sell");
while (!int.TryParse(Console.ReadLine(), out records) || records < 0)
{
Console.WriteLine("That is not valid please enter a number");
}
return new Song(name, artist, records);
}
}
}
This is my Songs class if needed
namespace Songs
{
class Song
{
string name;
string artist;
int copiesSold;
public Song(string name, string artist, int copiesSold)
{
this.name = name;
this.artist = artist;
this.copiesSold = copiesSold;
}
public Song()
{
}
public string GetArtist()
{
return artist;
}
public string GetDetails()
{
return $"Name: {name} Artist: {artist} Copies Sold: {copiesSold},";
}
public string GetCertification()
{
if (copiesSold<200000)
{
return null;
}
if (copiesSold<400000)
{
return "Silver";
}
if (copiesSold<600000)
{
return "gold";
}
return "Platinum";
}
}
}
Fist, initialize your array of songs with new Song[ length ], then a simple for-loop will suffice.
static void Main(string[] args)
{
Song[] songs = new Song[4];
for(int i = 0; i < songs.Length; i++)
{
songs[i] = InputSongDetails();
}
}
Or as the commenters suggest, just use a variable-length List<Song>.
static void Main(string[] args)
{
List<Song> songs = new List<Song>();
for(int i = 0; i < 4; i++)
{
songs.Add(InputSongDetails());
}
}
Once you've mastered the basics, you can also accomplish this with a bit of Linq (though I wouldn't actually recommend it in this case):
static void Main(string[] args)
{
var songs = Enumerable.Range(0, 4)
.Select(i => InputSongDetails())
.ToList();
}
This is not really an answer as much as it is a tip for getting input from the user in a console application which might be useful to you (well, the answer is in the last code snippet, but p.s.w.g has already covered that very well).
Since an interactive console session usually ends up with a lot of Console.WriteLine("Ask the user a question"); string input = Console.ReadLine();, and, as you've already done very well, include some validation on the input in some cases, I've found it handy to write the following methods below.
Each of them take in a string, which is the prompt for the user (the question), and return a strongly-typed variable that represents their input. Validation (when needed) is all done in a loop in the method (as you've done):
private static ConsoleKeyInfo GetKeyFromUser(string prompt)
{
Console.Write(prompt);
var key = Console.ReadKey();
Console.WriteLine();
return key;
}
private static string GetStringFromUser(string prompt)
{
Console.Write(prompt);
return Console.ReadLine();
}
public static int GetIntFromUser(string prompt = null)
{
int input;
int row = Console.CursorTop;
int promptLength = prompt?.Length ?? 0;
do
{
Console.SetCursorPosition(0, row);
Console.Write(prompt + new string(' ', Console.WindowWidth - promptLength - 1));
Console.CursorLeft = promptLength;
} while (!int.TryParse(Console.ReadLine(), out input));
return input;
}
With these methods in place, getting input is as simple as:
string name = GetStringFromUser("Enter your name: ");
int age = GetIntFromUser("Enter your age: ");
And it makes writing the method to get a Song from the user that much easier:
private static Song GetSongFromUser()
{
return new Song(
GetStringFromUser("Enter song name: "),
GetStringFromUser("Enter Artist name: "),
GetIntFromUser("Enter number of copies sold: "));
}
So now our main method just looks like (and this is the answer to your question):
private static void Main()
{
var songs = new Song[4];
for (int i = 0; i < songs.Length; i++)
{
songs[i] = GetSongFromUser();
}
Console.WriteLine("\nYou've entered the following songs: ");
foreach (Song song in songs)
{
Console.WriteLine(song.GetDetails());
}
GetKeyFromUser("\nDone! Press any key to exit...");
}
Additionally, here are some suggestions for improving the Song class.
I'm currently working with linked lists. My assignment is asking me to make use of them in order to create a reverse polish calculator. I am having trouble understanding how to place each part of the string into the stack. My code currently looks like this
Main:
static void Main(string[] args)
{
LinkedList data = new LinkedList();
string UserData = Console.ReadLine();
Console.WriteLine("Enter a calculation in polish notation.");
data.Parse(UserData);
}
here i am simply accessing the parse method within the "LinkedList" class that receives the string "UserData". From there I'd like to start reading through the string and placing each part into the stack. I am not clear on how I can achieve that without arrays.
Here is my parse method:
public void Parse(string Input)
{
int data;
if(Input!=null)
{
}
}
As you can see I have nothing in it. In my head I'm thinking I should parse the string, then place each item in the stack but I'm more than likely wrong. Here is the entire LinkedList Class just in case:
public class LinkedList
{
private Node FrontHead;
public void printNodes()
{
Node Current = FrontHead;
while (Current != null)
{
Console.WriteLine(Current.data);
Current = Current.next;
}
}
public void Parse(string Input)
{
int data;
if(Input!=null)
{
}
}
public void Add(Object data)
{
Node NextToadd = new Node();
NextToadd.data = data;
NextToadd.next = FrontHead;
FrontHead = NextToadd;
}
public void last(Object data)
{
if (FrontHead == null)
{
FrontHead = new Node();
FrontHead.data = data;
FrontHead.next = null;
}
else
{
Node New = new Node();
New.data = data;
Node Crt = FrontHead;
while (Crt.next != null)
{
Crt = Crt.next;
}
Crt.next = New;
}
}
}
And the Node Class:
public class Node
{
public Object data;
public Node next;
}
a sample run would look like this:
input:
1 2 + =
output:
3
You could do
public void Parse(string Input)
{
if(Input!=null)
{
foreach (string s in Input.Split(' '))
{
Add(s);
}
}
}
But technically speaking, Split returns an array. So it depends what you mean by "NO ARRAYS", as i'm assuming that means the linked list is not an array, but arrays can be used elsewhere
Just iterate each sub-string of given string in Parse method. and call last(Name as per your code) method to add each substring to your list
public void Parse(string Input)
{
int data;
if(Input!=null)
{
foreach(string subString in Input.Split(' '))
{
//call last method
last(subString);
}
}
}
I am dealing with values delimited by commas sent to me as a string. The strings come in many different structures (meaning different data types in different locations of the string as well as varying amounts of data). So while one string might be represented as:
- common data,identifier,int,string,string,string.
Another might be represented as:
- common data,identifier,int,int,string,string,string.
Design goals:
Common parse method
Common validation (i.e. int.TryParse() returns true)
Readily able to add different structures
Is there a good design pattern, or combination of design patterns, that allows me to parse the values, check them, and return an object only if the right amount of values were pulled in and those values were the expected data types?
Note: I am dealing with more than 30 different string structures.
If all the lines start with common data, identifier, and then are followed by a variable but expected (i.e. known based on the identifier) set of values, then a table approach could work well. To continue your example, say you have two different types:
common data,identifier,int,string,string,string.
common data,identifier,int,int,string,string,string.
You can build a class that defines what you're looking for:
class ItemDesc
{
public string Ident { get; private set; }
public string Fields { get; private set; }
public ItemDesc(string id, string flds)
{
Ident = id;
Fields = flds;
}
}
The Fields property is just a string that contains one-character type descriptions for the variable data. That is, "isss" would be interpreted as int,string,string,string.
You can then build a Dictionary<string, ItemDesc> that you can use to look these up:
Dictionary<string, ItemDesc> ItemLookup = new Dictionary<string, ItemDesc>
{
{ "ItemType1", new ItemDesc("ItemType1", "isss") },
{ "ItemType2", new ItemDesc("ItemType2", "iisss") },
};
Now when you read a line, use string.Split() to split it into fields. Get the identifier, look it up the dictionary to get the item descriptions, and then parse the rest of the fields. Something like:
string line = GetLine();
var fields = line.Split(',');
// somehow get the identifier
string id = GetIdentifier();
ItemDesc desc;
if (!ItemLookup.TryGetValue(id, out desc))
{
// unrecognized identifier
}
else
{
int fieldNo = 3; // or whatever field is after the identifier
foreach (var c in desc.Fields)
{
switch (c)
{
case 'i' :
// try to parse an int and save it.
break;
case 's' :
// save the string
break;
default:
// error, unknown field type
break;
}
++fieldNo;
}
}
// at this point if no errors occurred, then you have a collection
// of parsed fields that you saved. You can now create your object.
would need little more details, based on your problem domain it could entirely change. but following seem to be the first set of patterns, they are ordered on suitability.
Interpreter
Strategy
Builder
Just split them using string.Split(), and then int.Parse() or int.TryParse() each int value in the resulting array as needed.
var myStrings = string.Split(sourceString);
int myint1 = int.Parse(myStrings[0]);
There are several ways of dealing with this. Here's a simple one (outputting just an object array):
class Template
{
// map identifiers to templates
static Dictionary<string, string> templates = new Dictionary<string, string>
{
{ "type1", "isss" },
{ "type2", "iisss" },
};
static bool ParseItem(string input, char type, out object output)
{
output = null;
switch (type)
{
case 'i':
int i;
bool valid = int.TryParse(input, out i);
output = i;
return valid;
case 's':
output = input;
return true;
}
return false;
}
public static object[] ParseString(string input)
{
string[] items = input.Split(',');
// make sure we have enough items
if (items.Length < 2)
return null;
object[] output = new object[items.Length - 2];
string identifier = items[1];
string template;
// make sure a valid identifier was specified
if (!templates.TryGetValue(identifier, out template))
return null;
// make sure we have the right amount of data
if (template.Length != output.Length)
return null;
// parse each item
for (int i = 0; i < template.Length; i++)
if (!ParseItem(items[i + 2], template[i], out output[i]))
return null;
return output;
}
}
If you're interested in returning actual objects instead of just object arrays, you can put metadata into the class definitions of the objects you're returning. Then when you get the object type you look for the metadata to figure out where to find its value in the input array. Here's a quick example:
namespace Parser
{
// create metadata attribute
class CsvPositionAttribute : Attribute
{
public int Position { get; set; }
public CsvPositionAttribute(int position)
{
Position = position;
}
}
// define some classes that use our metadata
public class type1
{
[CsvPosition(0)]
public int int1;
[CsvPosition(1)]
public string str1;
[CsvPosition(2)]
public string str2;
[CsvPosition(3)]
public string str3;
}
public class type2
{
[CsvPosition(0)]
public int int1;
[CsvPosition(1)]
public int int2;
[CsvPosition(2)]
public string str1;
[CsvPosition(3)]
public string str2;
[CsvPosition(4)]
public string str3;
}
public class CsvParser
{
public static object ParseString(string input)
{
string[] items = input.Split(',');
// make sure we have enough items
if (items.Length < 2)
return null;
string identifier = items[1];
// assume that our identifiers refer to a type in our namespace
Type type = Type.GetType("Parser." + identifier, false);
if (type == null)
return null;
object output = Activator.CreateInstance(type);
// iterate over fields in the type -- you may want to use properties
foreach (var field in type.GetFields())
// find the members that have our position attribute
foreach (CsvPositionAttribute attr in
field.GetCustomAttributes(typeof(CsvPositionAttribute),
false))
// if the item exists, convert it to the type of the field
if (attr.Position + 2 >= items.Length)
return null;
else
// ChangeType may throw exceptions on failure;
// catch them and return an error
try { field.SetValue(output,
Convert.ChangeType(items[attr.Position + 2],
field.FieldType));
} catch { return null; }
return output;
}
}
}