Files
2026-08-10 01:21:15 +08:00

209 lines
7.0 KiB
C

#include <c_utf8_file.h>
#include <stdio.h>
#include <string.h>
c_err_t c_utf8_file_read(const char* filepath, c_StringBuffer_t* out_sb) {
if (!filepath || !out_sb || !out_sb->buffer) {
return C_ERR_PARAM;
}
FILE* file = fopen(filepath, "rb"); // Open in binary mode to prevent Windows crlf translations
if (!file) {
return C_ERR_FAIL;
}
// Step 1: Detect and handle the optional 3-byte UTF-8 BOM sequence
unsigned char bom[3];
c_size_t bom_read = fread(bom, 1, 3, file);
c_bool_t has_bom = C_FALSE;
if (bom_read == 3 && bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF) {
has_bom = C_TRUE; // BOM sequence matched; file pointer is positioned right past it
} else {
// No BOM found; rewind file pointer back to the absolute beginning of the stream
fseek(file, 0, SEEK_SET);
}
// Step 2: Read data sequentially via stream chunking loops
char read_chunk[1024];
c_size_t bytes_read = 0;
c_size_t partial_offset = 0;
while ((bytes_read = fread(read_chunk + partial_offset, 1, sizeof(read_chunk) - partial_offset, file)) > 0) {
c_size_t total_available_bytes = bytes_read + partial_offset;
c_size_t valid_process_boundary = total_available_bytes;
// Verify that the chunk boundary does not break a multi-byte character in half.
// Look back from the absolute end of the chunk to catch multi-byte headers.
if (read_chunk[total_available_bytes - 1] & 0x80) {
c_size_t lookback = 1;
// Scan backward up to 4 bytes to find the leading byte of the fractured character
while (lookback <= 4 && lookback <= total_available_bytes) {
unsigned char b = (unsigned char)read_chunk[total_available_bytes - lookback];
if ((b & 0xC0) == 0xC0) { // Found a multi-byte lead byte
c_size_t expected_len = c_utf8_char_len((char)b);
if (lookback < expected_len) {
// Character is indeed fractured; shrink chunk boundary to omit it
valid_process_boundary = total_available_bytes - lookback;
}
break;
}
if ((b & 0x80) == 0) { // Standard ASCII character, boundary is clean
break;
}
lookback++;
}
}
// Pipe valid, cohesive text fragments into your dynamic string buffer tracker
if (valid_process_boundary > 0) {
c_err_t err = c_StringBuffer_Append(out_sb, read_chunk, valid_process_boundary);
if (err != C_ERR_OK) {
fclose(file);
return err;
}
}
// Move remaining fractured bytes to the front of the next chunk buffer iteration pass
partial_offset = total_available_bytes - valid_process_boundary;
if (partial_offset > 0) {
memmove(read_chunk, read_chunk + valid_process_boundary, partial_offset);
}
}
// Process residual bytes if the file stream terminates abruptly with an incomplete character sequence
if (partial_offset > 0) {
c_StringBuffer_Append(out_sb, read_chunk, partial_offset);
}
fclose(file);
return C_ERR_OK;
}
c_err_t c_utf8_file_write(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom) {
if (!filepath || !sb || !sb->buffer) {
return C_ERR_PARAM;
}
FILE* file = fopen(filepath, "wb"); // Open in binary mode for precise byte preservation
if (!file) {
return C_ERR_FAIL;
}
// Explicitly inject the UTF-8 BOM sequence if requested by the configuration parameter
if (write_bom) {
unsigned char bom[3] = {0xEF, 0xBB, 0xBF};
if (fwrite(bom, 1, 3, file) != 3) {
fclose(file);
return C_ERR_FAIL;
}
}
// Flush the string buffer's raw tracking payload into disk blocks
if (sb->size > 0) {
c_size_t written = fwrite(sb->buffer, 1, sb->size, file);
if (written != sb->size) {
fclose(file);
return C_ERR_FAIL;
}
}
fclose(file);
return C_ERR_OK;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_err_t c_utf8_file_append(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom) {
if (!filepath || !sb || !sb->buffer) {
return C_ERR_PARAM;
}
// Check if the file already exists by attempting to open it in read mode
FILE* check_file = fopen(filepath, "rb");
c_bool_t file_exists = (check_file != NULL);
if (file_exists) {
fclose(check_file);
}
// Open the file in append-binary mode
FILE* file = fopen(filepath, "ab");
if (!file) {
return C_ERR_FAIL;
}
// Write the BOM only if requested AND the file is brand new
if (write_bom && !file_exists) {
unsigned char bom[3] = {0xEF, 0xBB, 0xBF};
if (fwrite(bom, 1, 3, file) != 3) {
fclose(file);
return C_ERR_FAIL;
}
}
// Append the string buffer's raw tracking payload
if (sb->size > 0) {
c_size_t written = fwrite(sb->buffer, 1, sb->size, file);
if (written != sb->size) {
fclose(file);
return C_ERR_FAIL;
}
}
fclose(file);
return C_ERR_OK;
}
c_err_t c_utf8_file_readline(FILE* file, c_StringBuffer_t* out_line) {
if (!file || !out_line || !out_line->buffer) {
return C_ERR_PARAM;
}
// Clear previous string buffer trackers to prepare for fresh line ingestion
c_StringBuffer_Clear(out_line);
char read_chunk[256];
c_bool_t data_extracted = C_FALSE;
long line_start_pos = ftell(file);
while (fgets(read_chunk, sizeof(read_chunk), file) != NULL) {
data_extracted = C_TRUE;
c_size_t chunk_len = strlen(read_chunk);
// Check if the chunk contains a newline character
char* newline_ptr = strchr(read_chunk, '\n');
if (newline_ptr != NULL) {
// Calculate exact copy length up to the newline boundary
c_size_t copy_len = newline_ptr - read_chunk;
if (copy_len > 0) {
// Strip carriage returns '\r' for safe cross-platform matching
if (read_chunk[copy_len - 1] == '\r') {
copy_len--;
}
}
if (copy_len > 0) {
c_err_t err = c_StringBuffer_Append(out_line, read_chunk, copy_len);
if (err != C_ERR_OK) return err;
}
return C_ERR_OK; // Line read complete
}
// If no newline is found, the line is longer than our chunk; append everything and keep reading
c_err_t err = c_StringBuffer_Append(out_line, read_chunk, chunk_len);
if (err != C_ERR_OK) return err;
}
// Handle end-of-file (EOF) state
if (data_extracted) {
return C_ERR_OK; // Returned the final trailing line containing no newline char
}
return C_ERR_FAIL; // Reached EOF without extracting any data bytes
}