Click here to Skip to main content
15,895,667 members
Articles / Desktop Programming / MFC

CTextFileDocument

Rate me:
Please Sign up or sign in to vote.
4.95/5 (84 votes)
24 May 2005Ms-RL10 min read 546.5K   10.5K   196  
CTextFileDocument lets you write and read text files with different encodings (ASCII, UTF-8, Unicode 16 little/big endian are supported).
// TextfileDemoDlg.cpp : implementation file
//

#include "stdafx.h"
#include "TextfileDemo.h"
#include "TextfileDemoDlg.h"
#include "textfile.h"
#include <vector>
#include <algorithm>
using namespace std;

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

#if PEK_TX_TECHLEVEL < 2

 #ifdef _UNICODE
  typedef wstring TextString;
 #else
  typedef string TextString;
 #endif

 void TtoC(CString& a, const TextString& b)
 {
	a = b.c_str();
 }  
 
#else

 typedef CString TextString;

 void TtoC(CString& a, const TextString& b)
 {
	a = b;
 }  

#endif


#ifndef _WIN32_WCE

#define TIMERSTART(TIMEVAR) clock_t TIMEVAR = clock();
#define TIMEREND(TIMEVAR, MESS) TRACE(MESS,  (double)(clock() - TIMEVAR) / CLOCKS_PER_SEC);

#else

#define TIMERSTART(TIMEVAR) 0;
#define TIMEREND(TIMEVAR, MESS) 0;

#endif






class defcodepage
{
public:
	defcodepage(CString a, UINT b)
	{
		text=a;
		value=b;
	}
	CString text;
	UINT value;
};

/*
CP_ACP ANSI code page 
CP_MACCP Macintosh code page 
CP_OEMCP OEM code page 
CP_SYMBOL Symbol code page (42) 
CP_THREAD_ACP The current thread's ANSI code page 
CP_UTF7 Translate using UTF-7 
CP_UTF8 
*/


defcodepage DefaultCodePages[] =
{
	defcodepage("CP_ACP (system default)", CP_ACP),
	defcodepage("CP_MACCP", CP_MACCP),
	defcodepage("CP_OEMCP", CP_OEMCP),
	defcodepage("CP_SYMBOL", CP_SYMBOL),
	defcodepage("CP_THREAD_ACP", CP_THREAD_ACP),
	defcodepage("CP_UTF7", CP_UTF7),
	defcodepage("CP_UTF8", CP_UTF8),
};


vector<int> SystemCodepages;

BOOL CALLBACK EnumCodePagesProc(LPTSTR lpCodePageString) 
{
	SystemCodepages.push_back( _ttoi(lpCodePageString) );

	return TRUE;
}



class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
	//{{AFX_DATA(CAboutDlg)
	enum { IDD = IDD_ABOUTBOX };
	//}}AFX_DATA

	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CAboutDlg)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
	//}}AFX_VIRTUAL

// Implementation
protected:
	//{{AFX_MSG(CAboutDlg)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
	//{{AFX_DATA_INIT(CAboutDlg)
	//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CAboutDlg)
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
	//{{AFX_MSG_MAP(CAboutDlg)
		// No message handlers
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTextfileDemoDlg dialog

CTextfileDemoDlg::CTextfileDemoDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CTextfileDemoDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CTextfileDemoDlg)
	m_filename = _T("");
	m_text = _T("");
	m_first = 0;
	m_last = 0;
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CTextfileDemoDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CTextfileDemoDlg)
	DDX_Control(pDX, IDC_COMBO_CODEPAGE, m_codepage);
	DDX_Control(pDX, IDC_COMBO_FORMAT, m_format);
	DDX_Text(pDX, IDC_EDIT_FILENAME, m_filename);
	DDX_Text(pDX, IDC_EDIT_TEXT, m_text);
	DDX_Text(pDX, IDC_EDIT_FIRSTCHAR, m_first);
	DDX_Text(pDX, IDC_EDIT_LASTCHAR, m_last);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CTextfileDemoDlg, CDialog)
	//{{AFX_MSG_MAP(CTextfileDemoDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON_SAVE, OnButtonSave)
	ON_BN_CLICKED(IDC_BUTTON_LOAD, OnButtonLoad)
	ON_BN_CLICKED(IDC_BUTTON_BROWSE, OnButtonBrowse)
	ON_BN_CLICKED(IDC_BUTTON_SELFTEST, OnButtonSelftest)
	ON_BN_CLICKED(IDC_BUTTON_FULLTEXT, OnButtonFulltext)
	ON_CBN_SELCHANGE(IDC_COMBO_FORMAT, OnSelchangeComboFormat)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTextfileDemoDlg message handlers

BOOL CTextfileDemoDlg::OnInitDialog()
{
	CDialog::OnInitDialog();


	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

#ifndef _WIN32_WCE
	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}
#endif

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	CString encodings[] = { _T("ASCII"), _T("UNI16_BE"), _T("UNI16_LE"), _T("UTF-8") };

	for(int k=0; k<sizeof(encodings)/sizeof(CString); k++)
		m_format.AddString( encodings[k] );


	
	// TODO: Add extra initialization here
	m_text = _T("Hello world!");
	m_filename = _T("testfile.txt");
	m_first = 32;
	m_last = 0xffff;
	UpdateData(FALSE);

	m_format.SetCurSel(1);

	EnumSystemCodePages(&EnumCodePagesProc, CP_SUPPORTED);

	for(int i=0; i<sizeof(DefaultCodePages)/sizeof(defcodepage); i++)
		m_codepage.AddString( DefaultCodePages[i].text );

	sort(SystemCodepages.begin(), SystemCodepages.end());
	
	for(int j=0; j<(int)SystemCodepages.size(); j++)
	{
		if(IsValidCodePage( SystemCodepages[j] ) )
		{
			CString a;
			a.Format(_T("%d"), SystemCodepages[j]);
			m_codepage.AddString(a);
		}
	}
	
	m_codepage.SetCurSel(0);

	CString s;
	s.Format(_T("System codepage: %d"), GetACP());
	GetDlgItem(IDC_STATIC_SYSTEMCP)->SetWindowText(s);

	return TRUE;  // return TRUE  unless you set the focus to a control
}


UINT CTextfileDemoDlg::GetSelectedCodepage()
{
	CString selected;
	m_codepage.GetLBText(m_codepage.GetCurSel(), selected);

	for(int i=0; i<sizeof(DefaultCodePages)/sizeof(defcodepage); i++)
	{
		if(DefaultCodePages[i].text == selected)
			return DefaultCodePages[i].value;
	}

	return _ttoi(selected);
}


void CTextfileDemoDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CTextfileDemoDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

#ifndef _WIN32_WCE
		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
#endif

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CTextfileDemoDlg::OnQueryDragIcon()
{
	return (HCURSOR) m_hIcon;
}

void CTextfileDemoDlg::OnButtonSave() 
{
	CWaitCursor wc;
	UpdateData();
	
	TRACE(_T("Start writing...\n"));
	TIMERSTART(writetime);

	CTextFileWrite file(m_filename, (CTextFileBase::TEXTENCODING) m_format.GetCurSel());
#if PEK_TX_TECHLEVEL > 0
	file.SetCodePage( GetSelectedCodepage() );
#endif

	if(!file.IsOpen())
	{
		MessageBox(_T("Couldn't open file!"), NULL, MB_ICONWARNING|MB_OK);
		return;
	}
	
	
	#ifdef _UNICODE
		file << m_text;
	#else
		
		int cp = GetSelectedCodepage();

		if(cp != CP_ACP && file.GetEncoding() == CTextFileBase::ASCII)
		{
			CString s;
			s.Format("You are writing n ASCII-file. Sense you are running a none-Unicode version"
					 " of this program the code-page you have selected will not be used when"
					 " the file is written.\r\n\r\nInstead you could convert the string that be written"
					 " to a a wstring with the system code page (%d). The will this Unicode-string"
					 " be converted with codepage you selected (%d) when the file is written."
					 " Do you want to do this? Otherwise will"
					 " file be written without any conversion.", GetACP(), cp);
			
			int res = MessageBox(s, NULL, MB_YESNO|MB_ICONQUESTION);

			if(res == IDYES)
			{
				wstring temp;
				CTextFileBase::ConvertCharToWstring(m_text, temp);

				file << temp.c_str();
			}
			else
				//Write without converting
				file << m_text;

		}
		else
			file << m_text;
	#endif


	if( file.IsDataLost() )
		MessageBox( _T("Data was lost when the file was written. This means that some Unicode-characters couldn't be converted to multi-byte characters"),
					NULL,
					MB_ICONWARNING|MB_OK);


	TIMEREND(writetime, _T("Time to write: %f.\n"));
}

void CTextfileDemoDlg::OnButtonLoad() 
{
	/* //The follow code converts file to UTF_8
	{
		wstring tot;
		{
			CTextFileRead file(m_filename);

			ASSERT(file.IsOpen());
			file.Read(tot);
		}
		{
			CTextFileWrite file(m_filename, CTextFileWrite::UTF_8);

			ASSERT(file.IsOpen());
			file.Write(tot.c_str());
		}
	}
	*/

	CWaitCursor wc;
	UpdateData();	

	TRACE(_T("Start reading...\n"));
	TIMERSTART(readtime);

	CTextFileRead file(m_filename);
#if PEK_TX_TECHLEVEL > 0
	file.SetCodePage( GetSelectedCodepage() );
#endif

	if(!file.IsOpen())
	{
		MessageBox(_T("Couldn't open file!"), NULL, MB_ICONWARNING|MB_OK);
		return;
	}
	
	TextString tot;

	#ifdef _UNICODE
		file.Read(tot);
	#else
		
		int cp = GetSelectedCodepage();

		if(cp != CP_ACP && file.GetEncoding() == CTextFileBase::ASCII)
		{
			CString s;
			s.Format("This is an ASCII-file. Sense you are running a none-Unicode version"
					 " of this program the code-page you have selected will not be used when"
					 " the file is read.\r\n\r\nInstead you could read the file to a wstring"
					 " (Unicode) with the codepage you selected (%d), and then convert the Unicode"
					 " string to an multibyte string with the system code page (%d). Do you want to do this? Otherwise will"
					 " file be read without any conversion.", cp, GetACP());
			
			int res = MessageBox(s, NULL, MB_YESNO|MB_ICONQUESTION);

			if(res == IDYES)
			{
				//Read the file to a Unicode-string.
				wstring temp;
				file.Read(temp);

				//Convert the Unicode-string to an ordinary string
				string simple;
				CTextFileBase::ConvertWcharToString(temp.c_str(), simple);

				tot = simple.c_str();
			}
			else
				//Read without converting
				file.Read(tot);

		}
		else
			file.Read(tot);
	#endif

	
	TIMEREND(readtime, _T("Time to read: %f.\n"));

	m_format.SetCurSel( file.GetEncoding() );
	TtoC(m_text, tot);

	//Note! If file is large, the edit box may not change data in Win9x
	UpdateData(FALSE);

	OnSelchangeComboFormat();

	if( file.IsDataLost() )
		MessageBox( _T("Data was lost when reading file. This means that some Unicode-characters couldn't be converted to multi-byte characters."),
					NULL,
					MB_ICONWARNING|MB_OK);

	

}

void CTextfileDemoDlg::OnButtonBrowse() 
{	
	UpdateData();
	CFileDialog dlg(TRUE, NULL, m_filename, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, 
						_T("Text Files (*.txt)|*.txt|All Files (*.*)|*.*||"));
		
	if(dlg.DoModal())
	{		
		m_filename = dlg.m_ofn.lpstrFile;
		UpdateData(FALSE);
	}		
}

void CTextfileDemoDlg::OnButtonSelftest() 
{
	UpdateData();
	wchar_t first = (wchar_t)m_first,
			last  = (wchar_t)m_last;

	int count =last-first+1;
	
	if(count < 1)
		return;

	CString mess;
	mess.Format(_T("A string with %d characters will be constructed, and writed to the current file with the current encoding. Then the file will be read an be compared with the constructed string. Current filename will be used. Continue?"), count);

	if(AfxMessageBox(mess, MB_ICONQUESTION|MB_YESNO) != IDYES)
		return;

	CWaitCursor wc;

	wstring out, in;

	for(wchar_t x=first; x<=last; x++)
		//Ignore wrapping
		if(x != 13 && x != 10)
		{
			out += x;
			if(x==last)
				break;
		}

	
	TRACE(_T("Start writing...\n"));
	TIMERSTART(writetime);

	{
		CTextFileWrite outfile(m_filename, (CTextFileWrite::TEXTENCODING) m_format.GetCurSel());
		ASSERT(outfile.IsOpen());

		outfile << out.c_str();
	}

	TIMEREND(writetime, _T("Time to write: %f.\n"));
	
	TRACE(_T("Start reading...\n"));
	TIMERSTART(readtime);
	
	{
		CTextFileRead infile(m_filename);
		ASSERT(infile.IsOpen());
		infile.Read(in);
	}

	TIMEREND(readtime, _T("Time to read: %f.\n"));
	
	if(in == out)
		MessageBox(_T("Match."));
	else
	{
		CString mess;
		mess.Format(_T("Strings doesn't match!\r\n\r\nCharacters written: ")
					_T("%d\nCharacters read: %d"),
							out.size(),
							in.size());

		MessageBox(mess);
	}

}

void CTextfileDemoDlg::OnButtonFulltext() 
{
	CString mess;
	mess.Format(_T("This test will create, write and read strings and test if any data is lost. All encodings will be used, and all possible strings (string, wstring and CString). Continue?"));

	if(AfxMessageBox(mess, MB_ICONQUESTION|MB_YESNO) != IDYES)
		return;

	CWaitCursor wc;

	CString enc[] = { _T("ASCII"), _T("UNI16_BE"), _T("UNI16_LE"), _T("UTF_8") };

	//Test string...
	for(int test=0; test<4; test++)
	{
		string out, in;
		unsigned char first = 32, last = 255;
		
		for(unsigned char x=first; x<=last; x++)
			//Ignore wrapping
			if(x != 13 && x != 10)
			{
				out += x;

				if(x%10 == 0)
				{
					out += "\r\n";
				}

				if(x==last)
					break;
			}

		
		TRACE(_T("Testing: string in %s...\n"), enc[test]);
		TIMERSTART(writetime);

		{
			CTextFileWrite outfile(m_filename, (CTextFileWrite::TEXTENCODING) test); //m_format.GetCurSel());
			ASSERT(outfile.IsOpen());
			outfile << out.c_str();
		}

		TIMEREND(writetime, _T("Time to write: %f.\n"));

		TIMERSTART(readtime);
		
		{
			CTextFileRead infile(m_filename);
			ASSERT(infile.IsOpen());
			infile.Read(in);
		}

		TIMEREND(readtime, _T("Time to read: %f.\n"));
		
		if(in != out)
		{
			CString mess;
			mess.Format(_T("Strings doesn't match!\r\n\r\nCharacters written: ")
						_T("%d\nCharacters read: %d"),
								out.size(),
								in.size());

			MessageBox(mess);
		}	
		else
			TRACE(_T("Test OK!\n\n"));
	}
	

	//Test wstring...
	{
		wstring out, in;

		for(test=0; test<4; test++)
		{
			if(test == 0 || test == 1)
			{
				out = L"";

				wchar_t first = 32, last = 127; //255;

				if(test != 0)
					last = 0xFFFF;

				for(wchar_t x=first; x<=last; x++)
					//Ignore wrapping
					if(x != 13 && x != 10)
					{
						out += x;

						if(x%10 == 0)
						{
							out += L"\r\n";
						}

						if(x==last)
							break;
					}
			}

			
			TRACE(_T("Testing: wstring in %s...\n"), enc[test]);
			TIMERSTART(writetime);

			{
				CTextFileWrite outfile(m_filename, (CTextFileWrite::TEXTENCODING) test); //m_format.GetCurSel());
				ASSERT(outfile.IsOpen());
				outfile << out.c_str();
			}

			TIMEREND(writetime, _T("Time to write: %f.\n"));
			TIMERSTART(readtime);
			
			{
				CTextFileRead infile(m_filename);
				ASSERT(infile.IsOpen());
				infile.Read(in);
			}

			TIMEREND(readtime, _T("Time to read: %f.\n"));
			
			if(in != out)
			{
				for(int i=0; i<(int)out.size(); i++)
				{
					if(in[i] != out[i])
					{
						wchar_t xi = in[i]; //, xo = out[i];
						xi++;

					}
				}
				CString mess;
				mess.Format(_T("Wstrings doesn't match!\r\n\r\nCharacters written: ")
							_T("%d\nCharacters read: %d"),
									out.size(),
									in.size());

				MessageBox(mess);
			}	
			else
				TRACE(_T("Test OK!\n\n"));
		}
	}

	//Test Cstring...
	

	for(test=0; test<4; test++)
	{

		 

#ifdef _UNICODE	
		static CString out, in;
		

		if(test == 0 || test == 1)
		{
			wchar_t first = 32, last = 127; //255;
			
			if(test != 0)
				last = 0xFFFF;

			out = _T("");

			for(wchar_t x=first; x<=last; x++)
				//Ignore wrapping
				if(x != 13 && x != 10)
				{
					out += x;

					if(x%10 == 0)
					{
						out += L"\r\n";
					}

					if(x==last)
						break;
				}
		}
#else
		CString out, in;

		unsigned char first = 32, last = 255;
		
		for(unsigned char x=first; x<=last; x++)
			//Ignore wrapping
			if(x != 13 && x != 10)
			{
				out += x;

				if(x%10 == 0)
				{
					out += "\r\n";
				}

				if(x==last)
					break;
			}
#endif

		TRACE(_T("Testing: CString in %s...\n"), enc[test]);
		TIMERSTART(writetime);

		{
			CTextFileWrite outfile(m_filename, (CTextFileWrite::TEXTENCODING) test); //m_format.GetCurSel());
			ASSERT(outfile.IsOpen());
			outfile << out;
		}

		TIMEREND(writetime, _T("Time to write: %f.\n"));
		TIMERSTART(readtime);
		
		{
			CTextFileRead infile(m_filename);
			ASSERT(infile.IsOpen());

			TextString temp;
			infile.Read(temp);

			TtoC(in, temp);
		}

		TIMEREND(readtime, _T("Time to read: %f.\n"));
		
		if(in != out)
		{
			CString mess;
			mess.Format(_T("CStrings doesn't match!\r\n\r\nCharacters written: ")
						_T("%d\nCharacters read: %d"),
								out.GetLength(),
								in.GetLength());

			MessageBox(mess);
		}	
		else
			TRACE(_T("Test OK!\n\n"));
	}

	
}


void CTextfileDemoDlg::OnSelchangeComboFormat() 
{
	m_codepage.EnableWindow( m_format.GetCurSel() == 0 ? TRUE : FALSE );	
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under Microsoft Reciprocal License


Written By
PEK
Sweden Sweden
PEK is one of the millions of programmers that sometimes program so hard that he forgets how to sleep (this is especially true when he has more important things to do). He thinks that there are not enough donuts in the world. He likes when his programs works as they should do, but dislikes when his programs is more clever than he is.

Comments and Discussions