/*
* Copyright (c) 1997-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
#pragma warning (disable: 4514) // unreferenced inline/local function has been removed
#include <STDIO.H>
#include <STDLIB.H>
#include <STRING.H>
#include <LOCALE.H>
#include <WCHAR.H>
#if defined(__VC32__)
#include <FCNTL.H>
#include <IO.H>
#endif
#undef BIG_ENDIAN
#if (defined(__MSVCRT__) || defined(_MSC_VER))
//#define _stricmp _stricmp
//#define _strnicmp _strnicmp
#else // linux
#define _stricmp strcasecmp
#define _strnicmp strncasecmp
#endif
const int KVersionNumber=025;
const int KLargeNumber=1000000;
extern int Utf8ToUnicode(wchar_t* aUnicode, const char* aUtf8);
extern int UnicodeToUtf8(char* aUtf8, const wchar_t* aUnicode);
enum TByteOrder
{
EByteOrderUnspecified,
EByteOrderBigEndian,
EByteOrderLittleEndian,
#if defined(BIG_ENDIAN)
EByteOrderNative=EByteOrderBigEndian,
EByteOrderForeign=EByteOrderLittleEndian
#else
EByteOrderNative=EByteOrderLittleEndian,
EByteOrderForeign=EByteOrderBigEndian
#endif
};
struct SBuffer
{
int iNumberOfBytes;
void* iData;
};
void PrintUsage(const char* aProgramName)
{
fprintf(stderr, "\nVersion %03d\n\nCharacter set conversion tool\nCopyright (c) 1999 Symbian Ltd\n\n", KVersionNumber);
fprintf(stderr, "Usage:\n\n\t%s [<options>] <inputspec> <outputspec>\n\nwhere\n\n\t"
"options := [-big|-little][-byteordermark]\n\t"
"inputspec := -input=<format> [<input_file>]\n\t"
"outputspec := -output=<format> [<output_file>]\n\t"
"format := unicode|1252|utf8|...\n\n", aProgramName);
const char* localeData=setlocale(LC_ALL, "");
while (*localeData!='.')
{
++localeData;
}
fprintf(stderr, "(The default encoding is currently \"%s\")\n\n", localeData+1);
}
void Assert(int aCondition, const char* aErrorMessageFormat, const void* aExtraParameter1=NULL, const void* aExtraParameter2=NULL)
{
if (!aCondition)
{
char errorMessage[100];
sprintf(errorMessage, aErrorMessageFormat, aExtraParameter1, aExtraParameter2);
fprintf(stderr, "Error: %s\n", errorMessage);
exit(1);
}
}
void PrintWarning(const char* aWarningMessage)
{
fprintf(stderr, "Warning: %s\n", aWarningMessage);
}
int TryFileParameter(int aArgc, char* aArgv[], int& aArgIndex, const char* aInputOrOutput, const char*& aEncoding, FILE*& aFile, const char* aFileMode)
{
char prefix[100];
strcpy(prefix, "-");
strcat(prefix, aInputOrOutput);
strcat(prefix, "=");
int lengthOfPrefix=strlen(prefix);
if (_strnicmp(aArgv[aArgIndex], prefix, lengthOfPrefix)==0)
{
Assert(aEncoding==NULL, "\"%s...\" is specified more than once", prefix);
aEncoding=aArgv[aArgIndex]+lengthOfPrefix;
++aArgIndex;
if ((aArgIndex>=aArgc) || (aArgv[aArgIndex][0]=='-'))
{
--aArgIndex;
}
else
{
aFile=fopen(aArgv[aArgIndex], aFileMode);
Assert(aFile!=NULL, "opening %s-file failed", aInputOrOutput);
}
return 1;
}
return 0;
}
void ReadParameters(int aArgc, char* aArgv[], int& aOutputByteOrderMark, TByteOrder& aUnicodeByteOrder, const char*& aInputEncoding, const char*& aOutputEncoding, FILE*& aInputFile, FILE*& aOutputFile)
{
if ((aArgc<=1) || (_stricmp(aArgv[1], "?")==0) || (_stricmp(aArgv[1], "/?")==0))
{
PrintUsage(aArgv[0]);
exit(0);
}
for (int i=1; i<aArgc; ++i) // start at index 1 to avoid the program name (which is the first parameter)
{
if (_stricmp(aArgv[i], "-byteordermark")==0)
{
Assert(!aOutputByteOrderMark, "\"-byteordermark\" is specified more than once");
aOutputByteOrderMark=1;
}
else if (_stricmp(aArgv[i], "-big")==0)
{
Assert(aUnicodeByteOrder==EByteOrderUnspecified, "the byte order of unicode text (i.e. \"-big\"/\"-little\") is specified more than once");
aUnicodeByteOrder=EByteOrderBigEndian;
}
else if (_stricmp(aArgv[i], "-little")==0)
{
Assert(aUnicodeByteOrder==EByteOrderUnspecified, "the byte order of unicode text (i.e. \"-big\"/\"-little\") is specified more than once");
aUnicodeByteOrder=EByteOrderLittleEndian;
}
else
{
Assert(TryFileParameter(aArgc, aArgv, i, "input", aInputEncoding, aInputFile, "r") ||
TryFileParameter(aArgc, aArgv, i, "output", aOutputEncoding, aOutputFile, "w"), "bad parameter \"%s\"", aArgv[i]);
}
}
Assert(aInputEncoding!=NULL, "no input encoding is specified");
Assert(aOutputEncoding!=NULL, "no output encoding is specified");
}
int ReadFromFileReturningNumberOfBytesRead(void* aBuffer, int aNumberOfBytesToRead, FILE* aInputFile)
{
int numberOfBytesRead=0;
int numberOfBytesToReadThisTime=aNumberOfBytesToRead;
for (;;)
{
for (;;)
{
const int remainingNumberOfBytesToRead=aNumberOfBytesToRead-numberOfBytesRead;
if (numberOfBytesToReadThisTime>remainingNumberOfBytesToRead)
{
numberOfBytesToReadThisTime=remainingNumberOfBytesToRead;
}
const int numberOfBytesReadThisTime=fread(aBuffer, 1, numberOfBytesToReadThisTime, aInputFile);
const int error=ferror(aInputFile);
if (error==0)
{
aBuffer=((unsigned char*)aBuffer)+numberOfBytesReadThisTime;
numberOfBytesRead+=numberOfBytesReadThisTime;
Assert(numberOfBytesRead<=aNumberOfBytesToRead, "internal error (read too many bytes)");
if ((numberOfBytesRead>=aNumberOfBytesToRead) || feof(aInputFile))
{
return numberOfBytesRead;
}
break;
}
numberOfBytesToReadThisTime/=2;
Assert(numberOfBytesToReadThisTime>0, "reading from file failed with error number %d", (const void*)error);
clearerr(aInputFile);
}
}
}
void WriteToFile(const void* aBuffer, int aNumberOfBytesToWrite, FILE* aOutputFile)
{
const int numberOfBytesWritten=fwrite(aBuffer, 1, aNumberOfBytesToWrite, aOutputFile);
Assert(numberOfBytesWritten==aNumberOfBytesToWrite, "only %d out of %d bytes could be written to file", (const void*)numberOfBytesWritten, (const void*)aNumberOfBytesToWrite);
const int error=ferror(aOutputFile);
Assert(error==0, "writing to file failed with error number %d", (const void*)error);
}
void HandleByteOrderMarks(int aOutputByteOrderMark, TByteOrder& aUnicodeByteOrder, const char* aInputEncoding, const char* aOutputEncoding, FILE* aInputFile, FILE* aOutputFile)
{
if (_stricmp(aInputEncoding, "unicode")==0)
{
unsigned short firstUnicodeCharacter=0;
const int numberOfBytesRead=ReadFromFileReturningNumberOfBytesRead((void*)&firstUnicodeCharacter, sizeof(unsigned short), aInputFile);
TByteOrder byteOrderSpecifiedByByteOrderMark=EByteOrderUnspecified;
if (numberOfBytesRead==sizeof(unsigned short))
{
switch (firstUnicodeCharacter)
{
case 0xfeff:
byteOrderSpecifiedByByteOrderMark=EByteOrderNative;
break;
case 0xfffe:
byteOrderSpecifiedByByteOrderMark=EByteOrderForeign;
break;
default:
const int error=fseek(aInputFile, 0, SEEK_SET); // rewind to the start of the file
Assert(error==0, "could not rewind to the start of the input file");
break;
}
}
if (byteOrderSpecifiedByByteOrderMark!=EByteOrderUnspecified)
{
if ((aUnicodeByteOrder!=EByteOrderUnspecified) && (byteOrderSpecifiedByByteOrderMark!=aUnicodeByteOrder))
{
PrintWarning("the byte order specified by the byte-order mark in the unicode input is different from the byte order specified by the parameter - taking the byte-order specified by the byte-order mark in the unicode input");
}
aUnicodeByteOrder=byteOrderSpecifiedByByteOrderMark;
}
}
if (aOutputByteOrderMark)
{
if (_stricmp(aOutputEncoding, "unicode")!=0)
{
PrintWarning("\"-byteordermark\" is only relevant for unicode output");
}
else
{
Assert(aUnicodeByteOrder!=EByteOrderUnspecified, "the byte order must be specified if a byte-order mark is to be added to the unicode output");
unsigned short firstUnicodeCharacter=(unsigned short)((aUnicodeByteOrder==EByteOrderNative)? 0xfeff: 0xfffe);
WriteToFile((const void*)&firstUnicodeCharacter, sizeof(unsigned short), aOutputFile);
}
}
}
void ObeyRequiredByteOrderIfUnicode(TByteOrder& aUnicodeByteOrder, const char* aEncoding, SBuffer& aBuffer)
{
if (_stricmp(aEncoding, "unicode")==0)
{
Assert(aBuffer.iNumberOfBytes%sizeof(wchar_t)==0, "internal error (bad number of bytes in unicode buffer)");
if (aUnicodeByteOrder==EByteOrderUnspecified)
{
PrintWarning("the byte order of unicode text is unspecified - defaulting to little endian");
aUnicodeByteOrder=EByteOrderLittleEndian;
}
if (aUnicodeByteOrder==EByteOrderForeign)
{
for (unsigned char* bytePointer=((unsigned char*)aBuffer.iData)+(aBuffer.iNumberOfBytes-sizeof(wchar_t)); bytePointer>=aBuffer.iData; bytePointer-=sizeof(wchar_t))
{
unsigned char temp=*bytePointer;
*bytePointer=*(bytePointer+1);
*(bytePointer+1)=temp;
}
}
}
}
int OtherToUnicode(const char* aInputEncoding, wchar_t* aUnicode, const char* aOther)
// if the output parameter is NULL, it returns the precise size of the would-be output parameter (in terms of number of "wchar_t"s) excluding any trailing '\0', otherwise it returns 0
{
if (_stricmp(aInputEncoding, "utf8")==0)
{
return Utf8ToUnicode(aUnicode, aOther);
}
char localeData[100];
strcpy(localeData, ".");
strcat(localeData, aInputEncoding);
Assert(setlocale(LC_ALL, localeData)!=NULL, "could not convert from encoding \"%s\"", aInputEncoding);
return mbstowcs(aUnicode, aOther, KLargeNumber);
}
int UnicodeToOther(const char* aOutputEncoding, char* aOther, const wchar_t* aUnicode)
// if the output parameter is NULL, it returns the precise size of the would-be output parameter (in terms of number of "char"s) excluding any trailing '\0', otherwise it returns 0
{
if (_stricmp(aOutputEncoding, "utf8")==0)
{
return UnicodeToUtf8(aOther, aUnicode);
}
char localeData[100];
strcpy(localeData, ".");
strcat(localeData, aOutputEncoding);
Assert(setlocale(LC_ALL, localeData)!=NULL, "could not convert to encoding \"%s\"", aOutputEncoding);
return wcstombs(aOther, aUnicode, KLargeNumber);
}
void DoConversion(TByteOrder& aUnicodeByteOrder, const char* aInputEncoding, const char* aOutputEncoding, FILE* aInputFile, FILE* aOutputFile)
{
SBuffer arrayOfBuffers[3];
arrayOfBuffers[0].iNumberOfBytes=0;
arrayOfBuffers[0].iData=malloc(KLargeNumber+2); // +2 for the 2 '\0' bytes appended to the data read from file
Assert(arrayOfBuffers[0].iData!=NULL, "cannot allocate enough memory");
arrayOfBuffers[1].iNumberOfBytes=0;
arrayOfBuffers[1].iData=NULL;
arrayOfBuffers[2].iNumberOfBytes=0;
arrayOfBuffers[2].iData=NULL;
SBuffer* currentBuffer=arrayOfBuffers;
currentBuffer->iNumberOfBytes=ReadFromFileReturningNumberOfBytesRead(currentBuffer->iData, KLargeNumber, aInputFile);
// append 2 '\0' bytes at the end of the buffer read from file (2 in case it is unicode)
((char*)currentBuffer->iData)[currentBuffer->iNumberOfBytes]='\0';
((char*)currentBuffer->iData)[currentBuffer->iNumberOfBytes+1]='\0';
ObeyRequiredByteOrderIfUnicode(aUnicodeByteOrder, aInputEncoding, *currentBuffer);
// if the input and output encodings are different, convert from one to the other (via unicode if neither is itself unicode)
if (_stricmp(aInputEncoding, aOutputEncoding)!=0)
{
if (_stricmp(aInputEncoding, "unicode")!=0)
{
SBuffer* nextBuffer=currentBuffer+1;
nextBuffer->iNumberOfBytes=sizeof(wchar_t)*OtherToUnicode(aInputEncoding, NULL, (const char*)currentBuffer->iData);
Assert(nextBuffer->iNumberOfBytes>=0, "invalid multi-byte character encountered");
nextBuffer->iData=malloc(nextBuffer->iNumberOfBytes+sizeof(wchar_t)); // "+sizeof(wchar_t)" for terminating '\0'
Assert(nextBuffer->iData!=NULL, "cannot allocate enough memory");
OtherToUnicode(aInputEncoding, (wchar_t*)nextBuffer->iData, (const char*)currentBuffer->iData);
currentBuffer=nextBuffer;
}
if (_stricmp(aOutputEncoding, "unicode")!=0)
{
SBuffer* nextBuffer=currentBuffer+1;
nextBuffer->iNumberOfBytes=sizeof(char)*UnicodeToOther(aOutputEncoding, NULL, (const wchar_t*)currentBuffer->iData);
Assert(nextBuffer->iNumberOfBytes>=0, "unconvertible unicode character encountered");
nextBuffer->iData=malloc(nextBuffer->iNumberOfBytes+sizeof(char)); // "+sizeof(char)" for terminating '\0'
Assert(nextBuffer->iData!=NULL, "cannot allocate enough memory");
UnicodeToOther(aOutputEncoding, (char*)nextBuffer->iData, (const wchar_t*)currentBuffer->iData);
currentBuffer=nextBuffer;
}
}
ObeyRequiredByteOrderIfUnicode(aUnicodeByteOrder, aOutputEncoding, *currentBuffer);
WriteToFile((const void*)currentBuffer->iData, currentBuffer->iNumberOfBytes, aOutputFile);
free(arrayOfBuffers[0].iData);
free(arrayOfBuffers[1].iData);
free(arrayOfBuffers[2].iData);
}
void FlushAndCloseFiles(FILE* aInputFile, FILE* aOutputFile)
{
Assert(fflush(aOutputFile)==0, "flushing output-file failed");
if (aInputFile!=stdin)
{
Assert(fclose(aInputFile)==0, "closing input-file failed");
}
if (aOutputFile!=stdout)
{
Assert(fclose(aOutputFile)==0, "closing output-file failed");
}
}
int main(int aArgc, char* aArgv[])
{
int outputByteOrderMark=0;
TByteOrder unicodeByteOrder=EByteOrderUnspecified;
const char* inputEncoding=NULL;
const char* outputEncoding=NULL;
FILE* inputFile=stdin;
FILE* outputFile=stdout;
ReadParameters(aArgc, aArgv, outputByteOrderMark, unicodeByteOrder, inputEncoding, outputEncoding, inputFile, outputFile);
#if defined(__VC32__)
_setmode(_fileno(inputFile), _O_BINARY);
_setmode(_fileno(outputFile), _O_BINARY);
#endif
HandleByteOrderMarks(outputByteOrderMark, unicodeByteOrder, inputEncoding, outputEncoding, inputFile, outputFile);
DoConversion(unicodeByteOrder, inputEncoding, outputEncoding, inputFile, outputFile);
FlushAndCloseFiles(inputFile, outputFile);
return 0;
}