【问题标题】:Process text with weird character处理带有奇怪字符的文本
【发布时间】:2015-08-07 00:08:28
【问题描述】:

我有一个由句子组成的文本语料库,每行一个。语料库是德文的,其中某些部分包含一些奇怪的字符。例如,当从终端使用less 命令查看时,以下是一些行:

w�hlen sie die zahlen aus,indem sie sie anklicken。西肯嫩 jede runde zwei bis zehn zahlen ausw�hlen.

keno xperiment ist eine erweiterte version vom keno lottery spiel.

nachdem sie ihre auswahl getroffen haben, klicken sie auf eine spielen, um die spielrunde zu starten, oder auf f�nf spielen, um mit den ausgew�hlten zahlen f�nf runden hintereinander zu spielen。

under dem hauptspielbereich finden sie eine reihe von buttons mit den zahlen 2 bis 10, au�erdem die zufallsauswahl und ein k�stchen mit der bezeichnung ‘vor jeder runde neue zahlen ausw�hlen’.

(“�”实际上只是带有元音变音的字符。令人惊讶的是,在某些行中,这些字符完美显示)

我想处理以删除包含这些损坏字符的行。代码如下:

#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <pthread.h>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>

using namespace std;

int lengthLowerBound = -1;
int requirePunct = 0;

unordered_map <string, string> specials;

// weird characters
#pragma warning( push)
#pragma warning( disable : 4101 )
bool isOK(char ch) {
  if (ch == '�') return false;
  return true;
}
#pragma warning( pop )

bool isOK(string &line) {
  for (int i = 0; i < line.length(); ++i) {
    if (line[i] == '�') return false;
  }
  return true;
}

// has punctuations
bool hasPunctuations(string &line) {
  for (int i = 0; i < line.length(); ++i) {
    if (ispunct(line[i])) return true;
  }
  return false;
}

int main(int argc, char *argv[]) {
  if (argc < 5) {
    cout << "Usage: ./Filter input1.txt input2.txt output1.txt output2.txt [hasPunctuation | noShorterThan x]" << endl;
    exit(1);
  }

  string inpFile1 = string(argv[1]);
  string inpFile2 = string(argv[2]);
  string outFile1 = string(argv[3]);
  string outFile2 = string(argv[4]);

  for (int i = 5; i < argc; ++i) {
    if (strcmp(argv[i], "hasPunctuation") == 0) requirePunct = 1;
    else if (strcmp(argv[i], "noShorterThan") == 0) lengthLowerBound = atoi(argv[i+1]);
  }

  // filter
  ifstream finp1(inpFile1, ifstream::in);
  if (finp1.fail()) {
    cout << "  Can't open file " << inpFile1 << endl;
    exit(1);
  }

  ifstream finp2(inpFile2, ifstream::in);
  if (finp2.fail()) {
    cout << "  Can't open file " << inpFile2 << endl;
    exit(1);
  }

  ofstream fout1(outFile1, ofstream::out);
  if (fout1.fail()) {
    cout << "  Can't open file " << outFile1 << endl;
    exit(1);
  }

  ofstream fout2(outFile2, ofstream::out);
  if (fout2.fail()) {
    cout << "  Can't open file " << outFile2 << endl;
    exit(1);
  }

  string line1, line2;
  int numLines = 0;
  cout << "# Start tokenizing" << endl;
  while (getline(finp1, line1)) {
    getline(finp2, line2);

    if (line1.empty() || line2.empty()) continue;

    if (!isOK(line1) || !isOK(line2)) continue;

    if (lengthLowerBound > 0) {
      if (line1.length() < lengthLowerBound || line2.length() < lengthLowerBound) continue; 
    }

    if (requirePunct) {
      if (!hasPunctuations(line1) || !hasPunctuations(line2)) continue;
    }

    fout1 << line1 << endl;
    fout2 << line2 << endl;

    ++numLines;
    if (numLines % 1000 == 0) cout << "\r  Read " << numLines/1000 << "k lines.";
  }
  cout << endl;
  cout << "# Done" << endl;

  fout2.close();
  fout1.close();
  finp2.close();
  finp1.close();

  return 0;
}

如果您只是复制上面的代码并按照代码本身的指导使用适当的命令运行它,在给定的句子上,您会看到代码没有做任何事情。我怀疑这是因为比较 ch == '�' 总是错误的。实际上有一个警告。

所以,我的问题是如何做我想做的事?而且它不需要在 C++ 中。 Python、Perl 或 sed 命令,任何东西,都受到高度赞赏。谢谢。

TL;DR:我想处理一个文本文件以删除引用文本中的所有“�”字符。

【问题讨论】:

  • 我很确定问题在于您正在像处理 ANSI 一样处理文本,而这些元音变音可能超出 ANSI 范围。阅读joelonsoftware.com/articles/Unicode.html,这是一本很棒的入门书。
  • 在您的源代码中嵌入不寻常的字符可能不起作用。相反,您需要以另一种方式检查字符(例如,通过其整数代码或 unicode 代码)。
  • 先用十六进制编辑器找到字符的代码点。

标签: c++ text special-characters


【解决方案1】:

看起来less 不理解您的德语文件的编码:出于这个原因,“�”字符可能会代替实际字符显示。过滤掉这些行的最简单方法可能是通过查看是否有任何字节设置了最高位来检查非 ASCII 字符:

bool isOk(const string& line) {
    return none_of(begin(line), end(line), [](uint8_t c) {return 0x80 & c;});
}

这适用于 UTF-8 编码的文本,因为任何非 ASCII 代码点都被编码为多个字节,每个字节都设置了最高位。它也适用于更有限的 8 位代码,例如 ISO-8859-1,因为非 ASCII 字符由 ASCII 范围之外的字节表示,它也必须设置最高位。

【讨论】:

    【解决方案2】:

    只需使用cctype.h 中的函数/方法检查字符串中的每个字符

    bool isOk(string str) {
        for(int i=0; i < str.length(); i++) {
            char c = tolower(str[i]);
            if(!isalnum(c) && !ispunct(c) && !isspace(c)) // check if not alphanumeric, a punctuation nor white space
                return false
        }
        return true;
    }
    

    编辑:也许真的不需要'tolower'

    【讨论】:

      【解决方案3】:

      最可能的解释是您的语料库(大部分)以单字节编码进行编码,例如 ISO-8859-1(或密切相关的 Windows-1252 和 ISO-8859-15),而您的终端期待 UTF Unicode的-8编码。

      '�' 是 Unicode "replacement character" (U+FFFD); Unicode 输出设备通常会显示带有替换字符实例的无效代码。

      由于替换字符不能容纳在单个字节中,当您尝试从中创建窄字符文字时,C 编译器会生成警告。您可以创建一个宽字符文字或 unicode 字符串文字,但这对您没有帮助,因为替换字符实际上并不存在于文本中。

      你真正想做的是:

      • 将您的语料库转换为 UTF-8,或

      • 告诉您的终端期待语料库中使用的任何编码。

      您选择哪一个将取决于您希望如何使用语料库数据。

      对于第一种可能性,请查看iconv 实用程序(还有一个同名的 Posix 标准库函数)。其次,您需要更改您的语言环境,可能还需要更改终端模拟器的配置。请参阅 Stackexchange 姐妹网站上的 this question 了解一些想法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-12-01
        • 2012-06-11
        • 1970-01-01
        • 1970-01-01
        • 2023-03-20
        • 2017-10-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多