Click here to Skip to main content
15,885,025 members
Articles / Desktop Programming / MFC

RC Localization Tool (LocalizeRC)

Rate me:
Please Sign up or sign in to vote.
4.89/5 (53 votes)
14 Jan 2004BSD9 min read 487.4K   9.5K   159  
A tool for localizing/translating Resource Scripts
// IniEx.cpp: implementation of the CIniEx class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "IniEx.h"
#include "malloc.h"

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

// UNICODE File Reader
////////////////////////////////

CStdioUnicodeFile::CStdioUnicodeFile():
	CStdioFile()
{
}

CStdioUnicodeFile::CStdioUnicodeFile(LPCTSTR lpszFileName, UINT nOpenFlags):
	CStdioFile(lpszFileName, nOpenFlags)
{
}

// static method to check if file is unicode
BOOL CStdioUnicodeFile::IsUnicode(LPCTSTR lpszFileName)
{
	CStdioUnicodeFile File;
	if( !File.Open( lpszFileName, CFile::modeRead | CFile::typeBinary ) )
		return false;
	// check first two bytes (BOT)
	BYTE fffe[2] = {0, 0};
	if (2 == File.Read(&fffe, 2)) 
	{
		if (fffe[0] == 0xFF || fffe[1] == 0xFE)
		{
			File.Close();
			return true;
		}
	}
	File.Close();
	return false;
}

BOOL CStdioUnicodeFile::ReadString(CString& rString)
{
	ASSERT_VALID(this);
	rString = _T("");    // empty string without deallocating
	const int nMaxSize = 128;
	LPTSTR lpsz = rString.GetBuffer(nMaxSize);
	LPTSTR lpszResult;
	int nLen = 0;
	for (;;)
	{
		lpszResult = _fgetts(lpsz, nMaxSize+1, m_pStream);
		rString.ReleaseBuffer();
		// handle error/eof case
		if (lpszResult == NULL && !feof(m_pStream))
		{
			clearerr(m_pStream);
			AfxThrowFileException(CFileException::generic, _doserrno,
				m_strFileName);
		}
		// if string is read completely or EOF
		if (lpszResult == NULL ||
			(nLen = lstrlen(lpsz)) < nMaxSize ||
			lpsz[nLen-1] == '\n')
			break;
		nLen = rString.GetLength();
		lpsz = rString.GetBuffer(nMaxSize + nLen) + nLen;
	}
	// remove '\n' from end of string if present
	lpsz = rString.GetBuffer(0);
	nLen = rString.GetLength();
	if (nLen != 0 && lpsz[nLen-1] == '\n') {
		rString.GetBufferSetLength(nLen-1);
		nLen = rString.GetLength();
		if (nLen != 0 && lpsz[nLen-1] == '\r')
			rString.GetBufferSetLength(nLen-1);
	}
	return lpszResult != NULL;
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

//GrowSize for Dynmiz Section Allocation
CIniEx::CIniEx(int GrowSize/*=4*/)
{
	m_GrowSize=GrowSize;

	m_SectionNo=0;
	m_writeWhenChange=FALSE;
	m_makeBackup=FALSE;
	m_NoCaseSensitive=TRUE;
	m_Changed=FALSE;
	m_Keys=NULL;
	m_Values=NULL;
	m_allocatedObjectCount=0;
}

CIniEx::~CIniEx()
{
	if (m_writeWhenChange)
		WriteFile(m_makeBackup);
	ResetContent();
}

BOOL CIniEx::OpenAtExeDirectory(LPCTSTR pFileName,
							BOOL writeWhenChange,/*=TRUE*/
							BOOL createIfNotExist/*=TRUE*/,
							BOOL noCaseSensitive /*=TRUE*/,
							BOOL makeBackup      /*=FALSE*/)
{

	CString filePath;
//if it's a dll argv will be NULL and it may cause memory leak	
#ifndef _USRDLL
	CString tmpFilePath;
	int nPlace=0;
	tmpFilePath=__argv[0];
	nPlace=tmpFilePath.ReverseFind('\\');
	
	
	if (nPlace!=-1)
	{
		filePath=tmpFilePath.Left(nPlace);
	}
	else
	{
		TCHAR curDir[MAX_PATH];
		GetCurrentDirectory(MAX_PATH,curDir);
		filePath=curDir;
	}
#else
	//it must be safe for dll's
	TCHAR curDir[MAX_PATH];
	GetCurrentDirectory(MAX_PATH,curDir);
	filePath=curDir;
#endif
	filePath+="\\";
	filePath+=pFileName;
	return Open(filePath,writeWhenChange,createIfNotExist,noCaseSensitive,makeBackup);
}

BOOL CIniEx::Open(LPCTSTR pFileName,
				  BOOL writeWhenChange,/*=FALSE*/
				  BOOL createIfNotExist/*=TRUE*/,
				  BOOL noCaseSensitive /*=FALSE*/,
				  BOOL makeBackup      /*=FALSE*/)
{

	CFileException e;
	BOOL bRet;
	CString Line;
	CString sectionStr;
	int nPlace;
	UINT mode=CFile::modeReadWrite;

	//if it's second ini file for this instance
	//we have to save it and delete member variables contents
	if (!m_FileName.IsEmpty()) 
	{
		if( m_writeWhenChange )
			WriteFile();
		ResetContent();
	}

	m_NoCaseSensitive=noCaseSensitive;
	m_writeWhenChange=writeWhenChange;
	m_makeBackup=makeBackup;

	CString tmpKey;
	CString tmpValue;
	if (createIfNotExist)
		mode= mode | CFile::modeCreate | CFile::modeNoTruncate;

	BOOL bIsUnicode = CStdioUnicodeFile::IsUnicode( pFileName );

	try
	{
		m_FileName=pFileName;

		//Grow the arrays as GrowSize(which given constructor)
		m_allocatedObjectCount = m_GrowSize;
		m_Keys=(CStringArray **)malloc( m_allocatedObjectCount * sizeof(CStringArray *) );
		m_Values=(CStringArray **)malloc( m_allocatedObjectCount * sizeof(CStringArray *) );
		for (int i=0;i<m_GrowSize;i++)
		{
			m_Keys[m_SectionNo+i]=NULL;
			m_Values[m_SectionNo+i]=NULL;
		}		

		CStdioUnicodeFile file;

		#ifdef UNICODE	
			if( bIsUnicode )
			{	
				if( !file.Open( pFileName, mode | CFILEFLAG_UNICODEHELPER, &e ) )
				{
					return false;
				}
				// skip first two bytes
				file.Seek( 2, CFile::begin );
			}
			else
			{
				if( !file.Open( pFileName, mode, &e ) )
					return false;
			}
		#else
			if( bIsUnicode )
			{
				CString strErr;
				strErr.Format( IDS_UNICODEFILE, pFileName );
				AfxMessageBox( strErr );
				return false;	// cancel
			}
			else
			{
				if( !file.Open( pFileName, mode, &e ) )
					return false;
			}
		#endif

		for(;;)
		{
			//Read one line from given ini file
			bRet=file.ReadString(Line);
			if (!bRet) 
				break;
			Line.TrimRight();
			if (Line=="") 
				continue;
					
			//if line's first character = '[' 
			//and last character = ']' it's section 
			if (Line.Left(1)=="[" && Line.Right(1)=="]")
			{
				m_Keys[m_SectionNo]=new CStringArray;
				m_Values[m_SectionNo]=new CStringArray;
				m_SectionNo++;		
				GrowIfNecessary();
				
				sectionStr=Line.Mid(1,Line.GetLength()-2);
				m_Sections.SetAtGrow(m_SectionNo-1,sectionStr);
				continue;
			}
			
			nPlace=Line.Find(_T("="));
			if (nPlace==-1)
			{
				tmpKey=Line;
				tmpValue="";
			}
			else
			{
				tmpKey=Line.Left(nPlace);
				tmpValue=Line.Mid(nPlace+1);
			}

			// create Section of no one exists in file
			if( m_SectionNo == 0 )
			{	
				m_Keys[m_SectionNo]=new CStringArray;
				m_Values[m_SectionNo]=new CStringArray;
				m_SectionNo++;
			}
			m_Keys[m_SectionNo-1]->Add(tmpKey);
			m_Values[m_SectionNo-1]->Add(tmpValue);
			m_Sections.SetAtGrow(m_SectionNo-1,sectionStr);
		}
		file.Close();
	}
	catch (CFileException *e)
	{
		m_ErrStr.Format( _T("%d"), e->m_cause );
	}
	

	return TRUE;
}

CString CIniEx::GetValue(CString Key)
{
	return GetValue(_T(""),Key);
}

//if Section Name="" -> looking up key for witout section
CString CIniEx::GetValue(CString Section,CString Key,CString DefaultValue/*=""*/)
{
	int nIndex=LookupSection(&Section);
	if (nIndex==-1) return DefaultValue;
	int nRet;
	CString retStr;
	for (INT_PTR i=m_Keys[nIndex]->GetUpperBound();i>=0;i--)
	{
		nRet=CompareStrings(&(m_Keys[nIndex]->GetAt(i)),&Key);
		if (nRet==0)
		{
			retStr=m_Values[nIndex]->GetAt(i);
			/*int nPlace=retStr.ReverseFind(';');
			if (nPlace!=-1) 
				retStr.Delete(nPlace,retStr.GetLength()-nPlace);*/
			return retStr;
		}
	}
	return DefaultValue;
}



//returns index of key for given section
//if no result returns -1
int CIniEx::LookupKey(int nSectionIndex,CString *Key)
{
	ASSERT(nSectionIndex<=m_SectionNo);
	int nRet;
	for (INT_PTR i=m_Keys[nSectionIndex]->GetUpperBound();i>=0;i--)
	{
		nRet=CompareStrings(&m_Keys[nSectionIndex]->GetAt(i),Key);
		if (nRet==0) 
			return (int)i;
	}
	return -1;
}

//return given sections index in array
int CIniEx::LookupSection(CString *Section)
{
	int nRet;
	for (int i=0;i<m_Sections.GetSize();i++)
	{
		nRet=CompareStrings(&m_Sections.GetAt(i),Section);
		if (nRet==0) return i;
	}
	return -1;
}

//Sets for Key=Value for without section
void CIniEx::SetValue(CString Key,CString Value)
{
	SetValue(_T(""),Key,Value);
}

//writes Key=value given section
void CIniEx::SetValue(CString Section,CString Key,CString Value)
{
	//file opened?
	ASSERT(!m_FileName.IsEmpty());

	//if given key already existing, overwrite it
	int nIndex=LookupSection(&Section);
	int nKeyIndex;
	if (nIndex==-1)
	{
		//if key not exist grow arrays (if necessary)
		m_Changed=TRUE;
		m_SectionNo++;
		GrowIfNecessary();
		m_Keys[m_SectionNo-1]=new CStringArray;
		m_Values[m_SectionNo-1]=new CStringArray;
		nIndex=m_SectionNo-1;
		m_Sections.SetAtGrow(m_SectionNo-1,Section);
	}

	
	//looking up keys for section
	nKeyIndex=LookupKey(nIndex,&Key);
	
	//if key exist -> overwrite it
	//if not add to end of array
	if (nKeyIndex!=-1) 
	{
		if (CompareStrings(&m_Values[nIndex]->GetAt(nKeyIndex),&Value)!=0)
			m_Changed=TRUE;
		m_Values[nIndex]->SetAt(nKeyIndex,Value);
	}
	else	//if not exist
	{
		m_Changed=TRUE;
		m_Keys[nIndex]->Add(Key);
		m_Values[nIndex]->Add(Value);
	}
}


//returns backup file name
//if you didn't want backup (when openning file) it returns ""
CString CIniEx::WriteFile(BOOL makeBackup/*=FALSE*/)
{
	if (!m_Changed) 
		return _T("");
	CString tmpFileName=m_FileName;

	if (makeBackup)
	{
		if (m_BackupFileName.IsEmpty())
		{
			FindBackupFile();
		}
		CopyFile(m_FileName,m_BackupFileName,FALSE);
	}

	
	CStdioUnicodeFile file;
	if (!file.Open(m_FileName, CFILEFLAG_UNICODEHELPER | CFile::modeCreate | CFile::modeWrite)) 
	{
		#ifdef _DEBUG
			afxDump << "ERROR!!!!: The file could not open for writing\n";
		#endif
		return _T("");
	}

	CString strNewline;

#ifdef UNICODE
	// write 0xFF 0xFE
	BYTE fffe[2] = { 0xFF, 0xFE };
	file.Write(fffe, 2);
	strNewline = "\r\n";
#else
	strNewline = "\n";
#endif

	CString tmpLine;
	for (int i=0;i<m_Sections.GetSize();i++)
	{
		if (!m_Sections.GetAt(i).IsEmpty())
		{
			tmpLine.Format(_T("[%s]%s"),m_Sections.GetAt(i), strNewline );
			file.WriteString(tmpLine);
		}
		if (!m_Keys[i]) continue;
		for (int j=0;j<=m_Keys[i]->GetUpperBound();j++)
		{
			//if key is empts we don't write "="
			tmpLine.Format(_T("%s%s%s%s"),m_Keys[i]->GetAt(j),
							m_Keys[i]->GetAt(j).IsEmpty()?"":"=",
							   m_Values[i]->GetAt(j), strNewline );
		
			file.WriteString(tmpLine);
 
		}
	}
	file.Close();
	return m_BackupFileName;
}

BOOL CIniEx::GetWriteWhenChange(void)
{
	return m_writeWhenChange;
}


void CIniEx::SetWriteWhenChange(BOOL WriteWhenChange)
{
	m_writeWhenChange=WriteWhenChange;
}


void CIniEx::SetBackupFileName(CString &backupFile)
{
	m_BackupFileName=backupFile;
}


void CIniEx::FindBackupFile(void)
{
	WIN32_FIND_DATA ffData;
	BOOL bContinue=TRUE;
	CString filePath(m_FileName);
	CString ext;
	int nPlace=filePath.ReverseFind('.');
	filePath.Delete(nPlace,filePath.GetLength()-nPlace);
	filePath+="*.*";
	int extNo=0;
	LPTSTR p;
	HANDLE handle=FindFirstFile(filePath,&ffData);
	while (bContinue)
	{
		bContinue=FindNextFile(handle,&ffData);
		p=ffData.cFileName;
		p+=_tcslen(ffData.cFileName)-3;
		if (_ttoi(p)>extNo) extNo=_ttoi(p);
	}
	m_BackupFileName.Format(_T("%s.%.3d"),m_FileName,extNo+1);

}


void CIniEx::ResetContent()
{
	if (!m_Keys) return;
	if ( m_Keys)
	{
		for (int i=0;i<m_allocatedObjectCount;i++)
		{
			if (m_Keys[i])
				delete m_Keys[i];
			if (m_Values[i])
				delete m_Values[i];
		}
		free(m_Keys);
		free(m_Values);
	}
	m_Keys=NULL;
	m_Values=NULL;
	m_Sections.RemoveAll();
	m_SectionNo=0;
	m_FileName="";
	m_Changed=FALSE;

}


//Removes key and it's value from given section
BOOL CIniEx::RemoveKey(CString Section,CString Key)
{
	int nIndex=LookupSection(&Section);
	if (nIndex==-1) return FALSE;

	int nKeyIndex=LookupKey(nIndex,&Key);
	if (nKeyIndex==-1) return FALSE;

	m_Keys[nIndex]->RemoveAt(nKeyIndex);
	m_Values[nIndex]->RemoveAt(nKeyIndex);
	m_Changed=TRUE;
	return TRUE;
}

//Removes key and it's value 
BOOL CIniEx::RemoveKey(CString Key)
{
	return RemoveKey(_T(""),Key);
}


//Removes given section(including all keys and values)
//return FALSE when given section not found
//It won't couse memory leak because when deleting object
//the msize (malloc size) checking
BOOL CIniEx::RemoveSection(CString Section)
{
	int nIndex=LookupSection(&Section);
	if (nIndex==-1) return FALSE;

	m_Keys[nIndex]->RemoveAll();
	m_Values[nIndex]->RemoveAll();

	delete m_Keys[nIndex];
	delete m_Values[nIndex];

	m_Keys[nIndex]=NULL;
	m_Values[nIndex]=NULL;

	m_Sections.RemoveAt(nIndex);
	m_SectionNo--;
	m_Changed=TRUE;
	return TRUE;
}

int CIniEx::CompareStrings(const CString *str1, CString *str2)
{
	if (m_NoCaseSensitive)
		return str1->CompareNoCase(*str2);
	else
		return str1->Compare(*str2);
}

void CIniEx::GrowIfNecessary(void)
{
	//for first gives GrowSize
	if (m_SectionNo>=m_allocatedObjectCount)
	{
		//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//realloc for GrowSize
		m_allocatedObjectCount += m_GrowSize;
		m_Keys=(CStringArray **)realloc(m_Keys,sizeof(CStringArray*) * (m_allocatedObjectCount) );
		m_Values=(CStringArray **)realloc(m_Values,sizeof(CStringArray*) * (m_allocatedObjectCount) );
		//allocated + GrowSize
		//zero memory for new allocation
		for (int i=0;i<m_GrowSize;i++)
		{
			m_Keys[m_SectionNo+i]=NULL;
			m_Values[m_SectionNo+i]=NULL;
		}
	}
}

//copy each string (section name) because 
//if sections parametter be a pointer it may clear content of member
void CIniEx::GetSections(CStringArray &sections)
{
	for (int i=0;i<m_Sections.GetSize();i++)
		sections.Add(m_Sections.GetAt(i));

}

void CIniEx::GetKeysInSection(CString section,CStringArray &keys)
{
	int nIndex=LookupSection(&section);
	if (nIndex==-1) return;

	for (int i=0;i<m_Keys[nIndex]->GetSize();i++)
	{
		keys.Add(m_Keys[nIndex]->GetAt(i));
	}
}

void CIniEx::SortIniValues()
{
	for (int i=0;i<m_Sections.GetSize();i++)
	{
		if (!m_Keys[i]) 
			continue;

		// Quicksort
		QuickSortRecursive( i, 0, m_Keys[i]->GetUpperBound(), true );
	}
}

int CIniEx::CompareItems( CString str1, CString str2 )
{
	return str1.CompareNoCase(str2);
}

BOOL CIniEx::Swap( int nSection, int nLeftIndex, int nRightIndex )
{
	CString strHelp = m_Keys[nSection]->GetAt(nLeftIndex);
	m_Keys[nSection]->SetAt(nLeftIndex, m_Keys[nSection]->GetAt(nRightIndex) );
	m_Keys[nSection]->SetAt(nRightIndex, strHelp );

	strHelp = m_Values[nSection]->GetAt(nLeftIndex);
	m_Values[nSection]->SetAt(nLeftIndex, m_Values[nSection]->GetAt(nRightIndex));
	m_Values[nSection]->SetAt(nRightIndex, strHelp);

	return true;
}

void CIniEx::QuickSortRecursive(int nSection, int iLow, int iHigh, BOOL bAscending)
{
	// Params renamed for easier comparison with literature
	int iLeft = iLow;
	int iRight = iHigh;

	// Important: Save Pivot-Element on Stack, instead of using GetAt(iPivot) in 
	// "while('compare')-Loop". Original implementation by Attila Hajdrik used 
	// GetAt(iPivot) in within the Loop
	// int iPivot = (iLow+iHigh) / 2;
	CString Pivot = m_Keys[nSection]->GetAt((iLow+iHigh) / 2); 

	do
	{
		if( bAscending )
		{
			while( CompareItems(m_Keys[nSection]->GetAt(iLeft), Pivot) < 0 ) iLeft++;
			while( CompareItems(Pivot, m_Keys[nSection]->GetAt(iRight)) < 0 ) iRight--;
		}
		else
		{
			while( CompareItems(m_Keys[nSection]->GetAt(iLeft), Pivot) > 0 ) iLeft++;
			while( CompareItems(Pivot, m_Keys[nSection]->GetAt(iRight)) > 0 ) iRight--;
		}

		if( iLeft <= iRight )
		{
			if( iLeft != iRight ) // Ignore unnecessary swaps
				Swap(nSection, iLeft, iRight);

			iLeft++;
			iRight--;
		}
	}
	while( iLeft <= iRight );

	if( iLow < iRight )
		QuickSortRecursive(nSection, iLow, iRight, bAscending);

	if( iLeft < iHigh )
		QuickSortRecursive(nSection, iLeft, iHigh, bAscending);
}

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 The BSD License


Written By
Web Developer
Germany Germany
Author of the shareware WinCD.

Comments and Discussions