如何在 C++ 中将 ISO-8859-7 字符串“转换”为 UTF-8?

3
我正在使用10年以上的机器,使用单个字节来表示希腊字符 ISO 8859-7。 我需要捕获这些字符并将它们转换为 UTF-8,以便将它们注入 JSON 并通过 HTTPS 发送。 此外,我使用的是 GCC v4.4.7,并且不想升级,因此无法使用 codeconv 等工具。
例如:"OΛΑ": 我得到 char 值数组 [ 0xcf, 0xcb, 0xc1, ],我需要写成字符串 "\u039F\u039B\u0391"
PS:我不是字符集专家,请避免像“ISO 8859 是 Unicode 的子集,所以你只需要实现算法”等哲学性答案。

你基本上是在问:“我可以使用哪个库将一种编码转换为另一种编码,以兼容我的古老编译器?”这有点不是本主题,可以查看softwarerecs.stackexchange.com。 - Dan M.
我想实现这个功能,但不使用外部库。 - afe
一般而言,这是不可能的,因为编码映射并非固定不变。当然,将ISO编码中的256个字符直接映射到UTF-8的hacky ad-hoc解决方案是可行的。除非您还想进行反向转换。 - Dan M.
“我想在不使用外部库的情况下实现这个。” - libiconv 算吗?它非常常见,甚至函数都包含在 GNU 的 libc 中,因此您甚至无需在 Linux 上链接额外的库。 - Ted Lyngmo
3个回答

1
鉴于需要映射的值很少,一个简单的解决方案是使用查找表。
伪代码:
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

所有这些说法,我建议使用库来节省时间。

这可能是我失去希望时可以选择的低成本解决方案。我将其作为备选计划。 - afe
这是一个很好的工作解决方案。我刚刚使用libiconv生成了一个std::unordered_map<unsigned char, std::string_view>,然后可以单独包含该映射,而无需使用iconv或任何其他库。 - Ted Lyngmo
1
@TedLyngmo 聪明地使用元编程,我很喜欢。不过,在这种情况下,我更喜欢使用数组表格。 - eerorika
谢谢!当我到电脑旁时,我会在这里添加一个godbolt链接作为评论。在这种情况下,数组要好得多,我同意。 - Ted Lyngmo
@afe 这是你需要的表格:https://godbolt.org/z/5zanvc。高位中有三个 ? (\x3f),这些是在 iso-8859-7 中未使用的代码点。 - Ted Lyngmo
@TedLyngmo 是的,有一些未使用的值。 - eerorika

1
一种方法是使用Posix的libiconv库。在Linux上,所需的函数(iconv_open, iconviconv_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, '\0');
        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,将其包含在您的项目中,而不是使用 iconv

演示


0

好的,我决定自己做,而不是寻找兼容的库。下面是我的做法。

主要问题是确定如何使用单个ISO字节填充Unicode的两个字节,因此我使用调试器读取了由旧机器和常量字符串(默认为UTF-8)编写的相同字符的值。我从 "O" 和 "Π" 开始,发现在UTF-8中,第一个字节总是0xCE,而第二个字节则用ISO值加上偏移量(-0x30)来填充。我构建了以下代码来实现这一点,并使用了一个包含所有希腊字母(大写和小写)的测试字符串。然后我意识到,从 "π"(ISO中的0xF0)开始,第一个字节和第二个字节的偏移量都会改变,因此我添加了一个测试,以确定应用哪个规则。下面的方法返回一个bool值,让调用者知道原始字符串是否包含ISO字符(对其他目的有用),并使用传递的引用覆盖原始字符串。为了与基本上是用C++编写的C项目保持一致,我使用char数组而不是字符串进行操作。

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]!='\0'){
    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]='\0';
// paste into old char array
strcpy(in, out);

return wasISO;

}


这适用于 iso-8859-7 字符 0xa1 0xa2 0xa4 0xa5 0xaf 吗? - Ted Lyngmo
既然你问了,我猜它不是重点,我只关注带有希腊字符但没有符号的部分。按照描述的步骤,很容易添加所有缺失的字符。 - afe
我没有测试过你的版本,但是看起来制作3字节的UTF8序列不起效果。我提供的映射表既准确又更快,适用于所有iso-8859-7字符。 - Ted Lyngmo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接