如何在 C++ 中将 ISO-8859-7 字符串 "convert" 转换为 UTF-8?
How can I "convert" ISO-8859-7 strings to UTF-8 in C++?
我正在使用 10 年以上的机器,这些机器使用 ISO 8859-7 来表示希腊字符,每个字符使用一个字节。
我需要捕获这些字符并将它们转换为 UTF-8,以便将它们注入 JSON 以通过 HTTPS 发送。
另外,我使用的是 GCC v4.4.7,我不想升级,所以我不能使用 codeconv 等。
示例:“OΛA”:
我得到 char 值 [ 0xcf, 0xcb, 0xc1, ]
,我需要写这个字符串 "\u039F\u039B\u0391"
.
PS:我不是字符集专家所以请避免像“ISO 8859 是 Unicode 的一个子集所以你只需要实现算法”这样的哲学回答。
鉴于要映射的值很少,一个简单的解决方案是使用查找 table。
伪代码:
id_offset = 0x80 // 0x00 .. 0x7F same in UTF-8
c1_offset = 0x20 // 0x80 .. 0x9F control characters
table_offset = id_offset + c1_offset
table = [
u8"\u00A0", // 0xA0
u8"‘", // 0xA1
u8"’",
u8"£",
u8"€",
u8"₯",
// ... Refer to ISO 8859-7 for full list of characters.
]
let S be the input string
let O be an empty output string
for each char C in S
reinterpret C as unsigned char U
if U less than id_offset // same in both encodings
append C to O
else if U less than table_offset // control code
append char '\xC2' to O // lead byte
append char C to O
else
append string table[U - table_offset] to O
综上所述,我建议改用库来节省时间。
一种方法是使用 Posix libiconv
库。在 Linux 上,所需的功能(iconv_open
、iconv
和 iconv_close
)甚至包含在 libc
中,因此不需要额外的链接。在您的旧机器上,您可能需要安装 libiconv
,但我对此表示怀疑。
转换可能就这么简单:
#include <iconv.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <iterator>
#include <stdexcept>
#include <string>
// A wrapper for the iconv functions
class Conv {
public:
// Open a conversion descriptor for the two selected character sets
Conv(const char* to, const char* from) : cd(iconv_open(to, from)) {
if(cd == reinterpret_cast<iconv_t>(-1))
throw std::runtime_error(std::strerror(errno));
}
Conv(const Conv&) = delete;
~Conv() { iconv_close(cd); }
// the actual conversion function
std::string convert(const std::string& in) {
const char* inbuf = in.c_str();
size_t inbytesleft = in.size();
// make the "out" buffer big to fit whatever we throw at it and set pointers
std::string out(inbytesleft * 6, '[=10=]');
char* outbuf = out.data();
size_t outbytesleft = out.size();
// the const_cast shouldn't be needed but my "iconv" function declares it
// "char**" not "const char**"
size_t non_rev_converted = iconv(cd, const_cast<char**>(&inbuf),
&inbytesleft, &outbuf, &outbytesleft);
if(non_rev_converted == static_cast<size_t>(-1)) {
// here you can add misc handling like replacing erroneous chars
// and continue converting etc.
// I'll just throw...
throw std::runtime_error(std::strerror(errno));
}
// shrink to keep only what we converted
out.resize(outbuf - out.data());
return out;
}
private:
iconv_t cd;
};
int main() {
Conv cvt("UTF-8", "ISO-8859-7");
// create a string from the ISO-8859-7 data
unsigned char data[]{0xcf, 0xcb, 0xc1};
std::string iso88597_str(std::begin(data), std::end(data));
auto utf8 = cvt.convert(iso88597_str);
std::cout << utf8 << '\n';
}
输出(UTF-8):
ΟΛΑ
使用它您可以创建一个从 ISO-8859-7 到 UTF-8 的映射 table,您将其包含在您的项目中而不是 iconv
:
好的,我决定自己做而不是寻找兼容的库。这是我的做法。
主要问题是弄清楚如何使用 ISO 的单个字节填充 Unicode 的两个字节,所以我使用调试器读取相同字符的值,先由旧机器写入,然后用常量字符串(默认为 UTF-8)。我从“O”和“Π”开始,发现在 UTF-8 中,第一个字节始终是 0xCE,而第二个字节填充了 ISO 值加上一个偏移量 (-0x30)。我构建了以下代码来实现这一点,并使用了一个包含所有希腊字母(大写和小写)的测试字符串。然后我意识到从“π”(ISO 中的 0xF0)开始,第一个字节和第二个字节的偏移量都发生了变化,所以我添加了一个测试来确定应用这两个规则中的哪一个。下面的方法 returns 一个布尔值,让调用者知道原始字符串是否包含 ISO 字符(对其他用途有用),并用新字符串覆盖作为引用传递的原始字符串。我使用 char 数组而不是字符串来与项目的其余部分保持一致,这基本上是一个用 C++ 编写的 C 项目。
bool iso_to_utf8(char* in){
bool wasISO=false;
if(in == NULL)
return wasISO;
// count chars
int i=strlen(in);
if(!i)
return wasISO;
// create and size new buffer
char *out = new char[2*i];
// fill with 0's, useful for watching the string as it gets built
memset(out, 0, 2*i);
// ready to start from head of old buffer
i=0;
// index for new buffer
int j=0;
// for each char in old buffer
while(in[i]!='[=10=]'){
if(in[i] >= 0){
// it's already utf8-compliant, take it as it is
out[j++] = in[i];
}else{
// it's ISO
wasISO=true;
// get plain value
int val = in[i] & 0xFF;
// first byte to CF or CE
out[j++]= val > 0xEF ? 0xCF : 0xCE;
// second char to plain value normalized
out[j++] = val - (val > 0xEF ? 0x70 : 0x30);
}
i++;
}
// add string terminator
out[j]='[=10=]';
// paste into old char array
strcpy(in, out);
return wasISO;
}
我正在使用 10 年以上的机器,这些机器使用 ISO 8859-7 来表示希腊字符,每个字符使用一个字节。 我需要捕获这些字符并将它们转换为 UTF-8,以便将它们注入 JSON 以通过 HTTPS 发送。 另外,我使用的是 GCC v4.4.7,我不想升级,所以我不能使用 codeconv 等。
示例:“OΛA”:
我得到 char 值 [ 0xcf, 0xcb, 0xc1, ]
,我需要写这个字符串 "\u039F\u039B\u0391"
.
PS:我不是字符集专家所以请避免像“ISO 8859 是 Unicode 的一个子集所以你只需要实现算法”这样的哲学回答。
鉴于要映射的值很少,一个简单的解决方案是使用查找 table。
伪代码:
id_offset = 0x80 // 0x00 .. 0x7F same in UTF-8
c1_offset = 0x20 // 0x80 .. 0x9F control characters
table_offset = id_offset + c1_offset
table = [
u8"\u00A0", // 0xA0
u8"‘", // 0xA1
u8"’",
u8"£",
u8"€",
u8"₯",
// ... Refer to ISO 8859-7 for full list of characters.
]
let S be the input string
let O be an empty output string
for each char C in S
reinterpret C as unsigned char U
if U less than id_offset // same in both encodings
append C to O
else if U less than table_offset // control code
append char '\xC2' to O // lead byte
append char C to O
else
append string table[U - table_offset] to O
综上所述,我建议改用库来节省时间。
一种方法是使用 Posix libiconv
库。在 Linux 上,所需的功能(iconv_open
、iconv
和 iconv_close
)甚至包含在 libc
中,因此不需要额外的链接。在您的旧机器上,您可能需要安装 libiconv
,但我对此表示怀疑。
转换可能就这么简单:
#include <iconv.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <iterator>
#include <stdexcept>
#include <string>
// A wrapper for the iconv functions
class Conv {
public:
// Open a conversion descriptor for the two selected character sets
Conv(const char* to, const char* from) : cd(iconv_open(to, from)) {
if(cd == reinterpret_cast<iconv_t>(-1))
throw std::runtime_error(std::strerror(errno));
}
Conv(const Conv&) = delete;
~Conv() { iconv_close(cd); }
// the actual conversion function
std::string convert(const std::string& in) {
const char* inbuf = in.c_str();
size_t inbytesleft = in.size();
// make the "out" buffer big to fit whatever we throw at it and set pointers
std::string out(inbytesleft * 6, '[=10=]');
char* outbuf = out.data();
size_t outbytesleft = out.size();
// the const_cast shouldn't be needed but my "iconv" function declares it
// "char**" not "const char**"
size_t non_rev_converted = iconv(cd, const_cast<char**>(&inbuf),
&inbytesleft, &outbuf, &outbytesleft);
if(non_rev_converted == static_cast<size_t>(-1)) {
// here you can add misc handling like replacing erroneous chars
// and continue converting etc.
// I'll just throw...
throw std::runtime_error(std::strerror(errno));
}
// shrink to keep only what we converted
out.resize(outbuf - out.data());
return out;
}
private:
iconv_t cd;
};
int main() {
Conv cvt("UTF-8", "ISO-8859-7");
// create a string from the ISO-8859-7 data
unsigned char data[]{0xcf, 0xcb, 0xc1};
std::string iso88597_str(std::begin(data), std::end(data));
auto utf8 = cvt.convert(iso88597_str);
std::cout << utf8 << '\n';
}
输出(UTF-8):
ΟΛΑ
使用它您可以创建一个从 ISO-8859-7 到 UTF-8 的映射 table,您将其包含在您的项目中而不是 iconv
:
好的,我决定自己做而不是寻找兼容的库。这是我的做法。
主要问题是弄清楚如何使用 ISO 的单个字节填充 Unicode 的两个字节,所以我使用调试器读取相同字符的值,先由旧机器写入,然后用常量字符串(默认为 UTF-8)。我从“O”和“Π”开始,发现在 UTF-8 中,第一个字节始终是 0xCE,而第二个字节填充了 ISO 值加上一个偏移量 (-0x30)。我构建了以下代码来实现这一点,并使用了一个包含所有希腊字母(大写和小写)的测试字符串。然后我意识到从“π”(ISO 中的 0xF0)开始,第一个字节和第二个字节的偏移量都发生了变化,所以我添加了一个测试来确定应用这两个规则中的哪一个。下面的方法 returns 一个布尔值,让调用者知道原始字符串是否包含 ISO 字符(对其他用途有用),并用新字符串覆盖作为引用传递的原始字符串。我使用 char 数组而不是字符串来与项目的其余部分保持一致,这基本上是一个用 C++ 编写的 C 项目。
bool iso_to_utf8(char* in){
bool wasISO=false;
if(in == NULL)
return wasISO;
// count chars
int i=strlen(in);
if(!i)
return wasISO;
// create and size new buffer
char *out = new char[2*i];
// fill with 0's, useful for watching the string as it gets built
memset(out, 0, 2*i);
// ready to start from head of old buffer
i=0;
// index for new buffer
int j=0;
// for each char in old buffer
while(in[i]!='[=10=]'){
if(in[i] >= 0){
// it's already utf8-compliant, take it as it is
out[j++] = in[i];
}else{
// it's ISO
wasISO=true;
// get plain value
int val = in[i] & 0xFF;
// first byte to CF or CE
out[j++]= val > 0xEF ? 0xCF : 0xCE;
// second char to plain value normalized
out[j++] = val - (val > 0xEF ? 0x70 : 0x30);
}
i++;
}
// add string terminator
out[j]='[=10=]';
// paste into old char array
strcpy(in, out);
return wasISO;
}