// ========================================================================== // Class Implementation : COXCsvFile // ========================================================================== // Header file : OXCsvFile.h // Version: 9.3 // This software along with its related components, documentation and files ("The Libraries") // is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is // governed by a software license agreement ("Agreement"). Copies of the Agreement are // available at The Code Project (www.codeproject.com), as part of the package you downloaded // to obtain this file, or directly from our office. For a copy of the license governing // this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900. // ////////////////////////////////////////////////////////////////////////// #include #include #include #include "OXCsvFile.h" #include "UTBStrOp.h" #include "UTB64Bit.h" // _ttoi64 is incorrectly defined in TCHAR.H, so you need to undefine // it and redefine it correctly to avoid a compile error when // building a non-Unicode version of your application. #ifdef _UNICODE #ifdef _ttoi64 #undef _ttoi64 #define _ttoi64 _wtoi64 #endif #else #ifdef _ttoi64 #undef _ttoi64 #define _ttoi64 _atoi64 #endif #endif // // Internal constants // const TCHAR tcZero=_T('0'); const TCHAR tcNulc=_T('\0'); /** static strip(CString& text) This function takes the passed in CString object, and removes any leading or trailing whitespace characters. The CString is modified directly, returning a string that either starts and ends with non-whitepace characters, or an empty string. --- In : strText : Reference to the CString that is to be stripped. --- Out : strText : The string, with all leading and trailing whitespace removed. --- Returns : --- Effect : **/ static void inline strip(CString& strText) { strText.TrimRight(); strText.TrimLeft(); } IMPLEMENT_DYNAMIC(COXCsvFile, CStdioFile) /** Textual error messages for reporting the error to a user. **/ LPCTSTR COXCsvFile::m_pstrErrorMsgs[]={ _TEXT("No Error"), _TEXT("Invalid column index was specified"), _TEXT("Invalid column name was specified"), _TEXT("Column contained an invalid numeric value"), _TEXT("Column data did not match data in set"), _TEXT("Line was too long"), _TEXT("Line contained too many records"), }; COXCsvFile::COXCsvFile() : CStdioFile(), m_nColumns(0), m_nLastError(errNone), m_bLineEmpty(FALSE), m_tcFieldDelim(_T(',')), m_tcStringDelim(_T('\"')) { } COXCsvFile::COXCsvFile(FILE *pFile) : CStdioFile(pFile), m_nColumns(0), m_nLastError(errNone), m_bLineEmpty(FALSE), m_tcFieldDelim(_T(',')), m_tcStringDelim(_T('\"')) { } COXCsvFile::COXCsvFile(LPCTSTR lpszFileName, UINT nOpenFlags) : CStdioFile(lpszFileName,nOpenFlags), m_nColumns(0), m_nLastError(errNone), m_bLineEmpty(FALSE), m_tcFieldDelim(_T(',')), m_tcStringDelim(_T('\"')) { } #ifdef _DEBUG void COXCsvFile::AssertValid() const { CStdioFile::AssertValid(); ASSERT(m_nColumns==m_arrColumns.GetSize()); ASSERT(m_tcFieldDelim!=tcNulc); ASSERT(m_tcStringDelim!=tcNulc); } void COXCsvFile::Dump(CDumpContext& dc) const { CStdioFile::Dump(dc); dc << _T("Number of Columns: ") << m_nColumns << _T("\n"); dc << _T("Last Error: (") << GetLastError() << _T(") ") << GetLastErrorMsg() << _T("\n"); if(IsLineEmpty()) { dc << _T("The current line is empty\n"); } dc << _T("Field Delimiter: '") << m_tcFieldDelim << _T("'\n"); dc << _T("String Delimiter: '") << m_tcStringDelim << _T("'\n"); } #endif ///////////////////////////////// // Column management functions // ///////////////////////////////// void COXCsvFile::SetColumnInfo(int nIndex, LPCTSTR lpszName, Types nType, BOOL bQuote) { ASSERT(nIndex>=0 && nIndex=0 && nIndex0) m_arrColumns.SetSize(nCount); m_nColumns=nCount; for(nIndex=0; nIndex0) m_arrColumns.SetSize(nColumns); m_nColumns=nColumns; } void COXCsvFile::SetColumns(const CStringArray& arrColumns) { int nIndex; SetError(errNone); // // store the header information // m_nColumns=PtrToInt(arrColumns.GetSize()); m_arrColumns.RemoveAll(); m_arrColumns.SetSize(m_nColumns); for(nIndex=0; nIndex=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } return WriteColumn(nColumn, lpszText, m_arrColumns[nColumn].m_bQuote); } BOOL COXCsvFile::WriteColumn(int nColumn, LPCTSTR lpszText, BOOL bQuote) { CString strSpecialChars; // the field and string delimiters, and line break characters strSpecialChars=m_tcFieldDelim; strSpecialChars+=m_tcStringDelim; strSpecialChars+=_T("\r\n"); SetError(errNone); if(nColumn<0 || nColumn>=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } // // Important: The string needs to be quoted if one of the following conditions // is met: // 1. The string contains one of the following characters: // a. the field delimiter character // b. the string delimiter character // c. carraige return // d. line feed // 2. The string begins with a whitespace character // 3. The string ends with a whitespace character // if(!bQuote && _tcscspn(lpszText,strSpecialChars)>=_tcslen(lpszText) && !_istspace(lpszText[0]) && !_istspace(lpszText[_tcslen(lpszText)-1])) { m_arrColumns[nColumn].m_strData=lpszText; } else { CString strItem(m_tcStringDelim); for(int nIndex=0; lpszText[nIndex]!=tcNulc; ++nIndex) { if(lpszText[nIndex]==m_tcStringDelim) { strItem+=m_tcStringDelim; strItem+=m_tcStringDelim; } else { strItem+=lpszText[nIndex]; } } strItem+=m_tcStringDelim; m_arrColumns[nColumn].m_strData=strItem; m_arrColumns[nColumn].m_nType=tString; } return TRUE; } inline TCHAR toHex(int nDigit) { ASSERT(nDigit>=0 && nDigit<16); return (TCHAR)((nDigit>=10) ? _T('a')+nDigit-10 : tcZero+nDigit); } BOOL COXCsvFile::WriteColumn(int nColumn, unsigned char ucData, BOOL bHex) { TCHAR text[5]; if(nColumn<0 || nColumn>=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } if(bHex) { // // generate a hexidecimal string // text[0]=tcZero; text[1]=_T('x'); text[2]=toHex((ucData >> 4) & 0x0f); text[3]=toHex(ucData & 0x0f); text[4]=tcNulc; } else { if(ucData>=100) { // // generate a 3 digit decimal string // text[0]=(TCHAR)(tcZero+(ucData/100)); text[1]=(TCHAR)(tcZero+((ucData%100)/10)); text[2]=(TCHAR)(tcZero+(ucData%10)); text[3]=tcNulc; } else if(ucData>=10) { // // generate a 2 digit decimal string // text[0]=(TCHAR)(tcZero+(ucData/10)); text[1]=(TCHAR)(tcZero+(ucData%10)); text[2]=tcNulc; } else { // // generate a 1 digit decimal string // text[0]=(TCHAR)(tcZero+ucData); text[1]=tcNulc; } } m_arrColumns[nColumn].m_strData=text; m_arrColumns[nColumn].m_nType=tByte; return TRUE; } BOOL COXCsvFile::WriteColumn(int nColumn, unsigned short unData, BOOL bHex) { TCHAR text[7]; if(nColumn<0 || nColumn>=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } if(bHex) { // // generate a hexidecimal value // text[0]=tcZero; text[1]=_T('x'); text[2]=toHex((unData >> 12) & 0x0f); text[3]=toHex((unData >> 8) & 0x0f); text[4]=toHex((unData >> 4) & 0x0f); text[5]=toHex(unData & 0x0f); text[6]=tcNulc; } else { if(unData>=10000) { // // generate a 5 digit decimal string // text[0]=(TCHAR)(tcZero+(unData/10000)); text[1]=(TCHAR)(tcZero+((unData%10000)/1000)); text[2]=(TCHAR)(tcZero+((unData%1000)/100)); text[3]=(TCHAR)(tcZero+((unData%100)/10)); text[4]=(TCHAR)(tcZero+(unData%10)); text[5]=tcNulc; } else if(unData>=1000) { // // generate a 4 digit decimal string // text[0]=(TCHAR)(tcZero+(unData/1000)); text[1]=(TCHAR)(tcZero+((unData%1000)/100)); text[2]=(TCHAR)(tcZero+((unData%100)/10)); text[3]=(TCHAR)(tcZero+(unData%10)); text[4]=tcNulc; } else if(unData>=100) { // // generate a 3 digit decimal string // text[0]=(TCHAR)(tcZero+(unData/100)); text[1]=(TCHAR)(tcZero+((unData%100)/10)); text[2]=(TCHAR)(tcZero+(unData%10)); text[3]=tcNulc; } else if(unData>=10) { // // generate a 2 digit decimal string // text[0]=(TCHAR)(tcZero+(unData/10)); text[1]=(TCHAR)(tcZero+(unData%10)); text[2]=tcNulc; } else { // // generate a 1 digit decimal string // text[0]=(TCHAR)(tcZero+unData); text[1]=tcNulc; } } m_arrColumns[nColumn].m_strData=text; m_arrColumns[nColumn].m_nType=tShort; return TRUE; } BOOL COXCsvFile::WriteColumn(int nColumn, unsigned long ulData, BOOL bHex) { TCHAR text[20]; if(nColumn<0 || nColumn>=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } if(bHex) { UTBStr::stprintf(text, 20, _T("0x%08x"), ulData); } else { UTBStr::stprintf(text, 20, _T("%d"), ulData); } m_arrColumns[nColumn].m_strData=text; m_arrColumns[nColumn].m_nType=tLong; return TRUE; } BOOL COXCsvFile::WriteColumn(int nColumn, short nData, BOOL bHex) { TCHAR text[20]; if(nColumn<0 || nColumn>=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } if(bHex) { UTBStr::stprintf(text, 20, _T("0x%04x"), nData); } else { UTBStr::stprintf(text, 20, _T("%d"), nData); } m_arrColumns[nColumn].m_strData=text; m_arrColumns[nColumn].m_nType=tShort; return TRUE; } BOOL COXCsvFile::WriteColumn(int nColumn, long lData, BOOL bHex) { TCHAR text[20]; if(nColumn<0 || nColumn>=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } if(bHex) { UTBStr::stprintf(text, 20, _T("0x%08x"), lData); } else { UTBStr::stprintf(text, 20, _T("%d"), lData); } m_arrColumns[nColumn].m_strData=text; m_arrColumns[nColumn].m_nType=tLong; return TRUE; } BOOL COXCsvFile::WriteColumn(int nColumn, float fData) { TCHAR text[20]; if(nColumn<0 || nColumn>=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } UTBStr::stprintf(text, 20, _T("%f"), fData); // // Use the string write column in case quoting is needed. Some nations use comma // as the decimal character, and also to account for the variable delimiter characters // that might be used. // WriteColumn(nColumn, text); m_arrColumns[nColumn].m_nType=tFloat; return TRUE; } BOOL COXCsvFile::WriteColumn(int nColumn, double dData) { TCHAR text[20]; if(nColumn<0 || nColumn>=m_nColumns) { SetError(errBadColumnIndex); return FALSE; } UTBStr::stprintf(text, 20, _T("%f"), dData); // // Use the string write column in case quoting is needed. Some nations use comma // as the decimal character, and also to account for the variable delimiter characters // that might be used. // WriteColumn(nColumn, text); m_arrColumns[nColumn].m_nType=tDouble; return TRUE; } void COXCsvFile::WriteLine() { CString strLine; // // Run through the columns, adding each of them to the line for output to the file. // Clear the data value for each column after it has been added. // for(int nColumn=0; nColumn=m_arrColumns.GetSize()) { m_arrColumns.SetSize(nColumn+1); m_nColumns=nColumn+1; } if (nColumn) m_bLineEmpty=FALSE; else m_bLineEmpty=TRUE; m_arrColumns[nColumn].m_strData=strText; ++nColumn; strBlanks.Empty(); strText.Empty(); } else if(tcNext==m_tcFieldDelim) { // // We have reached the end of an item // if(nColumn==m_nColumns) SetError(errTooManyColumns); if(nColumn>=m_arrColumns.GetSize()) { m_arrColumns.SetSize(nColumn+1); m_nColumns=nColumn+1; } m_bLineEmpty=FALSE; m_arrColumns[nColumn].m_strData=strText; ++nColumn; strBlanks.Empty(); strText.Empty(); } else if(_istspace(tcNext)) { if(!strText.IsEmpty()) { strBlanks+=tcNext; } } else { if(!strBlanks.IsEmpty()) { strText+=strBlanks; strBlanks.Empty(); } strText+=tcNext; } } if(!strText.IsEmpty()) { if(nColumn==m_nColumns) SetError(errTooManyColumns); if(nColumn>=m_arrColumns.GetSize()) { m_arrColumns.SetSize(nColumn+1); m_nColumns=nColumn+1; } m_arrColumns[nColumn].m_strData=strText; m_bLineEmpty=FALSE; } if(m_nColumns==-1) { m_nColumns=nColumn; if(GetLastError()==errTooManyColumns) SetError(errNone); } bRetval=(GetLastError()==errNone); } return bRetval; } int COXCsvFile::FindColumn(LPCTSTR lpszName) const { int nIndex; for(nIndex=0; nIndex=0 && nColumn=0 && nColumn=0 && nValue<=UCHAR_MAX) { ucData=(unsigned char)nValue; return TRUE; } SetError(errNumericValue); } else { SetError(errBadColumnIndex); } return FALSE; } BOOL COXCsvFile::ReadColumn(int nColumn, unsigned short& unData) { unData=0; if(nColumn>=0 && nColumn=0 && lValue<=USHRT_MAX) { unData=(unsigned short)lValue; return TRUE; } SetError(errNumericValue); } else { SetError(errBadColumnIndex); } return FALSE; } BOOL COXCsvFile::ReadColumn(int nColumn, int& nDataIndex, LPCTSTR lpstrSet[]) { CString strText, strItem; int nIndex; nDataIndex=0; if(nColumn>=0 && nColumn=0 && nColumn=SHRT_MIN && nValue<=SHRT_MAX) { nData=(short)nValue; return TRUE; } SetError(errNumericValue); } else { SetError(errBadColumnIndex); } return FALSE; } BOOL COXCsvFile::ReadColumn(int nColumn, long& lData) { lData=0; if(nColumn>=0 && nColumn=LONG_MIN && lValue<=LONG_MAX) { lData=(long)lValue; return TRUE; } SetError(errNumericValue); } else { SetError(errBadColumnIndex); } return FALSE; } BOOL COXCsvFile::ReadColumn(int nColumn, float& fData) { fData=0.0; if(nColumn>=0 && nColumn=(1-FLT_MAX) && fValue<=FLT_MAX) { fData=(float)fValue; return TRUE; } SetError(errNumericValue); } else { SetError(errBadColumnIndex); } return FALSE; } BOOL COXCsvFile::ReadColumn(int nColumn, double& dData) { dData=0.0; if(nColumn>=0 && nColumn=(1-DBL_MAX) && fValue<=DBL_MAX) { dData=(double)fValue; return TRUE; } SetError(errNumericValue); } else { SetError(errBadColumnIndex); } return FALSE; }