如何在c ++中使用不同编码的文本文件上应用<cctype>函数

时间:2018-04-07 09:28:53

标签: c++ linux file-io character-encoding locale

我想将一些文件(大约1000个)拆分为单词并删除数字和标点符号。然后,我将相应地处理这些标记化的单词...但是,这些文件主要是的德语,并且以不同的类型编码:

  • ISO-8859-1
  • ISO Latin-1
  • ASCII
  • UTF-8

我遇到的问题是我无法找到正确的方法来应用tolower() std::cout,当我使用Ubuntu linux时,我也会在终端中获得一些奇怪的图标französische

例如,在非UTF-8文件中,单词franz�sische显示为fürf�rÖrebro等... Österreich忽略了tolower()"Unicode replacement character" � (U+FFFD)等字词。据我所知,在尝试处理Unicode时,程序无法正确解码的任何字符都会插入Ö

当我打开UTF-8文件时,我没有得到任何奇怪的字符,但我仍然无法将大写字母特殊字符(如std::setlocale(LC_ALL, "de_DE.iso88591");)转换为小写字母...我使用tolower()和其他一些选项我在stackoverflow上找到了,但我仍然没有得到所需的输出。

我对如何解决这个问题的猜测是:

  1. 检查即将打开的文件的编码
  2. 根据其特定编码打开文件
  3. 将文件输入转换为UTF-8
  4. 处理文件并应用algorithm
  5. 以上-locale -a是否可行或复杂性是否会飙升?

    这个问题的正确方法是什么?如何使用某种编码选项打开文件?

    1。我的操作系统是否应该启用相应的区域设置作为全局变量来处理(不用打扰控制台如何显示它)文本?(例如在linux中我没有启用de_DE,当我使用LANG=en_US.UTF-8 LANGUAGE=en_US LC_CTYPE="en_US.UTF-8" LC_NUMERIC=el_GR.UTF-8 LC_TIME=el_GR.UTF-8 LC_COLLATE="en_US.UTF-8" LC_MONETARY=el_GR.UTF-8 LC_MESSAGES="en_US.UTF-8" LC_PAPER=el_GR.UTF-8 LC_NAME=el_GR.UTF-8 LC_ADDRESS=el_GR.UTF-8 LC_TELEPHONE=el_GR.UTF-8 LC_MEASUREMENT=el_GR.UTF-8 LC_IDENTIFICATION=el_GR.UTF-8 LC_ALL= C C.UTF-8 el_GR.utf8 en_AG en_AG.utf8 en_AU.utf8 en_BW.utf8 en_CA.utf8 en_DK.utf8 en_GB.utf8 en_HK.utf8 en_IE.utf8 en_IN en_IN.utf8 en_NG en_NG.utf8 en_NZ.utf8 en_PH.utf8 en_SG.utf8 en_US.utf8 en_ZA.utf8 en_ZM en_ZM.utf8 en_ZW.utf8 POSIX 时)

    2。此问题仅在终端默认编码时可见吗?在通常用c ++处理提取的字符串之前,是否需要采取任何进一步的步骤?

    我的linux语言环境:

    void processFiles() {
        std::string filename = "17454-8.txt";
        std::ifstream inFile;
        inFile.open(filename);
        if (!inFile) {
            std::cerr << "Failed to open file" << std::endl;
            exit(1);
        }
    
        //calculate file size
        std::string s = "";
        s.reserve(filesize(filename) + std::ifstream::pos_type(1));
        std::string line;
        while( (inFile.good()) && std::getline(inFile, line) ) {
            s.append(line + "\n");
        }
        inFile.close();
    
        std::cout << s << std::endl;
        //remove punctuation, numbers, tolower,
        //TODO encoding detection and specific transformation (cannot catch Ö, Ä etc) will add too much complexity...
        std::setlocale(LC_ALL, "de_DE.iso88591");
        for (unsigned int i = 0; i < s.length(); ++i) {
            if (std::ispunct(s[i]) || std::isdigit(s[i]))
                s[i] = ' ';
            if (std::isupper(s[i]))
                s[i]=std::tolower(s[i]);
        }
        //std::cout << s << std::endl;
        //tokenize string
        std::istringstream iss(s);
        tokens.clear();
        tokens = {std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}};
        for (auto & i : tokens)
            std::cout << i << std::endl;
    
            //PROCESS TOKENS
        return;
    }
    

    以下是我编写的一些示例代码,它不像我想要的那样工作。

    modal

1 个答案:

答案 0 :(得分:2)

Unicode为字符定义“代码点”。 代码点是32位值。

有一些类型的编码。 ASCII仅使用7位,这给出了128个不同的字符。 Microsoft使用第8位来定义另外128个字符,具体取决于语言环境,并称为“代码页”。如今MS使用UTF-16 2字节编码。因为这对于整个Unicode集来说还不够,所以UTF-16也依赖于语言环境,其名称与Unicode的名称“Latin-1”或“ISO-8859-1”等相匹配。

在Linux(通常用于文件)中使用的最多是UTF-8,它为每个字符使用可变数量的字节。前128个字符与ASCII字符完全相同,每个字符只有一个字节。要表示字符,UTF8最多可以使用4个字节。 Wikipedia中的更多信息。

虽然MS对文件和RAM使用UTF-16,但Linux可能会将UFT-32用于RAM。

要读取文件,您需要知道其编码。试图发现它是一个真正的噩梦,可能不会成功。使用std::basic_ios::imbue可以为您的流设置所需的区域设置,例如this SO answer

tolower此类函数可以使用区域设置,例如

#include <iostream>
#include <locale>

int main() {
    wchar_t s = L'\u00D6'; //latin capital 'o' with diaeresis, decimal 214
    wchar_t sL = std::tolower(s, std::locale("en_US.UTF-8")); //hex= 00F6, dec= 246
    std::cout << "s = " << s << std::endl;
    std::cout << "sL= " << sL << std::endl;

    return 0;
}

输出:

s = 214
sL= 246

在这个other SO answer中,您可以找到好的解决方案,例如使用iconv Linuxiconv W32库。

在Linux中,终端可以设置为在LC_ALLLANGLANGUAGE的帮助下使用区域设置,例如:

//Deutsch
LC_ALL="de_DE.UTF-8"
LANG="de_DE.UTF-8"
LANGUAGE="de_DE:de:en_US:en"

//English 
LC_ALL="en_US.UTF-8"
LANG="en_US.UTF-8"
LANGUAGE="en_US:en"