Chuyển Đổi Định Dạng Kết Thúc Dòng Giữa Các Hệ Điều Hành
Chắc hẳn các bạn cũng biết (hoặc nếu chưa biết thì bây giờ biết) thì mỗi hệ điều hành có một định dạng kết thúc dòng (end-line) riêng trong file, ví dụ với 3 nền tảng phổ biến nhất hiện nay:
- Windows: định dạng
CR-LF
- Linux: định dạng
LF
- Classic Mac: định dạng
CR
Chú thích: CR
- Carrige Return, tượng trưng cho kí tự \r
, LF
- Line Feed, tượng trưng cho kí tự \n
. Ở Windows, hệ điều hành sẽ nhận ra kết thúc dòng khi bắt gặp cặp kí tự \r\n
; ở Linux thì khi bắt gặp \n
và Mac là \r
.
Điều này có nghĩa, việc tạo một file .txt
ở hệ điều hành này và đem sang hệ điều hành khác làm việc như thao tác với file trong lập trình có thể gây ra một số lỗi về định dạng. Chính vì thế mình đã viết một cái hàm nho nhỏ (ngôn ngữ C++) dùng để chuyển định dạng file sao cho phù hợp với 3 nền tảng phổ biến trên.
Phương pháp như sau:
- đầu tiên ta sẽ đọc file cần chuyển định dạng với chế độ mở file nhị phân (binary mode). Tiếp đến ta dùng kiểu dữ liệu mảng động là
vector
và kết hợp vớiiterator
trong C++ để lấy được nội dung của file vào chuỗi. - Ta sẽ lần lượt tạo 3 hàm chuyển đổi cho 3 hê điều hành kể trên, ví dụ như
convert_win32
,convert_linux
,convert_mac
. Thuật toán chuyển đổi rất đơn giản, các bạn mức nhập môn hoàn toàn có thể làm được:- Ở Windows: nếu đọc thấy kí tự
\r
mà kí tự sau nó không phải\n
thì chèn thêm\n
vào sau; nếu đọc thấy kí tự\n
mà phía trước không phải là\r
thì chèn thêm\r
vào phía trước. - Ở Linux: nếu đọc thấy kí tự
\r
mà kí tự phía sau là\n
thì xóa kí tự\r
đi, nếu không phải\n
thì thay thế\r
bằng\n
. - Ở Mac: nếu đọc thấy kí tự
\n
mà phía trước là\r
thì xóa\n
đi, nếu không phải\r
thì thay thế\n
bằng\r
.
Đấy, hết sức đơn giản luôn.
- Ở Windows: nếu đọc thấy kí tự
- Ta sẽ nhận dạng hệ điều hành thông qua 3 predefined macros này:
_WIN32
,__linux__
(hoặc__linux
) và__APPLE__
. Với Windows thì ta gọi hàmconvert_win32
, Linux làconvert_linux
và Mac làconvert_mac
. - Sau khi chỉnh xong thì ta xuất nội dung đã chỉnh sửa ra file gốc ban đầu.
Lí thuyết đủ rồi, giờ code thử nào.
Đầu tiên ta sẽ có hàm chuyển định dạng với tham số truyền vào là tên đường dẫn file:
void convert_file_format(const string & path);
Đọc từ file với dạng nhị phân và đưa vào mảng động vector
của thư viện STL thông qua iterator
:
fstream fileInput(path, ios::binary | ios::in);
if (fileInput.fail())
return;
vector<char> buffer((istreambuf_iterator<char>(fileInput)), (istreambuf_iterator<char>()));
Bây giờ đến bước cài đặt 3 hàm chuyển đổi:
Windows:
void format_win32(vector<char> &data)
{
for (size_t i = 0; i < data.size(); ++i)
switch (data[i])
{
case '\r':
if (data[i + 1] != '\n') data.insert(data.begin() + i + 1, '\n');
break;
case '\n':
if (data[i - 1] != '\r') data.insert(data.begin() + i - 1, '\r');
}
}
Linux:
void format_linux(vector<char> &data)
{
for (size_t i = 0; i < data.size(); ++i)
switch (data[i])
{
case '\r':
if (data[i + 1] == '\n') data.erase(data.begin() + i, data.begin() + i + 1);
else
{
data.erase(data.begin() + i, data.begin() + i + 1);
data.insert(data.begin() + i, '\n');
}
break;
}
}
Và Mac:
void format_mac(vector<char> &data)
{
for (size_t i = 0; i < data.size(); ++i)
switch (data[i])
{
case '\n':
if (data[i - 1] == '\r') data.erase(data.begin() + i, data.begin() + i + 1);
else
{
data.erase(data.begin() + i, data.begin() + i + 1);
data.insert(data.begin() + i, '\r');
}
break;
}
}
Tiếp theo là dùng 3 predefined macros để nhận dạng hệ điều hành, gọi hàm ứng với hệ điều hành và đóng file, ta sẽ đặt đoạn code sau trong hàm conver_file_format
phía trên:
#ifdef _WIN32
format_win32(buffer);
#elif __linux__
format_linux(buffer);
#else
format_mac(buffer);
#endif
fi.close();
Nếu bạn đang dùng Windows thì macro được defined sẵn là _WIN32
, nếu xài Linux thì __linux__
.
Cuối cùng là xuất chuỗi đã chỉnh sửa ra file ban đầu:
ofstream fo(path);
for (size_t i = 0; i < buffer.size(); ++i)
fo << buffer[i];
fo.close();
Cách dùng: trước khi đọc file nào (đọc để lưu dữ liệu) thì các bạn chỉ cần gọi hàm convert_file_format(<tên đường dẫn đến file>);
là được, quá đơn giản.
Tóm lại: kĩ thuật cài đặt không có gì quá phức tạp, chỉ cần các bạn đã học về xử lí chuỗi, đọc file, cấp phát động thì hoàn toàn có thể làm được.
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <iterator>
using namespace std;
void format_win32(vector<char> &data)
{
for (size_t i = 0; i < data.size(); ++i)
switch (data[i])
{
case '\r':
if (data[i + 1] != '\n') data.insert(data.begin() + i + 1, '\n');
break;
case '\n':
if (data[i - 1] != '\r') data.insert(data.begin() + i - 1, '\r');
}
}
void format_linux(vector<char> &data)
{
for (size_t i = 0; i < data.size(); ++i)
switch (data[i])
{
case '\r':
if (data[i + 1] == '\n') data.erase(data.begin() + i, data.begin() + i + 1);
else
{
data.erase(data.begin() + i, data.begin() + i + 1);
data.insert(data.begin() + i, '\n');
}
break;
}
}
void format_mac(vector<char> &data)
{
for (size_t i = 0; i < data.size(); ++i)
switch (data[i])
{
case '\n':
if (data[i - 1] == '\r') data.erase(data.begin() + i, data.begin() + i + 1);
else
{
data.erase(data.begin() + i, data.begin() + i + 1);
data.insert(data.begin() + i, '\r');
}
break;
}
}
void convert_file_format(const string & path) {
fstream fileInput(path, ios::binary | ios::in);
if (fileInput.fail())
return;
vector<char> buffer((istreambuf_iterator<char>(fileInput)), (istreambuf_iterator<char>()));
#ifdef _WIN32
format_win32(buffer);
#elif __linux__
format_linux(buffer);
#else
format_mac(buffer);
#endif
fileInput.close();
}
int main() {
cout << "Enter file's path: ";
string path;
getline(cin, path);
convert_file_format(path);
return EXIT_SUCCESS;
}