Giúp xử lý file tiếng Việt trong c++

Chương trình dưới đây chạy để cắt file thành các đoạn 100 kí tự sao cho kí tự thứ 100, 200, 300… là khoảng trắng, tránh việc cắt vào 1 từ đầy đủ. Chương trình làm việc rất tốt với file chứa văn bảng tiếng anh, nhưng khi file là tiếng việt thì nó sẽ bỏ qua những chữ không thuộc ASCII vì char không support tiếng việt. Sau hơn nửa ngày tìm hiểu thì mình tìm ra cách là sử dụng thư viện codecvt, wchar_t thay cho char, wscanf thay scanf nhưng vẫn chưa đc. Mong mọi người giúp đỡ!

Đây là bài mình sửa: https://onlinegdb.com/DYONdDiMQ

#include <stdio.h>
#include <iostream>
#include <cwchar>
#include <fcntl.h> //_O_WTEXT
#include <io.h>    //_setmode()
#include <string>
#include <locale>
#include <codecvt> //possible C++11?
#include <fstream>

const int GAP = 100;
int main() {
	_setmode(_fileno(stdin), _O_WTEXT); //needed for input
	_setmode(_fileno(stdout), _O_WTEXT); //needed for output
	_wfreopen(L"iput.txt", L"rt", stdin);
	_wfreopen(L"oput.txt", L"wt", stdout);
	int cnt = 0;
	wchar_t str[2002];
	wchar_t c;
	int i, len;
	while (wscanf(L"%c", &c) != -1) {
		str[cnt] = c;
		cnt++;
		if (cnt % GAP == 0) {
			if (str[cnt - 1] == ' ') continue;
			len = 1; // number of space will be added
			while (len < GAP && str[cnt - 1 - len] != ' ')
				len++;
			if (len == GAP) continue;
			// move len character forward, start from cnt - len
			for (i = cnt - len; i < cnt; i++) {
				str[i + len] = str[i];
				str[i] = ' ';
			}
			cnt += len;
		}
	}
	str[cnt] = 0;
	wprintf(L"%s", str);
	return 0;
}

Code gốc: https://onlinegdb.com/HlYta0snc

#include <stdio.h>

const int GAP = 100;
int main() {
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	int cnt = 0;
	char str[2002];
	char c;
	int i, len;
	while (scanf("%c", &c) != -1) {
		str[cnt] = c;
		cnt++;
		if (cnt % GAP == 0) {
			if (str[cnt - 1] == ' ') continue;
			len = 1; // number of space will be added
			while (len < GAP && str[cnt - 1 - len] != ' ') 
				len++;
			if (len == GAP) continue;
			// move len character forward, start from cnt - len
			for (i = cnt - len; i < cnt; i++) {
				str[i + len] = str[i];
				str[i] = ' ';
			}
			cnt += len;
		}
	}
	str[cnt] = 0;
	printf("%s", str);
	return 0;
}

“Chưa được” là từ khó hiểu nhất trong các câu hỏi.
Bạn nên cho biết chương trình chạy xong thì xuất ra những gì.
Giống hệt tập tin đầu vào? Có cách, nhưng sai vị trí? …

3 Likes

C++ sao lại đi xài freopen, scanf, printf gì là sao :V

file lưu dưới encoding gì? Windows có các encoding này:

  • UTF-8 (with BOM)
  • UTF-8 (without BOM)
  • UCS-2 BE
  • UCS-2 LE

trước tiên xác định file input có encoding là gì. Có thể thay đổi encoding của file ko hay file input đó bắt buộc là encoding X nào đó? Nếu là UTF-8 thì code trên có xài codecvt chuyển đổi thành wchar_t gì đâu :V


BOM là byte-order mark, là 3 (với UTF-8) hay 2 (với UCS-2) ký tự đầu tiên trong file ko thuộc về content của file mà được chèn vào đầu file để xác định encoding của file, chỉ có ông Windows làm trò này vì ko thống nhất được encoding của file như bên Linux (Linux mặc định là UTF-8 ko có BOM) :V

  • 3 bytes BOM của UTF-8 là ef bb bf
  • 2 bytes BOM của UCS-2 LE là ff fe
  • 2 bytes BOM của UCS-2 BE là fe ff

LE/BE là little-endian hoặc big-endian, 1 ký tự biểu diễn bằng 2 bytes uuvv có thể được viết là uu vv hoặc vv uu, giống như ngày tháng năm có nơi lại ghi là năm tháng ngày ấy :V Trên Windows mặc định là LE, nhưng trên Mac lại mặc định là BE :V :V

với encoding UTF-8 thì 1 ký tự có thể được viết thành 1-4 bytes. Với UCS-2 thì mỗi ký tự cố định là 2 bytes. Ví dụ chuỗi Nguyễn:

  • UTF-8 được lưu thành
    4e 67 75 79 e1 bb 85 6e
    N  g  u  y  ễ        n
    
    các ký tự ASCII sau khi encode vẫn có nguyên giá trị ASCII và chỉ cần 1 byte, còn ký tự sau khi encode cần tới 3 bytes :V
  • UCS-2 LE được lưu thành
    4e 00 67 00 75 00 79 00 c5 1e 6e 00
    N     g     u     y     ễ     n
    
    tất cả các ký tự đều được encode thành 2 bytes. Rất tiện nhưng nếu văn bản có nhiều ký tự ASCII thì sẽ tốn bộ nhớ nhiều hơn UTF-8, mà tiếng Việt có kha khá ký tự ASCII :V
  • UCS-2 BE được lưu thành
    00 4e 00 67 00 75 00 79 1e c5 00 6e
    N     g     u     y     ễ     n
    
    ký tự đảo ngược với UCS-2 LE vì anh thích :V :V

wchar_t trong C++ có thể là 2 hoặc 4 bytes, tùy vào hệ điều hành :V Trên Windows là 2 bytes, trên Linux/Mac có thể là 4 bytes :V Và mặc định của wchar_t tùy hệ điều hành mà có thể là LE hoặc BE. Mac là BE, Win/Linux là LE. Vì cái LE/BE này mà đọc file UCS-2 vào mảng wchar_t tuy ko cần chuyển đổi codec nhưng vẫn phải đảo byte nếu khác endianness :V :V


giải thích vậy cho thấy đọc file lên mảng wchar_t mà ko biết file định dạng gì thì làm sao đọc đúng :V :V

6 Likes

Encode là UCS-2 LE thì phải làm sao đc b!

std::wifstream fin{"input.txt"}; // có chữ w trước ifstream :V 
std::locale loc{fin.getloc(), new std::codecvt_utf16<wchar_t, 0x10ffff, std::little_endian>};
fin.imbue(loc);

// xài fin như std::cin 
// ví dụ
std::wstring word;
fin >> word;
std::wstring line;
std::getline(fin, line);

edit: có thể xài thử hàm này :V có thể đọc vào cả 4 loại encoding luôn:

#include <locale>
#include <codecvt>
#include <fstream>

template <class CharT>
auto openUnicodeInputFile(const CharT* filename) -> std::wifstream {
    // Peek first byte
    int firstByte = 0;
    {
        std::ifstream test{filename, std::ios::binary};
        if (test) firstByte = test.peek();
    }
    std::wifstream res{filename};
    if (!res) return res;
    if (firstByte == 0xfe || firstByte == 0xff) {
        std::locale loc{res.getloc(), new std::codecvt_utf16<wchar_t, 0x10ffff, std::consume_header>};
        res.imbue(loc);
    } else {
        std::locale loc{res.getloc(), new std::codecvt_utf8<wchar_t, 0x10ffff, std::consume_header>};
        res.imbue(loc);
    }
    return res;
}

// sử dụng:
const char* filename = "input.txt";
hoặc
const wchar_t* filename = L"đầu vào.txt";

auto fin = openUnicodeInputFile(filename);
if (!fin) {
    std::wcerr << L"Error: cannot open " << filename << "\n";
    std::exit(1);
}
std::wstring word;
fin >> word;
std::wcout << L"Chào " << word << "\n";
4 Likes

Cảm ơn bạn mình làm đc r nhé!

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?