0

I have a text file that I'm trying to input into an array called columns. Each row in the text file belongs to a different attribute in a sub-class I have created.

For example, row 2 in my text file is a date that I would like to pass over...I do not want to use the Split because I do not have a delimiter but I do not know an alternative. I am not fully understanding the below if someone could help. When I try to run it, it says that columns[1] is out of its range...Thank you.

StreamReader textIn = 
    new StreamReader(
    new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read));

//create the list
List<Event> events = new List<Event>();

while (textIn.Peek() != -1)
{
    string row = textIn.ReadLine();
    string[] columns = row.Split(' ');
    Event special = new Event();
    special.Day = Convert.ToInt32(columns[0]);
    special.Time = Convert.ToDateTime(columns[1]);
    special.Price = Convert.ToDouble(columns[2]);
    special.StrEvent = columns[3];
    special.Description = columns[4];
    events.Add(special);
}

Input file sample:

1 
8:00 PM 
25.00 
Beethoven's 9th Symphony 
Listen to the ninth and final masterpiece by Ludwig van Beethoven. 
2 
6:00 PM 
15.00 
Baseball Game 
Come watch the championship team play their archrival--No work stoppages, guaranteed.
Tim
  • 28,212
  • 8
  • 63
  • 76
Jae
  • 39
  • 1
  • 3
  • 9
  • Could you show a sample row of data that you're parsing? – Gjeltema Apr 22 '13 at 04:35
  • You must have a delimiter or a known length. Otherwise how would you be able to differentiate between rows? – ChiefTwoPencils Apr 22 '13 at 04:43
  • Any reason not to use existing serialization format (and corresponding classes) - JSON or XML are much more structured and easier to covert to objects, at least CSV which is much better for tabular data than list of attibutes. – Alexei Levenkov Apr 22 '13 at 04:45
  • Is there the possibility of multiple events in one text file, or is it one event per file? – Tim Apr 22 '13 at 04:51
  • There are multiple events in the one text file. Grrr, I dont know how to get my example in its own lines so I'm using a "/" for every line....Here is an example of the text file: 1/ 8:00 PM/ 25.00/ Beethoven's 9th Symphony/ Listen to the ninth and final masterpiece by Ludwig van Beethoven./ 2/ 6:00 PM/ 15.00/ Baseball Game/ Come watch the championship team play their archrival--No work stoppages, guaranteed./ – Jae Apr 22 '13 at 05:05
  • Use the `
    ` tags.  I've added it for you.
    – Tim Apr 22 '13 at 05:08
  • @JaeChang - I'm going to update my answer with a couple of examples that will show how much easier this would be if you use a delimited file or XML file. – Tim Apr 22 '13 at 05:17

3 Answers3

2

Well, one way to do it (though it is a bit ugly) would be to use File.ReadAllLines, and then loop through the array, something like this:

string[] lines = File.ReadAllLines(path);

int index = 0;

while (index < lines.Length)
{

    Event special = new Event();
    special.Day = Convert.ToInt32(lines[index]);
    special.Time = Convert.ToDateTime(lines[index + 1]);
    special.Price = Convert.ToDouble(lines[index + 2]);
    special.StrEvent = lines[index + 3];
    special.Description = lines[index + 4];
    events.Add(special);

    lines = lines + 5;
}

This is very brittle code - a lot can break it. What if one of the events is missing a line? What if there are multiple blank lines in it? What if one of the Convert.Toxxx methods throws an error?

If you have the option to change the format of the file, I strongly recommend you make it delimited at least. If you can't change the format, you'll need to make the code sample above more robust so that it can handle blank lines, failed conversions, missing lines, etc.

Much, much, much easier to use a delimited file. Even easier to use an XML or JSON file.

Delimited File (CSV)

Let's say you have the same sample input, but this time it's a CSV file, like this:

1,8:00 PM,25.00,"Beethoven's 9th Symphony","Listen to the ninth and final masterpiece by Ludwig van Beethoven."
2,6:00 PM,15.00,"Baseball Game","Come watch the championship team play their archrival--No work stoppages, guaranteed"

I put quotes on the last two items in case there's ever a comma in there, it won't break the parsing.

For CSV files, I like to use the Microsoft.VisualBasic.FileIO.TextFieldParser class, which despite it's name can be used in C#. Don't forget to add a reference to Microsoft.VisualBasic and a using directive (using Microsoft.VisualBasic.FileIO;).

The following code will allow you to parse the above CSV sample:

using (TextFieldParser parser = new TextFieldParser(path))
{

    parser.Delimiters = new string[] {","};
    parser.TextFieldType = Delimited;
    parser.HasFieldsEnclosedInQuotes = true;
    string[] parsedLine;

    while (!parser.EndOfData)
    {
        parsedLine = parser.ReadFields();

        Event special = new Event();
        special.Day = Convert.ToInt32(parsedLine[0]);
        special.Time = Convert.ToDateTime(parsedLine[1]);
        special.Price = Convert.ToDouble(parsedLine[2]);
        special.StrEvent = parsedLine[3];
        special.Description = parsedLine[4];
        events.Add(special);    
    }
}

This still has some issues though - you would need to handle cases where there were missing fields and I would recommend using TryParse methods instead of Convert.Toxxx, but it's a little easier (I think) than the non-delimited sampe.

XML File (Using LINQ to XML)

Now let's try it with an XML file and use LINQ to XML to get the data:

<Events>
  <Event>
    <Day>1</Day>
    <Time>8:00 PM</Time>
    <Price>25.00</Price>
    <Title><![CDATA[Beethoven's 9th Symphone]]></Title>
    <Description><![CDATA[Listen to the ninth and final masterpiece by Ludwig van Beethoven.]]></Description>
  </Event>
  <Event>
    <Day>2</Day>
    <Time>6:00 PM</Time>
    <Price>15.00</Price>
    <Title><![CDATA[Baseball Game]]></Title>
    <Description><![CDATA[Come watch the championship team play their archrival--No work stoppages, guaranteed]]></Description>
  </Event>
</Events>

I've used CDATA for the title and description so that special characters won't break the XML parsing.

This is easily parsed into your Events by the following code:

XDocument doc = XDocument.Load(path);

List<Event> events = (from x in doc.Descendants("Event")
                     select new Event {
                                Day = Convert.ToInt32(x.Element("Day").Value),
                                Time = Convert.ToDateTime(x.Element("Time").Value),
                                Price = Convert.ToDouble(x.Element("Price").Value),
                                StrEvent = x.Element("Title").Value,
                                Description = x.Element("Description").Value
                     }).ToList();

Of course, this is still not perfect as you still have the possibility of conversion failures or missing elements.

Pipe-Delimited File Example

Per our discussion in the comments, if you want to use the pipe (|), you need to put each event (in its entirety) on one line, like this:

1|8:00 PM|25.00|Beethoven's 9th Symphony|Listen to the ninth and final masterpiece by Ludwig van Beethoven.
2|6:00 PM|15.00,|Baseball Game|Come watch the championship team play their archrival--No work stoppages, guaranteed

You can still use the TextFieldParser example above if you like (just change the delimiter from , to |, or if you want you can use your original code.

Some Final Thoughts

I wanted to also address the original code and show why it wasn't working. The main reason was that you were reading one line at a time, and then splitting on ' '. This would have been a good start if all the fields were on the same line (although it still would have had problems because of spaces in the Time, StrEvent and Description fields), but they weren't.

So when you read the first line (which was 1) and split on ' ', you got one value back (1). When you tried to access the next element of the split array, you got the index out of range error because there was no columns[1] for that line.

Essentially, you were trying to treat each line as if it had all the fields in it, when in reality it was one field per line.

Tim
  • 28,212
  • 8
  • 63
  • 76
  • I am able to add a delimiter in my text which I have tried but I then get an error when I try to run it. I've tried the delimiter, "|" by adding that in my row.Split('|') and adding it to the end of every line and the error I got was that the special.Time = Convert.ToDateTime(columns[1]); was not a valid DateTime. – Jae Apr 22 '13 at 05:27
  • @JaeChang - the delimiter should go between each field, not at the end of the line. For example: `1|8:00 PM|25.00|Beethoven's 9th Symphony|Listen to the ninth and final masterpiece by Ludwig van Beethoven.`. – Tim Apr 22 '13 at 05:54
  • As for the not a valid DateTime error, I'm not sure why you got that...if the line was, for example `8:00 PM|`, I would have expected an index out of ranger error columns[1], assuming you split only on the |. If you split on the | and the ' ', then yes, you would get an invalid DateTime format error because columns[1] would equal "PM". – Tim Apr 22 '13 at 06:00
  • I really appreciate all your help and options. I'm a newbie and have not learned anything besides classes that work with text files. I will try to go off your comment on my delimiter being between each field. Thank you Tim! – Jae Apr 22 '13 at 06:02
  • You're welcome. Don't forget to put all the fields on one line (in other words, one line per event). I'll stick another example in my answer for you. – Tim Apr 22 '13 at 06:04
1

For your given sample file something like

string[] lines = File.ReadAllLines(path);

for (int index = 4; index < lines.Length; index += 5)
{
    Event special = new Event();
    special.Day = Convert.ToInt32(lines[index - 4]);
    special.Time = Convert.ToDateTime(lines[index - 3]);
    special.Price = Convert.ToDouble(lines[index - 2]);
    special.StrEvent = lines[index - 1];
    special.Description = lines[index];
    events.Add(special);
}

Would do the job, but like Tim already mentioned, you should consider changing your file format.

Tobias Schulte
  • 716
  • 5
  • 18
  • Hadn't thought of using a for loop that way - I wonder which would be more efficient, the while loop or the for loop? – Tim Apr 22 '13 at 06:18
  • @Tim In terms of performance it doesn't matter whether you you use a while loop or a for loop in most cases. One exception is when you loop through an array and use the "Length" field in the loop-condition in a for loop (in this case range checks on array access in the body are omitted, see [link](http://stackoverflow.com/questions/552766/for-and-while-loop-in-c-sharp)). But I don't know if that is true for access to the array like I do here. – Tobias Schulte Apr 22 '13 at 07:18
  • @Tobias Could you explain your given example? I'm not sure how the index += 5 works. Why does it increment by 5? I would like to have the 1st 5 rows as one object, the 2nd 5 rows as another object etc so I can have it populate in a textbox depending on user input. Thank you. – Jae Apr 24 '13 at 03:58
  • @Jae The basic idea is, that the body of the `for` loop is executed once for every `Event` in the file. So it has to read 5 lines in each iteration and therefore increment the `index` (which represents the current line) by 5. Initializing the index with 4 and then accessing the lines with `[index-4]`, `[index-3]`, ... and `[index]` combined with the check `index < lines.length` means that the array access can never be out of range. – Tobias Schulte Apr 24 '13 at 06:39
  • @Tobias Ahhh, I understand how to index works now but how is each loop differentiated? If I want to access the 3rd loop's event and get that to show in a textbox on my main form. How would I reference it? – Jae Apr 25 '13 at 04:13
  • @Jae After the loop has finished the 3rd Event in the file is the 3rd element in the `events` List and can therefore be referenced as `events[2]`. – Tobias Schulte Apr 25 '13 at 05:47
  • @Tobias Thanks again for explaining that to me! – Jae Apr 25 '13 at 05:55
0

delimiters can be deleted if your side column values haven't intersect char or have fix size.by this condition you can read file and split field on it.
if you want to read from file and load data automatically to variables , i suggest Serialize and deSeialize variabls to file but that file isn't text file!

mojtaba
  • 339
  • 1
  • 4
  • 17