Assume that I have a DataGridView which is populated by a number of different strings (different length, numbers and plain text) in Cells.
Want I want to do is to copy and paste these strings, which could be any selection of Cells.
My approach to copy is:
if (e.Control && e.KeyCode == Keys.C)
{
// copy
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
Clipboard.SetDataObject(tmpCells);
}
Which is working properly.
My approach to paste is:
if (e.Control && e.KeyCode == Keys.V)
{
// paste
IDataObject dataInClipboard = Clipboard.GetDataObject();
string stringInClipboard = (string)dataInClipboard.GetData(DataFormats.Text);
char[] rowSplitter = { '\r', '\n' };
char[] columnSplitter = { '\t' };
string[] rowsInClipboard = stringInClipboard.Split(rowSplitter, StringSplitOptions.RemoveEmptyEntries);
int r1 = this.MyDataGridView.SelectedCells[0].RowIndex;
int c1 = this.MyDataGridView.SelectedCells[0].ColumnIndex;
int r2 = this.MyDataGridView.SelectedCells[this.MyDataGridView.SelectedCells.Count-1].RowIndex;
int c2 = this.MyDataGridView.SelectedCells[this.MyDataGridView.SelectedCells.Count-1].ColumnIndex;
int r = Math.Min(r1, r2); // Do not care if selection was taken by drag mouse up or down, always start from min
int c = Math.Min(c1, c2); // Do not care if selection was taken by drag mouse left or right, always start from min
for (int iRow = 0; iRow < rowsInClipboard.Length; ++iRow )
{
string[] valuesInRow = rowsInClipboard[iRow].Split(columnSplitter);
for (int iCol = 0; iCol < valuesInRow.Length; ++iCol )
{
if (this.MyDataGridView.ColumnCount-1 >= c + iCol)
{
DataGridViewCell DGVC = (this.MyDataGridView.Rows[r + iRow].Cells[c + iCol]);
DGVC.Value = valuesInRow[iCol];
}
}
}
}
}
Which works fine UNLESS the string itself DOES NOT contain any delimiter I specified with rowSplitter and columnSplitter. But this unfortunately is the case very often. It then separates the string and expands it to the next cell.
Example:
Cell[n] = {"This string contains a new line delimiter \n but should use only one cell."}
Will be pasted to:
Cell[n] = {"This string contains a new line delimiter"};
Cell[n+1] = {"but should use only one cell."}
So my question is: is it possible to restore the DataGridViewSelectedCellCollection as it was copied to the clipboard before? Just casting from object to DataGridViewSelectedCellCollection will not work:
DataGridViewSelectedCellCollection DGSCC = (DataGridViewSelectedCellCollection)dataInClipboard; // compiles, but throws exception at runtime
Do I have any other option but parsing each string by a defined formatting?
You will have to define own format for clipboard, which will do what default one can't do for you.
Simplest solution in this specific case is to convert multi-line breaks into \n and then convert back when you paste, but in any case it means no more
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
Clipboard.SetDataObject(tmpCells);
but
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
string result = "";
foreach(DataGridViewCell cell in tempCells)
// ... try to replicate what default clipboard text representation does
// change line breaks
Clipboard.SetDataObject(result.Replace("\xd\xa", "\\n"));
and paste will be:
IDataObject dataInClipboard = Clipboard.GetDataObject();
string stringInClipboard = dataInClipboard.GetData(DataFormats.Text).ToString().Replace("\\n", "\xd\xa");
Related
I have the following test table in Word, with one cell having a multilevel list:
Using the code below, I can copy cells from the Word Table to a corresponding cell in an Excel worksheet:
foreach (Microsoft.Office.Interop.Word.Table table in objDoc.Tables)
{
for (int row = 1; row <= table.Rows.Count; row++)
{
for (int col = 1; col <= table.Columns.Count; col++)
{
string text = table.Cell(row, col).Range.Text;
worksheet.Cells[row, col] = text;
}
}
}
However, I get the following result where the Word cell containing the list is not copied properly into Excel:
I have also tried the following:
worksheet.Cells[row, col] = table.Cell(row, col).Range.FormattedText;
But I get the same results.
I also tried converting the list in the Word file by copying and pasting with Keep Text Only to remove Word's automatic formatting, and manually deleting the tabs. That yielded this result:
Although I can get the text with the list numbers, I do not get a carriage return, line break, or line feed to separate the items the list.
At the very least, I would like to preserve the list numbering and line breaks without having to manually cut/paste with Keep Text Only; and I want to avoid having to parse the text for the list numbers (which could be numbers or letters) and inserting line feeds.
There are multiple problems involved with achieving the stated result:
Excel doesn't use the same character as Word for new lines or new paragraphs. (In this case it must be new paragraphs since the numbering is being generated.) Excel wants ANSI 10; Word is using ANSI 13. So that needs to be converted.
Automatic Line numbering is formatting. Passing a string loses formatting; it can only be carried across using Copy. Or the numbering has to be converted to plain text.
Another issue is the "dot" at the end of the cell content, which is again ANSI 13 in combination with ANSI 7 (end-of-cell marker). This should also be removed.
The following bit of sample code takes care of all three conversions. (Note: this is VBA code that I've converted off the top of my head, so watch out for small syntax "gotchas")
Word.Range rng = table.Cell[rowCounter, colCounter].Range;
//convert the numbers to plain text, then undo the conversion
rng.ListFormat.ConvertNumbersToText();
string cellContent = rng.Text;
objDoc.Undo(1);
//remove end-of-cell characters
cellContent = TrimCellText2(cellContent);
//replace remaining paragraph marks with the Excel new line character
cellContent.Replace((char)13, (char)10);
worksheet.Cells[rowCounter, colCounter].Value = cellContent;
//cut off ANSI 13 + ANSI 7 from the end of the string coming from a
//Word table cell
private string TrimCellText2(s As String)
{
int len = s.Length;
while (len > 0 && s.Substring(len - 1) == (char)13 || s.Substring(len - 1) == (char)7);
s = s.Substring(0, Math.Min(len-1, len));
return s;
}
With the help of Cindy Meister, combined with the answer from Paul Walls in this other question for replacing characters in a C# string, here is the resulting answer.
foreach (Microsoft.Office.Interop.Word.Table table in objDoc.Tables)
{
for (int row = 1; row <= table.Rows.Count; row++)
{
for (int col = 1; col <= table.Columns.Count; col++)
{
// Convert the formatted list number to plain text, then undo the conversion
table.Cell(row, col).Range.ListFormat.ConvertNumbersToText();
string cellContent = table.Cell(row, col).Range.Text;
objDoc.Undo(1);
// remove end-of-cell characters
cellContent = trimCellText2(cellContent);
// Replace remaining paragraph marks with the excel newline character
char[] linefeeds = new char[] { '\r', '\n' };
string[] temp1 = cellContent.Split(linefeeds, StringSplitOptions.RemoveEmptyEntries);
cellContent = String.Join("\n", temp1);
// Replace tabs from the list format conversion with spaces
char[] tabs = new char[] { '\t', ' ' };
string[] temp2 = cellContent.Split(tabs, StringSplitOptions.RemoveEmptyEntries);
cellContent = String.Join(" ", temp2);
worksheet.Cells[row, col] = cellContent;
}
}
}
private static string trimCellText2(string myString)
{
int len = myString.Length;
string charString13 = "" + (char)13;
string charString7 = "" + (char)7;
while ((len > 0 && myString.Substring(len - 1) == charString13) || (myString.Substring(len - 1) == charString7))
myString = myString.Substring(0, Math.Min(len - 1, len));
return myString;
}
And here is the resulting output in Excel: Excel Output
I generate textboxes like so:
for (byte i = 0; i < 4; ++i)
for (byte j = 0; j < 4; ++j)
Letterbox[i, j] = new TextBox();
Letterbox[i, j].Style = style; //MaxLength=1 among other things
...
and I'd wish that pasting string like this:
T
Ę
Ś
T
would result in first 4 textboxes containing 'T', 'Ę', 'Ś' and 'T' consecutively.
How can I achieve this?
Clarification (edit):
I want to be able to paste any string in this format (letter, enter, letter, enter...) when the application is running, not hardcode it
Here's an image explaining what I want.
You can use AddPasteHandler
like so:
DataObject.AddPastingHandler(tb, OnPaste);
OnPaste method implementation:
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
if (!isText) return;
var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string;
HandlePaste(text);
e.Handled = true;
}
private void HandlePaste(string text)
{
var letters = text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (letters.Length == 4)
{
for (var i = 0; i < 4; i++)
{
Letterbox[0, i].Text = letters[i];
}
}
}
tb is some other TextBox on my window, you can use whatever suits your requirements.
In HandlePaste pasted string is split by newlines and when it fits into 4 textboxes Text of each of them is set to appropriate letter. You could add some more validation here like checking if each of strings is exactly one char in length.
In my tool, user can choose one configuration (through combobox->multiple datatable) and the respective table will reflect in the excel sheet as per below.Columns (data in rows will differ) that will remain the same for all configuration are Product Name, Serial Name and Length 1 and Total Length. Different configuration will have added columns such as Length 2, Length 3,Length 4 (user will add the data in these rows)etc.
I want to add conditional formatting formula in Total Length column where background cell will turn green if it is in range (minval to maxval) and red when it is out of range. I am stuck with my code without solution.It did not change any color when the user add the data in the excel. Help. Thanks!
Table
private void ManualFormatExcelandAddRules(ExcelWorksheet WS, DataTable DTtoFormat, int ColStartAddOfDT, int RowStartAddOfDT)
{
int colCountofDT = DTtoFormat.Columns.Count;
int rowCountofDT = DTtoFormat.Rows.Count;
double minval = 0;
double maxval = 0;
int flag = 0;
for (int Colno = ColStartAddOfDT; Colno < ColStartAddOfDT + colCountofDT; Colno++)
{
WS.Cells[RowStartAddOfDT, Colno].Style.Border.BorderAround(ExcelBorderStyle.Thin);
for (int RowNo = RowStartAddOfDT + 1; RowNo <= RowStartAddOfDT + rowCountofDT; RowNo++)
{ if (WS.Cells[RowNo, Colno].Text.Contains("to") && WS.Cells[RowNo, ColStartAddOfDT].Text.Contains("DRAM"))
{
string[] GuidelineVal = WS.Cells[RowNo, Colno].Text.Split("to".ToArray(), StringSplitOptions.RemoveEmptyEntries).ToArray();
if (GuidelineVal[0].Trim() != "NA" && GuidelineVal[1].Trim() != "NA")
{
minval = Convert.ToDouble(GuidelineVal[0].Trim());
maxval = Convert.ToDouble(GuidelineVal[1].Trim());
flag = 0;
}
else
flag = 1;
}
else if (WS.Cells[RowNo, Colno].Text == "" && WS.Cells[RowStartAddOfDT + 1, Colno].Text.Contains("to"))
{
if (flag == 0)
{
string _statement = "AND(Convert.ToDouble(WS.Cells[RowNo, Colno].Text) >= minval,Convert.ToDouble(WS.Cells[RowNo, Colno].Text) <= maxval)";
var _cond = WS.ConditionalFormatting.AddExpression(WS.Cells[RowNo, Colno]);
_cond.Formula = _statement;
_cond.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
_cond.Style.Fill.BackgroundColor.Color = Color.Green;
}
else
WS.Cells[RowNo, Colno].Style.Fill.BackgroundColor.SetColor(Color.Red);
WS.Cells[RowNo, Colno].Style.Border.BorderAround(ExcelBorderStyle.Thin);
}
}
}
The conditional formatting expression you use is wrong/contains syntax errors/uses functions that don't exist and that makes that Excel will ignore it as it doesn't understand what it needs to do.
Looking at your code you have 4 variables that make up that expression:
RowNo and ColNo to indicate the cell to apply the conditional formattig to
minval and maxval to be the lower and upper bound of the condition
The following code uses those variables to build up the correct expression:
string _statement = string.Format(
CultureInfo.InvariantCulture,
"AND({0}>={1},{0}<={2})",
new OfficeOpenXml.ExcelCellAddress(RowNo, ColNo).Address,
minval,
maxval );
var _cond = WS.ConditionalFormatting.AddExpression(WS.Cells[RowNo, ColNo]);
_cond.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
_cond.Style.Fill.BackgroundColor.Color = Color.Green;
_cond.Formula = _statement;
Notice that the expression uses only valid Excel functions. You can't mixin .Net statements like Convert.ToDouble. It is also important to use the InvariantCulture for the number conversion, otherwise the separators might get interpreted as an extra parameter in your function.
When you debug this _statement will contain this: AND(A2>=40.2,A2<=44.5) and when applied to the A2 cell, that works as advertised.
I have a RichTextBox for a simple chat where I add lines programmatically.
I make the usernames bold and the messages in regular style.
After some lines I want to delete the first lines to keep the chat in a acceptably length. But when I do so I lose the text format and everything appears bold. What am I doing wrong and how can I fix this?
EDIT
I could solve the problem where I wasn't able to delete the first line.
I had to set the the ReadOnly property to false. Even though I was able to add new lines it prevented deleting lines. So the following code works to delete lines. Thanks to #TaW!
if (ChatText.Lines.Length >= 10)
{
int p = 0; int count = 0;
do
{
p = ChatText.Text.IndexOf("\n\r");
if (p >= 0)
{
ChatText.SelectionStart = p;
ChatText.SelectionLength = 2; // length of "\n\r"
ChatText.SelectedText = "\n";
count++;
}
}
while(p >= 0);
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = ChatText.Lines.Take(0).Select(x => x.Length + nll).Sum() - nll;
int pL = ChatText.Lines.Take(1).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
ChatText.SelectionStart = pS;
ChatText.SelectionLength = pL - pS;
ChatText.Cut();
}
//////////////////////////////////
// now add new lines
//////////////////////////////////
string[] chatstr;
// string text is given as method parameter
chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// go to the end of the text
ChatText.SelectionStart = ChatText.Text.Length;
ChatText.SelectionLength = 0;
// make text bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
// add username (chatstr[0]) and colon
ChatText.AppendText(chatstr[0] + ": ");
// make text regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
// add message (chatstr[1])
ChatText.AppendText(chatstr[1] + "\n");
// and finaly scroll down
ChatText.ScrollToCaret();
So deleting lines works and new lines are added as intended. Finaly!
solved :)
Never change the Text of a RichtTextBox if it contains any formatting.
Changing the Lines property (by Skip) is just another way to change the Text.
Instead only use the functions the RTB provides: Always start by selecting the portion you want to format, then apply one or more of the functions and/or set one or more of the properties..:
To delete portions use Cut.
Here is a function that will delete a number of entire lines:
void DeleteLines(RichTextBox rtb, int fromLine, int count)
{
int p1 = rtb.GetFirstCharIndexFromLine(fromLine);
int p2 = rtb.GetFirstCharIndexFromLine(fromLine + count);
rtb.SelectionStart = p1;
rtb.SelectionLength = p2 - p1;
bool readOnly = rtb.ReadOnly; // allow change even when the RTB is readonly
rtb.ReadOnly = false; ;
rtb.Cut();
rtb.ReadOnly = readOnly;
}
Trying to keept the formatting alive yourself is a tedious and error-prone waste of your time.
In addition to font properties you would also have to resore all other things you can set with the SelectedXXX properties, like colors, alignment, spacing etc etc..
To delete the first 3 lines use:
DeleteLines(yourRTB, 0, 3);
To restrict the text to 10 lines use:
DeleteLines(yourRTB, 0, yourRTB.Lines.Length - 10);
Note that the function above should have a few checks for valid input; I left them out as the checks somehow need a decision what to do, if count or fromLine if greater than Lines.Length or if fromLine is negative..
While we are at it, here is how to append a bold line:
yourRTB.SelectionStart = yourRTB.Text.Length;
yourRTB.SelectionLength = 0;
using (Font font = new Font(yourRTB.SelectionFont, FontStyle.Bold))
yourRTB.SelectionFont = font;
yourRTB.AppendText(yourNewLine + textOfNewLine);
Of course it really shold go into a reuseable function that the the bolding as a parameter..
Update:
since you are using WordWrap you may prefer this function. It deletes the actual lines, not the visible ones:
void DeleteLinesWW(RichTextBox rtb, int fromLine, int count)
{
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = rtb.Lines.Take(fromLine).Select(x => x.Length + nll).Sum() - nll;
int pL = rtb.Lines.Take(fromLine + count).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
rtb.SelectionStart = pS;
rtb.SelectionLength = pL - pS ;
bool readOnly = rtb.ReadOnly;
rtb.ReadOnly = false; // allow change even when the RTB is readonly
rtb.Cut();
rtb.ReadOnly = readOnly;
}
A word on NewLine: Do note that I have not used the Environment.NewLine constant as it not really a good idea. If you add multiline text to the RichTextBox in the designer and then look at it you will see that it uses simple '\n' new lines, no returns, no NL-CR, just '\n'. So this seems to be the generic way in a winforms RTB and I recommend using it..
The new function relies on all lines having a newline of the same length!
To make sure you can use this replacement function:
int RTBReplace(RichTextBox rtb, string oldText, string newText)
{
int p = 0; int count = 0;
do
{
p = richTextBox1.Text.IndexOf(oldText);
if (p >= 0)
{
richTextBox1.SelectionStart = p;
richTextBox1.SelectionLength = oldText.Length;
richTextBox1.SelectedText = newText;
count ++;
}
}
while (p >= 0);
return count;
}
Calling it like this:
RTBReplace(yourrichTextBox, "\r\n", "\n");
Update 2:
Here is an example how to add your chat lines:
private void button1_Click(object sender, EventArgs e)
{
string cLine = "Taw: Hello World"; // use your own lines!
var chatstr = cLine.Split(new string[] { ": " }, 2, StringSplitOptions.None);
AppendLineBold(yourrichTextBox, "\n" + chatstr[0], true);
AppendLineBold(yourrichTextBox, chatstr[1], false);
yourrichTextBox.ScrollToCaret();
}
void AppendLineBold(RichTextBox rtb, string text, bool bold)
{
rtb.SelectionStart = richTextBox1.Text.Length;
rtb.SelectionLength = 0;
using (Font font = new Font(rtb.SelectionFont,
bold ? FontStyle.Bold : FontStyle.Regular))
rtb.SelectionFont = font;
rtb.AppendText(text);
}
Update 3:
Looks like the ReadOnly property disallows the use of Cut. So we need to temporatily allow changes.
Funny: SelectedText can't be set either, but AppendText works fine..
To keep text formatting, you can also try the following (it's a little shorter and should also do the trick)
string text = "Username: hello this is a chat message";
// delete the first line when after 10 lines
if (ChatText.Lines.Length >= 10)
{
ChatText.SelectionStart = 0; // set SelectionStart to the beginning of chat text (RichTextBox)
ChatText.SelectionLength = ChatText.Text.IndexOf("\n", 0) + 1; // select the first line
ChatText.SelectedText = ""; // replace by an empty string
ChatText.SelectionStart = ChatText.Text.Length; // set SelectionStart to text end to make SelectionFont work for appended text
}
// split the string in chatstr[0] = username, chatstr[1] = message
string[] chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// make the username bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
ChatText.AppendText(chatstr[0] + ": ");
// make the message regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
ChatText.AppendText(chatstr[1] + Environment.NewLine);
ChatText.ScrollToCaret();
Im trying to read contents of a csv file into different variables in order to send to a web service.It has been working fine but suddenly today i got and exception.
index was outside the bounds of the array:
what Did I do wrong?
String sourceDir = #"\\198.0.0.4\e$\Globus\LIVE\bnk.run\URA.BP\WEBOUT\";
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(sourceDir);
foreach (string fileName2 in fileEntries)
{
// read values
StreamReader st = new StreamReader(fileName2);
while (st.Peek() >= 0)
{
String report1 = st.ReadLine();
String[] columns = report1.Split(','); //split columns
String prnout = columns[0];
String tinout = columns[1];
String amtout = columns[2];
String valdate = columns[3];
String paydate = columns[4];
String status = columns[5];
String branch = columns[6];
String reference = columns[7];
}
}
It's hard to guess without even seeing the .csv file, but my first one would be that you don't have 8 columns.
It would be easier if you could show the original .csv file, and tell us where the exception pops.
edit: If you think the data is alright, I'd suggest you debugging and see what the split call returns in Visual Studio. That might help
edit2: And since you're doing that processing in a loop, make sure each row has at least 8 columns.
My money is on bad data file. If that is the only thing in the equation that has changed (aka you haven't made any code changes) then that's pretty much your only option.
If your data file isn't too long post it here and we can tell you for sure.
You can add something like below to check for invalid column lengths:
while (st.Peek() >= 0)
{
String report1 = st.ReadLine();
String[] columns = report1.Split(','); //split columns
if(columns.Length < 8)
{
//Log something useful, throw an exception, whatever.
//You have the option to quitely note that there was a problem and
//continue on processing the rest of the file if you want.
continue;
}
//working with columns below
}
Just for sanity's sake, I combined all the various notes written here. This code is a bit cleaner and has some validation in it.
Try this:
string dir = #"\\198.0.0.4\e$\Globus\LIVE\bnk.run\URA.BP\WEBOUT\";
foreach (string fileName2 in Directory.GetFiles(dir)) {
StreamReader st = new StreamReader(fileName2);
while (!sr.EndOfStream) {
string line = sr.ReadLine();
if (!String.IsNullOrEmpty(line)) {
string[] columns = line.Split(',');
if (columns.Length == 8) {
string prnout = columns[0];
string tinout = columns[1];
string amtout = columns[2];
string valdate = columns[3];
string paydate = columns[4];
string status = columns[5];
string branch = columns[6];
string reference = columns[7];
}
}
}
}
EDIT: As some other users have commented, the CSV format also accepts text qualifiers, which usually means the double quote symbol ("). For example, a text qualified line may look like this:
user,"Hello!",123.23,"$123,123.12",and so on,
Writing CSV parsing code is a little more complicated when you have a fully formatted file like this. Over the years I've been parsing improperly formatted CSV files, I've worked up a standard code script that passes virtually all unit tests, but it's a pain to explain.
/// <summary>
/// Read in a line of text, and use the Add() function to add these items to the current CSV structure
/// </summary>
/// <param name="s"></param>
public static bool TryParseLine(string s, char delimiter, char text_qualifier, out string[] array)
{
bool success = true;
List<string> list = new List<string>();
StringBuilder work = new StringBuilder();
for (int i = 0; i < s.Length; i++) {
char c = s[i];
// If we are starting a new field, is this field text qualified?
if ((c == text_qualifier) && (work.Length == 0)) {
int p2;
while (true) {
p2 = s.IndexOf(text_qualifier, i + 1);
// for some reason, this text qualifier is broken
if (p2 < 0) {
work.Append(s.Substring(i + 1));
i = s.Length;
success = false;
break;
}
// Append this qualified string
work.Append(s.Substring(i + 1, p2 - i - 1));
i = p2;
// If this is a double quote, keep going!
if (((p2 + 1) < s.Length) && (s[p2 + 1] == text_qualifier)) {
work.Append(text_qualifier);
i++;
// otherwise, this is a single qualifier, we're done
} else {
break;
}
}
// Does this start a new field?
} else if (c == delimiter) {
list.Add(work.ToString());
work.Length = 0;
// Test for special case: when the user has written a casual comma, space, and text qualifier, skip the space
// Checks if the second parameter of the if statement will pass through successfully
// e.g. "bob", "mary", "bill"
if (i + 2 <= s.Length - 1) {
if (s[i + 1].Equals(' ') && s[i + 2].Equals(text_qualifier)) {
i++;
}
}
} else {
work.Append(c);
}
}
list.Add(work.ToString());
// If we have nothing in the list, and it's possible that this might be a tab delimited list, try that before giving up
if (list.Count == 1 && delimiter != DEFAULT_TAB_DELIMITER) {
string[] tab_delimited_array = ParseLine(s, DEFAULT_TAB_DELIMITER, DEFAULT_QUALIFIER);
if (tab_delimited_array.Length > list.Count) {
array = tab_delimited_array;
return success;
}
}
// Return the array we parsed
array = list.ToArray();
return success;
}
You should note that, even as complicated as this algorithm is, it still is unable to parse CSV files where there are embedded newlines within a text qualified value, for example, this:
123,"Hi, I am a CSV File!
I am saying hello to you!
But I also have embedded newlines in my text.",2012-07-23
To solve those, I have a multiline parser that uses the Try() feature to add additional lines of text to verify that the main function worked correctly:
/// <summary>
/// Parse a line whose values may include newline symbols or CR/LF
/// </summary>
/// <param name="sr"></param>
/// <returns></returns>
public static string[] ParseMultiLine(StreamReader sr, char delimiter, char text_qualifier)
{
StringBuilder sb = new StringBuilder();
string[] array = null;
while (!sr.EndOfStream) {
// Read in a line
sb.Append(sr.ReadLine());
// Does it parse?
string s = sb.ToString();
if (TryParseLine(s, delimiter, text_qualifier, out array)) {
return array;
}
}
// Fails to parse - return the best array we were able to get
return array;
}
Since you don't know how many columns will be in csv file, you might need to test for length:
if (columns.Length == 8) {
String prnout = columns[0];
String tinout = columns[1];
...
}
I bet you just got an empty line (extra EOL at the end), and that's as simple as that