mirror of
git://git.savannah.nongnu.org/eliot.git
synced 2024-12-25 21:59:30 +01:00
483 lines
14 KiB
C++
483 lines
14 KiB
C++
/*****************************************************************************
|
|
* Eliot
|
|
* Copyright (C) 2005-2007 Olivier Teulière
|
|
* Authors: Olivier Teulière <ipkiss @@ gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*****************************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <sstream>
|
|
#include <cstdlib>
|
|
#include <cstdarg>
|
|
#include <cstring>
|
|
#include <cwchar>
|
|
#include <cwctype>
|
|
#include <cerrno>
|
|
#include <iconv.h>
|
|
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "encoding.h"
|
|
#include "dic_exception.h"
|
|
|
|
using namespace std;
|
|
|
|
|
|
#ifdef WIN32
|
|
// Utility function to get the last system error as a string
|
|
static string GetWin32Error()
|
|
{
|
|
char *lpMsgBuf;
|
|
DWORD dw = GetLastError();
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL, dw,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR) &lpMsgBuf,
|
|
0, NULL);
|
|
string msg = lpMsgBuf;
|
|
LocalFree(lpMsgBuf);
|
|
return msg;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if !HAVE_WCWIDTH
|
|
// wcwidth replacement (for win32 in particular)
|
|
// Inspired from the gnulib package, without some of the refinements
|
|
static inline int wcwidth(wchar_t c)
|
|
{
|
|
// Assume all the printable characters have width 1
|
|
return c == 0 ? 0 : (iswprint(c) ? 1 : -1);
|
|
}
|
|
#endif
|
|
|
|
|
|
int wtoi(const wchar_t *iWStr)
|
|
{
|
|
return wcstol(iWStr, NULL, 10);
|
|
}
|
|
|
|
|
|
int _swprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, ...)
|
|
{
|
|
int res;
|
|
va_list argp;
|
|
va_start(argp, format);
|
|
#ifdef WIN32
|
|
// Mingw32 does not take the maxlen argument
|
|
(void)maxlen;
|
|
res = vswprintf(wcs, format, argp);
|
|
#else
|
|
res = vswprintf(wcs, maxlen, format, argp);
|
|
#endif
|
|
va_end(argp);
|
|
return res;
|
|
}
|
|
|
|
|
|
wchar_t *_wcstok(wchar_t *wcs, const wchar_t *delim, wchar_t **ptr)
|
|
{
|
|
#ifdef WIN32
|
|
// Mingw32 does not take the third argument
|
|
(void)ptr;
|
|
return wcstok(wcs, delim);
|
|
#else
|
|
return wcstok(wcs, delim, ptr);
|
|
#endif
|
|
}
|
|
|
|
|
|
#define _MAX_SIZE_FOR_STACK_ 30
|
|
wstring convertToWc(const string& iStr)
|
|
{
|
|
#ifdef WIN32
|
|
if (iStr.empty())
|
|
return L"";
|
|
|
|
const unsigned int bufSize = iStr.size();
|
|
// Temporary buffer for output
|
|
// We will have at most as many characters as in the UTF-8 string
|
|
wchar_t *wideBuf = new wchar_t[bufSize];
|
|
int number = MultiByteToWideChar(CP_OEMCP, MB_ERR_INVALID_CHARS,
|
|
iStr.c_str(), bufSize, wideBuf, bufSize);
|
|
wstring res(wideBuf, number);
|
|
delete[] wideBuf;
|
|
if (number == 0)
|
|
{
|
|
// Retrieve the system error message for the last-error code
|
|
throw DicException("convertToWc: MultiByteToWideChar failed:" +
|
|
GetWin32Error());
|
|
}
|
|
return res;
|
|
#else
|
|
// Get the needed length (we _can't_ use string::size())
|
|
size_t len = mbstowcs(NULL, iStr.c_str(), 0);
|
|
if (len == (size_t)-1)
|
|
return L"";
|
|
|
|
// Change the allocation method depending on the length of the string
|
|
if (len < _MAX_SIZE_FOR_STACK_)
|
|
{
|
|
// Without multi-thread, we can use static storage
|
|
static wchar_t tmp[_MAX_SIZE_FOR_STACK_];
|
|
len = mbstowcs(tmp, iStr.c_str(), len + 1);
|
|
return tmp;
|
|
}
|
|
else
|
|
{
|
|
wchar_t *tmp = new wchar_t[len + 1];
|
|
len = mbstowcs(tmp, iStr.c_str(), len + 1);
|
|
wstring res = tmp;
|
|
delete[] tmp;
|
|
return res;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
string convertToMb(const wstring& iWStr)
|
|
{
|
|
#ifdef WIN32
|
|
const unsigned int size = iWStr.size() * 4;
|
|
if (size == 0)
|
|
return "";
|
|
char buf[size];
|
|
int res = WideCharToMultiByte(CP_OEMCP, 0, iWStr.c_str(), iWStr.size(),
|
|
buf, size, NULL, NULL);
|
|
if (res == 0)
|
|
{
|
|
// Retrieve the system error message for the last-error code
|
|
throw DicException("convertToMb: WideCharToMultiByte failed: " +
|
|
GetWin32Error());
|
|
}
|
|
return string(buf, res);
|
|
#else
|
|
// Get the needed length (we _can't_ use wstring::size())
|
|
size_t len = wcstombs(NULL, iWStr.c_str(), 0);
|
|
if (len == (size_t)-1)
|
|
return "";
|
|
|
|
// Change the allocation method depending on the length of the string
|
|
if (len < _MAX_SIZE_FOR_STACK_)
|
|
{
|
|
// Without multi-thread, we can use static storage
|
|
static char tmp[_MAX_SIZE_FOR_STACK_];
|
|
len = wcstombs(tmp, iWStr.c_str(), len + 1);
|
|
return tmp;
|
|
}
|
|
else
|
|
{
|
|
char *tmp = new char[len + 1];
|
|
len = wcstombs(tmp, iWStr.c_str(), len + 1);
|
|
string res = tmp;
|
|
delete[] tmp;
|
|
return res;
|
|
}
|
|
#endif
|
|
}
|
|
#undef _MAX_SIZE_FOR_STACK_
|
|
|
|
|
|
string convertToMb(wchar_t iWChar)
|
|
{
|
|
return convertToMb(wstring(1, iWChar));
|
|
}
|
|
|
|
|
|
string truncString(const string &iStr, unsigned int iMaxWidth)
|
|
{
|
|
// Heuristic: the width of a character cannot exceed the number of
|
|
// bytes used to represent it (even in UTF-8)
|
|
if (iStr.size() <= iMaxWidth)
|
|
return iStr;
|
|
return truncAndConvert(convertToWc(iStr), iMaxWidth);
|
|
}
|
|
|
|
|
|
string truncAndConvert(const wstring &iWstr, unsigned int iMaxWidth)
|
|
{
|
|
unsigned int width = 0;
|
|
unsigned int pos;
|
|
for (pos = 0; pos < iWstr.size(); ++pos)
|
|
{
|
|
int n = wcwidth(iWstr[pos]);
|
|
if (n == -1)
|
|
{
|
|
ostringstream ss;
|
|
// XXX: Should we throw an exception instead? Just ignore the problem?
|
|
#if 0
|
|
ss << "truncAndConvert: non printable character: " << iWstr[pos];
|
|
cerr << ss.str() << endl;;
|
|
//throw DicException(ss.str());
|
|
#endif
|
|
return convertToMb(iWstr);
|
|
}
|
|
if (width + n > iMaxWidth)
|
|
break;
|
|
width += n;
|
|
}
|
|
|
|
return convertToMb(iWstr.substr(0, pos));
|
|
}
|
|
|
|
|
|
string truncOrPad(const string &iStr, unsigned int iWidth, char iChar)
|
|
{
|
|
wstring wstr = convertToWc(iStr);
|
|
unsigned int width = 0;
|
|
unsigned int pos;
|
|
for (pos = 0; pos < wstr.size(); ++pos)
|
|
{
|
|
int n = wcwidth(wstr[pos]);
|
|
if (n == -1)
|
|
{
|
|
ostringstream ss;
|
|
// XXX: Should we throw an exception instead? Just ignore the problem?
|
|
#if 0
|
|
ss << "truncAndConvert: non printable character: " << wstr[pos];
|
|
cerr << ss.str() << endl;;
|
|
//throw DicException(ss.str());
|
|
#endif
|
|
return convertToMb(wstr);
|
|
}
|
|
if (width + n > iWidth)
|
|
break;
|
|
width += n;
|
|
}
|
|
|
|
if (iWidth > width)
|
|
return convertToMb(wstr.substr(0, pos)) + string(iWidth - width, iChar);
|
|
else
|
|
return convertToMb(wstr.substr(0, pos));
|
|
}
|
|
|
|
|
|
string padAndConvert(const wstring &iWstr, unsigned int iLength,
|
|
bool iLeftPad, char c)
|
|
{
|
|
int width = 0;
|
|
for (unsigned int i = 0; i < iWstr.size(); ++i)
|
|
{
|
|
int n = wcwidth(iWstr[i]);
|
|
if (n == -1)
|
|
{
|
|
ostringstream ss;
|
|
// XXX: Should we throw an exception instead? Just ignore the problem?
|
|
#if 0
|
|
ss << "padAndConvert: non printable character: " << iWstr[i];
|
|
cerr << ss.str() << endl;;
|
|
//throw DicException(ss.str());
|
|
#endif
|
|
return convertToMb(iWstr);
|
|
}
|
|
width += n;
|
|
}
|
|
|
|
if ((unsigned int)width >= iLength)
|
|
return convertToMb(iWstr);
|
|
else
|
|
{
|
|
// Padding is needed
|
|
string s(iLength - width, c);
|
|
if (iLeftPad)
|
|
return s + convertToMb(iWstr);
|
|
else
|
|
return convertToMb(iWstr) + s;
|
|
}
|
|
}
|
|
|
|
|
|
string centerAndConvert(const wstring &iWstr, unsigned int iLength, char c)
|
|
{
|
|
int width = 0;
|
|
for (unsigned int i = 0; i < iWstr.size(); ++i)
|
|
{
|
|
int n = wcwidth(iWstr[i]);
|
|
if (n == -1)
|
|
{
|
|
ostringstream ss;
|
|
// XXX: Should we throw an exception instead? Just ignore the problem?
|
|
#if 0
|
|
ss << "padAndConvert: non printable character: " << iWstr[i];
|
|
cerr << ss.str() << endl;;
|
|
//throw DicException(ss.str());
|
|
#endif
|
|
return convertToMb(iWstr);
|
|
}
|
|
width += n;
|
|
}
|
|
|
|
if ((unsigned int)width >= iLength)
|
|
return convertToMb(iWstr);
|
|
else
|
|
{
|
|
// Padding is needed
|
|
string s((iLength - width) / 2, c);
|
|
string res = s + convertToMb(iWstr) + s;
|
|
// If the string cannot be centered perfectly, pad again
|
|
// (on the left if iLength is even, on the right otherwise:
|
|
// this tends to align numbers of 1 or 2 digits in a nice way)
|
|
// Note: if needed, we could add the iLeftPad argument
|
|
if ((iLength - width) % 2)
|
|
{
|
|
if (iLength % 2)
|
|
res.append(1, c);
|
|
else
|
|
res.insert(res.begin(), c);
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
|
|
unsigned int readFromUTF8(wchar_t *oString, unsigned int iWideSize,
|
|
const char *iBuffer, unsigned int iBufSize,
|
|
const string &iContext)
|
|
{
|
|
#ifdef WIN32
|
|
if (iBufSize == 0 || iBuffer == NULL || *iBuffer == '\0')
|
|
{
|
|
return 0;
|
|
}
|
|
int res = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, iBuffer,
|
|
iBufSize, oString, iWideSize);
|
|
if (res == 0)
|
|
{
|
|
// Retrieve the system error message for the last-error code
|
|
throw DicException("readFromUTF8: MultiByteToWideChar failed (" +
|
|
iContext + "): " + GetWin32Error());
|
|
}
|
|
return res;
|
|
#else
|
|
iconv_t handle = iconv_open("WCHAR_T", "UTF-8");
|
|
if (handle == (iconv_t)(-1))
|
|
throw DicException("readFromUTF8: iconv_open failed");
|
|
size_t inChars = iBufSize;
|
|
size_t outChars = iWideSize * sizeof(wchar_t);
|
|
// Use the ICONV_CONST trick because the declaration of iconv()
|
|
// differs depending on the implementations...
|
|
ICONV_CONST char *in = const_cast<ICONV_CONST char*>(iBuffer);
|
|
char *out = (char*)oString;
|
|
size_t res = iconv(handle, &in, &inChars, &out, &outChars);
|
|
iconv_close(handle);
|
|
// Problem during encoding conversion?
|
|
if (res == (size_t)(-1))
|
|
{
|
|
throw DicException("readFromUTF8: iconv failed (" +
|
|
iContext + "): " + string(strerror(errno)));
|
|
}
|
|
return iWideSize - outChars / sizeof(wchar_t);
|
|
#endif
|
|
}
|
|
|
|
|
|
wstring readFromUTF8(const char *iBuffer, unsigned int iBufSize,
|
|
const string &iContext)
|
|
{
|
|
// Temporary buffer for output
|
|
// We will have at most as many characters as in the UTF-8 string
|
|
wchar_t *wideBuf = new wchar_t[iBufSize];
|
|
unsigned int number;
|
|
try
|
|
{
|
|
number = readFromUTF8(wideBuf, iBufSize, iBuffer, iBufSize, iContext);
|
|
}
|
|
catch (...)
|
|
{
|
|
// Make sure not to leak
|
|
delete[] wideBuf;
|
|
throw;
|
|
}
|
|
// Copy the string
|
|
wstring res(wideBuf, number);
|
|
delete[] wideBuf;
|
|
return res;
|
|
}
|
|
|
|
|
|
unsigned int writeInUTF8(const wstring &iWString, char *oBuffer,
|
|
unsigned int iBufSize, const string &iContext)
|
|
{
|
|
#ifdef WIN32
|
|
if (iWString.empty())
|
|
{
|
|
*oBuffer = '\0';
|
|
return 0;
|
|
}
|
|
int res = WideCharToMultiByte(CP_UTF8, 0, iWString.c_str(), iWString.size(),
|
|
oBuffer, iBufSize, NULL, NULL);
|
|
if (res == 0)
|
|
{
|
|
// Retrieve the system error message for the last-error code
|
|
throw DicException("writeInUTF8: WideCharToMultiByte failed (" +
|
|
iContext + "): " + GetWin32Error());
|
|
}
|
|
return res;
|
|
#else
|
|
iconv_t handle = iconv_open("UTF-8", "WCHAR_T");
|
|
if (handle == (iconv_t)(-1))
|
|
throw DicException("writeInUTF8: iconv_open failed");
|
|
size_t length = iWString.size();
|
|
size_t inChars = sizeof(wchar_t) * length;
|
|
size_t outChars = iBufSize;
|
|
// Use the ICONV_CONST trick because the declaration of iconv()
|
|
// differs depending on the implementations...
|
|
// FIXME: bonus ugliness for doing 2 casts at once, and accessing string
|
|
// internals...
|
|
ICONV_CONST char *in = (ICONV_CONST char*)(&iWString[0]);
|
|
char *out = oBuffer;
|
|
size_t res = iconv(handle, &in, &inChars, &out, &outChars);
|
|
iconv_close(handle);
|
|
// Problem during encoding conversion?
|
|
if (res == (size_t)(-1))
|
|
{
|
|
throw DicException("writeInUTF8: iconv failed (" +
|
|
iContext + "): " + string(strerror(errno)));
|
|
}
|
|
// Return the number of written bytes
|
|
return iBufSize - outChars;
|
|
#endif
|
|
}
|
|
|
|
|
|
string writeInUTF8(const wstring &iWString, const string &iContext)
|
|
{
|
|
// Temporary buffer for output
|
|
// Each character will take at most 4 bytes in the UTF-8 string
|
|
unsigned int bufSize = iWString.size() * 4;
|
|
char *buf = new char[bufSize];
|
|
unsigned int number;
|
|
try
|
|
{
|
|
number = writeInUTF8(iWString, buf, bufSize, iContext);
|
|
}
|
|
catch (...)
|
|
{
|
|
// Make sure not to leak
|
|
delete[] buf;
|
|
throw;
|
|
}
|
|
// Copy the string
|
|
string res(buf, number);
|
|
delete[] buf;
|
|
return res;
|
|
}
|
|
|