public static string[] SplitCSV(string sLine, char cFieldDelimX, char cTextDelimX, bool bStripTextDelimiterNotation) { //formerly CSVLineToRow
			string[] sarrReturn=null;
			int iElements=CountCSVElements(sLine,cFieldDelimX,cTextDelimX);
			//Console.Error.WriteLine( String.Format("Elements found:{0} text delimiter:'{1}'",iElements,char.ToString(cTextDelimX))); //debug only
			try {
				if (iElements>0) {
					sarrReturn=new string[iElements];
					int iFound=0;
					int iChar=0;
					bool bInQuotes=false;
					int iStartNow=0;
					int iEnderNow=-1;
					if (sLine!=null) {
						if (sLine!="") {
							while (iChar<=sLine.Length) {//intentionally <=
								if ( iChar!=sLine.Length&&sLine[iChar]==cTextDelimX) bInQuotes=!bInQuotes;
								if ( iChar==sLine.Length || (sLine[iChar]==cFieldDelimX&&!bInQuotes) ) {//TODO: make sure CountCSVFields has same logic, or combine the functions
									iEnderNow=iChar;
									sarrReturn[iFound]=sLine.Substring(iStartNow,iEnderNow-iStartNow);
									//Console.Error.WriteLine( String.Format("Found arg:\"{0}\" {{start:{1}; end:{2}; ender:{3}; next start:{4}}}",
									//	sarrReturn[iFound],iStartNow,(iChar==sLine.Length?"yes":"no"),iEnderNow,iEnderNow+1 )
									//);
									iFound++;
									iStartNow=++iEnderNow;
								}
								iChar++;
							}
							if (bStripTextDelimiterNotation) {
								for (int i=0; i<sarrReturn.Length; i++) {
									if (sarrReturn[i]!=null) {
										//Console.Error.Write("TextDelimiterNotation: "+sarrReturn[i]+" becomes ");
										sarrReturn[i]=RTable.CSVFieldToLiteralField(sarrReturn[i],cFieldDelimX,cTextDelimX);
										//Console.Error.WriteLine(sarrReturn[i]+".");
										//if (sarrReturn[i].Length>=2&&sarrReturn[i][0]==cTextDelimX&&sarrReturn[i][sarrReturn[i].Length-1]==cTextDelimX) {
										//	sarrReturn[i]=sarrReturn[i].Substring(1,sarrReturn[i].Length-2);
										//}
									}
								}
							}
						}//end if sLine!=""
						else {
							sarrReturn=new string[iElements];
							for (int i=0; i<iElements; i++) sarrReturn[i]="";
							//return new string[]{""};
						}
					}//end if sLine!=null
				}//end if iElements>0
			}
			catch (Exception exn) {
				Console.Error.WriteLine("Error in SplitCSV");
				Console.Error.WriteLine(exn.ToString());
				Console.Error.WriteLine();
			}
			return sarrReturn;
		}//end SplitCSV
		}//end RowToCSVLine
		public string RowToCSVLine(int AtInternalRowIndex, bool bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty, int ColumnStart, int ColumnCount) {
			int iAbs=ColumnStart;
			string sReturn="";
			try {
				for (int ColRel=0; ColRel<ColumnCount&&iAbs<this.Columns; ColRel++) {
					sReturn+=((ColRel!=0)?",":"")+RTable.LiteralFieldToCSVField(tearr[AtInternalRowIndex].Field(iAbs),this.cFieldDelimiter,this.cTextDelimiter,bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty);
					iAbs++;
				}
			}
			catch (Exception exn) {
				RReporting.ShowExn(exn,"converting row to csv line","rtable RowToCSVLine(AtInternalRowIndex="+AtInternalRowIndex+",bTabsAsNewLines="+(bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty?"true":"false")+",ColumnStart="+ColumnStart+",ColumnCount="+ColumnCount+")");
			}
			return sReturn;
		}
		public RTable CopyTitlesOnly() {
			RTable tReturn=null;
			try {
				if (this.teTitles!=null&&this.teTitles.Columns>0) {
					tReturn=new RTable(this.teTitles.Copy(),true);
				}
				else tReturn=new RTable();
			}
			catch (Exception exn) {
				tReturn=null;
				RReporting.ShowExn(exn,RReporting.sParticiple,"rtable CopyTitlesOnly()");
			}
			return tReturn;
		}//end CopyTitlesTo
		public string TitlesToCSVLine(bool bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty, bool bExtendedTitleColumns, int ColumnStart, int ColumnCount) {
			string sReturn="";
			RecheckIntegrity();
			if (this.teTitles!=null) {
				if (bExtendedTitleColumns) {
					string sField="";
					int ColAbs=ColumnStart;
					for (int ColRel=0; ColRel<ColumnCount; ColRel++) {
						sField="";
						if (bExtendedTitleColumns) {
							sField=RString.SafeString(GetForcedType(ColAbs),false);
							if (sField!="") sField+=" ";
						}//end if bExtendedTitleColumns
						string FieldDataNow=teTitles.Field(ColAbs);
						if (FieldDataNow==null) {
							RReporting.ShowErr("Can't access field","generating csv line","TitlesToCSVLine {NewLineInField:"+(bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty?"TAB":"BR with marker")+"; Row:title; Column:"+ColAbs+"}");
						}
						sField+=RString.SafeString(FieldDataNow,false);
						if (bExtendedTitleColumns) {
							string sMeta=GetForcedMeta(ColAbs);
							if (sMeta!=null) {
								sMeta=RString.SafeString(sMeta,false);
								sMeta=RString.RemoveEndsWhiteSpace(sMeta);
								if (!sMeta.StartsWith("{")) {
									sMeta=RString.Replace(sMeta,"{","");
									sMeta="{"+sMeta;
								}
								if (!sMeta.EndsWith("}")) {
									sMeta=RString.Replace(sMeta,"}","");
									sMeta+="}";
								}
								sField+=sMeta;
							}//end if metadata
						}//end if bExtendedTitleColumns
						sReturn+=((ColRel!=0)?",":"")+RTable.LiteralFieldToCSVField(sField,this.cFieldDelimiter,this.cTextDelimiter,bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty);
						ColAbs++;
					}//end for field (column header)
				}
				else sReturn=teTitles.ToCSVLine(cFieldDelimiter, cTextDelimiter, bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty,ColumnStart,ColumnCount);
			}
			else RReporting.ShowErr("Cannot read nonexistant title row.","Converting table titles to text line","TitlesToCSVLine");
			return sReturn;
		}
		}//end Save
		public bool SaveChunk(string sFile, bool bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty, bool bExtendedTitleColumns, int RowStart, int RowCount, int ColStart, int ColCount) {
			StreamWriter fsDest=null;
			//sLastFile=sFile; do NOT set, since only saving a chunk and therefore says nothing about the source
			//if (sLastFile==null) sLastFile="";
			bool bGood=false;
			//int iLines=0;
			RecheckIntegrity();
			try {
				fsDest=new StreamWriter(sFile);
				if (TitleRowIsNonBlank) {
					//fsDest.WriteLine(teTitles.ToCSVLine(cFieldDelimiter,cTextDelimiter,bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty));
					string sTitles="";
					string sField="";
					int ColAbs=ColStart;
					for (int ColRel=0; ColRel<ColCount; ColRel++) {
						sField="";
						if (bExtendedTitleColumns) {
							sField=RString.SafeString(GetForcedType(ColAbs),false);
							if (sField!="") sField+=" ";
						}//end if bExtendedTitleColumns
						string FieldDataNow=teTitles.Field(ColAbs);
						if (FieldDataNow==null) {
							RReporting.ShowErr("Can't access field","saving csv file","Save("+RReporting.StringMessage(sFile,true)+","+(bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty?"TAB as newline mode":"BR with marker as newline mode")+"){Row:title; Column:"+ColAbs+"}");
						}
						sField+=RString.SafeString(FieldDataNow,false);
						if (bExtendedTitleColumns) {
							string sMeta=GetForcedMeta(ColAbs);
							if (sMeta!=null) {
								sMeta=RString.SafeString(sMeta,false);
								sMeta=RString.RemoveEndsWhiteSpace(sMeta);
								if (!sMeta.StartsWith("{")) {
									sMeta=RString.Replace(sMeta,"{","");
									sMeta="{"+sMeta;
								}
								if (!sMeta.EndsWith("}")) {
									sMeta=RString.Replace(sMeta,"}","");
									sMeta+="}";
								}
								sField+=sMeta;
							}//end if metadata
						}//end if bExtendedTitleColumns
						sTitles+=((ColRel!=0)?",":"")+RTable.LiteralFieldToCSVField(sField,this.cFieldDelimiter,this.cTextDelimiter,bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty);
						ColAbs++;
					}//end for field (column header)
					fsDest.WriteLine(sTitles);
				}//end if Title Row is not blank
				Console.Error.Write("Writing row range "+RowStart+" to "+(RowStart+RowCount-1)+" of "+iRows+" (total "+0+" to "+(iRows-1)+")...");
				int RowAbs=RowStart;
				for (int RowRel=0; RowRel<RowCount&&RowAbs<iRows; RowRel++) {
					fsDest.WriteLine(RowToCSVLine(RowAbs,bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty,ColStart,ColCount));
					RowAbs++;
				}
				fsDest.Close();
				Console.Error.WriteLine("done.");
				bGood=true;
			}
			catch (Exception exn) {
				Console.Error.WriteLine("Could not save table to \""+sFile+"\":");
				Console.Error.WriteLine(exn.ToString());
				bGood=false;
				try { fsDest.Close(); }
				catch (Exception exn2) {
					RReporting.ShowExn(exn2,"closing file after exception","rtable Load");
				}
			}
			return bGood;
		}//end SaveChunk
		}//end Delete_AllWithMarker
		public bool Save(string sFile, bool bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty, bool Set_bSaveMetaDataInTitleRow) {
			if (sLastFile==sFileUnknown) {
				sLastFile=sFile;
				if (sLastFile==null) sLastFile="";
			}
			bSaveMetaDataInTitleRow=Set_bSaveMetaDataInTitleRow;
			StreamWriter fsDest=null;
			bool bGood=false;
			//int iLines=0;
			Delete_AllWithMarker();
			RecheckIntegrity();
			try {
				fsDest=new StreamWriter(sFile);
				if (TitleRowIsNonBlank) {
					//fsDest.WriteLine(teTitles.ToCSVLine(cFieldDelimiter,cTextDelimiter,bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty));
					string sTitles="";//cumulative
					string sField="";//cumulative
					for (int iCol=0; iCol<teTitles.Columns; iCol++) {
						sField="";//sField=RString.SafeString(GetForcedType(iCol),false);//old way
						if (bSaveMetaDataInTitleRow) sField=RString.SafeString(GetForcedType(iCol),false);
						if (sField!="") sField+=" ";
						string FieldDataNow=teTitles.Field(iCol);
						if (FieldDataNow==null) {
							RReporting.ShowErr("Can't access field","saving csv file","Save("+RReporting.StringMessage(sFile,true)+","+(bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty?"TAB as newline mode":"BR with marker as newline mode")+"){Row:title; Column:"+iCol+"}");
						}
						sField+=RString.SafeString(FieldDataNow,false);
						if (bSaveMetaDataInTitleRow) {
						string sMeta=GetForcedMeta(iCol);
							if (sMeta!=null) {
								sMeta=RString.SafeString(sMeta,false);
								sMeta=RString.RemoveEndsWhiteSpace(sMeta);
								if (!sMeta.StartsWith("{")) {
									sMeta=RString.Replace(sMeta,"{","");
									sMeta="{"+sMeta;
								}
								if (!sMeta.EndsWith("}")) {
									sMeta=RString.Replace(sMeta,"}","");
									sMeta+="}";
								}
								sField+=sMeta;
							}//end if metadata
						}//end if bSaveMetaDataInTitleRow
						sTitles+=((iCol!=0)?",":"")+RTable.LiteralFieldToCSVField(sField,this.cFieldDelimiter,this.cTextDelimiter,bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty);
					}//end for column title iCol
					fsDest.WriteLine(sTitles);
				}
				Console.Error.Write("Writing "+iRows+" rows...");
				for (int iNow=0; iNow<iRows; iNow++) {
					fsDest.WriteLine(RowToCSVLine(iNow,bReplaceNewLineWithTabInsteadOfHTMLBrWithMarkerProperty));
				}
				fsDest.Close();
				Console.Error.WriteLine("done.");
				bGood=true;
			}
			catch (Exception exn) {
				Console.Error.WriteLine("Could not save table to \""+sFile+"\":");
				Console.Error.WriteLine(exn.ToString());
				bGood=false;
				try { fsDest.Close(); }
				catch (Exception exn2) {
					RReporting.ShowExn(exn2,"closing file after exception","rtable Load");
				}
			}
			return bGood;
		}//end Save
		public bool Load(string sFile, bool FirstRowHasTitles) {
			sLastFile=sFile;
			if (sLastFile==null) sLastFile="";
			bool bGood=false;
			bFirstRowLoadAndSaveAsTitles=FirstRowHasTitles;
			StreamReader fsSource=null;
			string sLine=null;
			try {
				fsSource=new StreamReader(sFile);
				if (bFirstRowLoadAndSaveAsTitles) {
					sLine=fsSource.ReadLine();
					if ( bShowNewlineWarning && (RString.Contains(sLine,'\n')||RString.Contains(sLine,'\r')) ) {
						MessageBox.Show("Warning: newline character found in field.  File may have been saved in a different operating system and need line breaks converted.");
						bShowNewlineWarning=false;
					}
					teTitles=new TableEntry(RTable.SplitCSV(sLine,cFieldDelimiter,cTextDelimiter));
					//Parse TYPE NAME{METANAME:METAVALUE;...} title row notation:
					if (teTitles.Columns>0) {
						sarrFieldMetaData=new string[teTitles.Columns];
						iarrFieldType=new int[teTitles.Columns];
						for (int iColumn=0; iColumn<teTitles.Columns; iColumn++) {
							string FieldDataNow=teTitles.Field(iColumn);
							if (FieldDataNow==null) {
								RReporting.ShowErr("Field is not accessible","loading csv file","Load("+RReporting.StringMessage(sFile,true)+",...){Row 0:Titles; Column:"+iColumn+"}");
							}
							int iType=StartsWithType(FieldDataNow);
							int iStartName=0;
							if (iType>-1) {
								iarrFieldType[iColumn]=iType;
								iStartName=sarrType[iType].Length+1; //teTitles.SetField(iColumn,RString.SafeSubstring(teTitles.Field(iColumn),sarrType[iType].Length+1));
							}
							else {
								RReporting.Debug("Unknown type in column#"+iColumn.ToString()+"("+RReporting.StringMessage(FieldDataNow,true)+")");
							}
							int iMetaData=-1;
							//if (FieldDataNow!=null) {
							iMetaData=FieldDataNow.IndexOf("{");
							//}
							if (iMetaData>-1) {
								//string FieldDataNow=teTitles.Field(iColumn);
								if (FieldDataNow==null) {
									RReporting.ShowErr("Can't access field","loading csv file","rtable Load("+RReporting.StringMessage(sFile,true)+"){Row:titles; Column:"+iColumn+"}");
								}
								this.sarrFieldMetaData[iColumn]=FieldDataNow.Substring(iMetaData);
								while (iMetaData>=0 && (FieldDataNow[iMetaData]=='{'||FieldDataNow[iMetaData]==' ')) iMetaData--;
								teTitles.SetField(iColumn,RString.SafeSubstringByInclusiveEnder(FieldDataNow,iStartName,iMetaData));
							}
							else {
								teTitles.SetField(iColumn,RString.SafeSubstring(FieldDataNow,iStartName));
							}
						}//end for iColumn in title row
					}//end if teTitles.Columns>0
				}//if bFirstRowLoadAndSaveAsTitles
				tearr=new TableEntry[256];
				for (int iNow=0; iNow<tearr.Length; iNow++) {
					tearr[iNow]=null;
				}
				iRows=0;
				//if (!bFirstRowLoadAndSaveAsTitles||sLine!=null) {
				if (bAllowNewLineInQuotes) {
					bool bInQuotes=false;
					string sLineCombined="";
					while ( (sLine=fsSource.ReadLine()) != null ) {
						if (iRows>=Maximum) Maximum=iRows+iRows/2+1;
						for (int iChar=0; iChar<RString.SafeLength(sLine); iChar++) {
							if (sLine[iChar]==this.cTextDelimiter) bInQuotes=!bInQuotes;
						}
						sLineCombined+=sLine;
						if (!bInQuotes) {
							tearr[iRows]=new TableEntry(RTable.SplitCSV(sLineCombined,cFieldDelimiter,cTextDelimiter));
							iRows++;
							sLineCombined="";
						}
					}//end while not end of file
					if (sLineCombined!="") { //get bad data so it doesn't get lost
						tearr[iRows]=new TableEntry(RTable.SplitCSV(sLineCombined,cFieldDelimiter,cTextDelimiter));
						iRows++;
					}
				}
				else {
					while ( (sLine=fsSource.ReadLine()) != null ) {
						if (iRows>=Maximum) Maximum=iRows+iRows/2+1;
						tearr[iRows]=new TableEntry(RTable.SplitCSV(sLine,cFieldDelimiter,cTextDelimiter));
						iRows++;
					}
				}
				//}//if any data rows
				if (iRows<Maximum) {
					for (int i=iRows; i<Maximum; i++) {
						tearr[i]=new TableEntry();
					}
				}
				bGood=true;
				fsSource.Close();
			}
			catch (Exception exn) {
				RReporting.ShowExn(exn,"Loading table","rtable Load(\""+RReporting.StringMessage(sFile,true)+"\",FirstRowHasTitles="+(FirstRowHasTitles?"yes":"no")+")");
				try { fsSource.Close(); }
				catch (Exception exn2) {
					RReporting.ShowExn(exn2,"closing file after exception","rtable Load");
				}
			}
			return bGood;
		}//end Load