#include "row.h" #include "cell.h" #include "string_buffer.h" #include "assert.h" #include "vector.h" #include "ctype.h" struct fort_row { vector_t *cells; /*enum RowType type;*/ }; fort_row_t * create_row() { fort_row_t * row = (fort_row_t *)F_CALLOC(sizeof(fort_row_t), 1); if (row == NULL) return NULL; row->cells = create_vector(sizeof(fort_cell_t*), DEFAULT_VECTOR_CAPACITY); if (row->cells == NULL) { F_FREE(row); return NULL; } /* row->is_header = F_FALSE; row->type = Common; */ return row; } void destroy_row(fort_row_t *row) { size_t i = 0; if (row == NULL) return; if (row->cells) { size_t cells_n = vector_size(row->cells); for (i = 0; i < cells_n; ++i) { fort_cell_t *cell = *(fort_cell_t **)vector_at(row->cells, i); destroy_cell(cell); } destroy_vector(row->cells); } F_FREE(row); } int columns_in_row(const fort_row_t *row) { if (row == NULL || row->cells == NULL) return 0; return vector_size(row->cells); } fort_cell_t *get_cell_implementation(fort_row_t *row, size_t col, enum PolicyOnNull policy) { if (row == NULL || row->cells == NULL) { return NULL; } switch (policy) { case DoNotCreate: if (col < columns_in_row(row)) { return *(fort_cell_t**)vector_at(row->cells, col); } return NULL; break; case Create: while(col >= columns_in_row(row)) { fort_cell_t *new_cell = create_cell(); if (new_cell == NULL) return NULL; if (IS_ERROR(vector_push(row->cells, &new_cell))) { destroy_cell(new_cell); return NULL; } } return *(fort_cell_t**)vector_at(row->cells, col); break; } return NULL; } fort_cell_t *get_cell(fort_row_t *row, size_t col) { return get_cell_implementation(row, col, DoNotCreate); } const fort_cell_t *get_cell_c(const fort_row_t *row, size_t col) { return get_cell((fort_row_t *)row, col); } fort_cell_t *get_cell_and_create_if_not_exists(fort_row_t *row, size_t col) { return get_cell_implementation(row, col, Create); } int print_row_separator(char *buffer, size_t buffer_sz, const size_t *col_width_arr, size_t cols, const fort_row_t *upper_row, const fort_row_t *lower_row, enum HorSeparatorPos separatorPos, const separator_t *sep, const context_t *context) { #define CHECK_RESULT_AND_MOVE_DEV(statement) \ k = statement; \ if (k < 0) {\ goto clear; \ } \ dev += k; typedef char char_type; char new_line_char = '\n'; int (*snprint_n_chars_)(char *, size_t , size_t , char) = snprint_n_chars; assert(buffer); assert(context); int dev = 0; int k = 0; enum RowType lower_row_type = Common; if (lower_row != NULL) { lower_row_type = (enum RowType)get_cell_opt_value_hierarcial(context->table_options, context->row, FT_ANY_COLUMN, FT_OPT_ROW_TYPE); } enum RowType upper_row_type = Common; if (upper_row != NULL) { upper_row_type = (enum RowType)get_cell_opt_value_hierarcial(context->table_options, context->row-1, FT_ANY_COLUMN, FT_OPT_ROW_TYPE); } /* Row separator anatomy * * L I I I IV I I I R */ const char *L = NULL; const char *I = NULL; const char *IV = NULL; const char *R = NULL; typedef const char (*border_chars_point_t)[BorderItemPosSize]; const char (*border_chars)[BorderItemPosSize] = NULL; border_chars = (border_chars_point_t)&context->table_options->border_style.border_chars; if (upper_row_type == Header || lower_row_type == Header) { border_chars = (border_chars_point_t)&context->table_options->border_style.header_border_chars; } if (sep && sep->enabled) { L = &(context->table_options->border_style.separator_chars[LH_sip]); I = &(context->table_options->border_style.separator_chars[IH_sip]); IV = &(context->table_options->border_style.separator_chars[II_sip]); R = &(context->table_options->border_style.separator_chars[RH_sip]); } else { switch (separatorPos) { case TopSeparator: L = &(*border_chars)[TL_bip]; I = &(*border_chars)[TT_bip]; IV = &(*border_chars)[TV_bip]; R = &(*border_chars)[TR_bip]; break; case InsideSeparator: L = &(*border_chars)[LH_bip]; I = &(*border_chars)[IH_bip]; IV = &(*border_chars)[II_bip]; R = &(*border_chars)[RH_bip]; break; case BottomSeparator: L = &(*border_chars)[BL_bip]; I = &(*border_chars)[BB_bip]; IV = &(*border_chars)[BV_bip]; R = &(*border_chars)[BR_bip]; break; default: break; } } /* If all chars are not printable, skip line separator */ /* todo: add processing for wchar_t */ if (!isprint(*L) && !isprint(*I) && !isprint(*IV) && !isprint(*R)) return 0; size_t i = 0; for (i = 0; i < cols; ++i) { if (i == 0) { CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, 1, (char_type)*L)); } else { CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, 1, (char_type)*IV)); } CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, col_width_arr[i], (char_type)*I)); } CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, 1, (char_type)*R)); CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, 1, new_line_char)); return dev; clear: return -1; #undef CHECK_RESULT_AND_MOVE_DEV } int wprint_row_separator(wchar_t *buffer, size_t buffer_sz, const size_t *col_width_arr, size_t cols, const fort_row_t *upper_row, const fort_row_t *lower_row, enum HorSeparatorPos separatorPos, const separator_t *sep, const context_t *context) { #define CHECK_RESULT_AND_MOVE_DEV(statement) \ k = statement; \ if (k < 0) {\ goto clear; \ } \ dev += k; typedef wchar_t char_type; char new_line_char = L'\n'; int (*snprint_n_chars_)(wchar_t*, size_t , size_t , wchar_t) = wsnprint_n_chars; assert(buffer); assert(context); int dev = 0; int k = 0; enum RowType lower_row_type = Common; if (lower_row != NULL) { lower_row_type = (enum RowType)get_cell_opt_value_hierarcial(context->table_options, context->row, FT_ANY_COLUMN, FT_OPT_ROW_TYPE); } enum RowType upper_row_type = Common; if (upper_row != NULL) { upper_row_type = (enum RowType)get_cell_opt_value_hierarcial(context->table_options, context->row-1, FT_ANY_COLUMN, FT_OPT_ROW_TYPE); } /* Row separator anatomy * * L I I I IV I I I R */ const char *L = NULL; const char *I = NULL; const char *IV = NULL; const char *R = NULL; typedef const char (*border_chars_point_t)[BorderItemPosSize]; const char (*border_chars)[BorderItemPosSize] = NULL; border_chars = (border_chars_point_t)&context->table_options->border_style.border_chars; if (upper_row_type == Header || lower_row_type == Header) { border_chars = (border_chars_point_t)&context->table_options->border_style.header_border_chars; } if (sep && sep->enabled) { L = &(context->table_options->border_style.separator_chars[LH_sip]); I = &(context->table_options->border_style.separator_chars[IH_sip]); IV = &(context->table_options->border_style.separator_chars[II_sip]); R = &(context->table_options->border_style.separator_chars[RH_sip]); } else { switch (separatorPos) { case TopSeparator: L = &(*border_chars)[TL_bip]; I = &(*border_chars)[TT_bip]; IV = &(*border_chars)[TV_bip]; R = &(*border_chars)[TR_bip]; break; case InsideSeparator: L = &(*border_chars)[LH_bip]; I = &(*border_chars)[IH_bip]; IV = &(*border_chars)[II_bip]; R = &(*border_chars)[RH_bip]; break; case BottomSeparator: L = &(*border_chars)[BL_bip]; I = &(*border_chars)[BB_bip]; IV = &(*border_chars)[BV_bip]; R = &(*border_chars)[BR_bip]; break; default: break; } } /* If all chars are not printable, skip line separator */ /* todo: add processing for wchar_t */ if (!isprint(*L) && !isprint(*I) && !isprint(*IV) && !isprint(*R)) return 0; size_t i = 0; for (i = 0; i < cols; ++i) { if (i == 0) { CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, 1, (char_type)*L)); } else { CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, 1, (char_type)*IV)); } CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, col_width_arr[i], (char_type)*I)); } CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, 1, (char_type)*R)); CHECK_RESULT_AND_MOVE_DEV(snprint_n_chars_(buffer + dev, buffer_sz - dev, 1, new_line_char)); return dev; clear: return -1; #undef CHECK_RESULT_AND_MOVE_DEV } fort_row_t* create_row_from_string(const char *str) { char *pos = NULL; char *base_pos = NULL; int number_of_separators = 0; fort_row_t * row = create_row(); if (row == NULL) return NULL; if (str == NULL) return row; char *str_copy = F_STRDUP(str); if (str_copy == NULL) goto clear; pos = str_copy; base_pos = str_copy; number_of_separators = 0; while (*pos) { pos = strchr(pos, FORT_COL_SEPARATOR); if (pos != NULL) { *(pos) = '\0'; ++pos; number_of_separators++; } fort_cell_t *cell = create_cell(); if (cell == NULL) goto clear; /* int status = fill_buffer_from_string(cell->str_buffer, base_pos); */ int status = fill_cell_from_string(cell, base_pos); if (IS_ERROR(status)) { destroy_cell(cell); goto clear; } status = vector_push(row->cells, &cell); if (IS_ERROR(status)) { destroy_cell(cell); goto clear; } if (pos == NULL) break; base_pos = pos; } /* special case if in format string last cell is empty */ while (vector_size(row->cells) < (number_of_separators + 1)) { fort_cell_t *cell = create_cell(); if (cell == NULL) goto clear; /* int status = fill_buffer_from_string(cell->str_buffer, ""); */ int status = fill_cell_from_string(cell, ""); if (IS_ERROR(status)) { destroy_cell(cell); goto clear; } status = vector_push(row->cells, &cell); if (IS_ERROR(status)) { destroy_cell(cell); goto clear; } } F_FREE(str_copy); return row; clear: destroy_row(row); F_FREE(str_copy); return NULL; } fort_row_t* create_row_from_fmt_string(const char* FT_RESTRICT fmt, va_list *va_args) { string_buffer_t *buffer = create_string_buffer(DEFAULT_STR_BUF_SIZE, CharBuf); if (buffer == NULL) return NULL; int cols_origin = number_of_columns_in_format_string(fmt); int cols = 0; while (1) { va_list va; va_copy(va, *va_args); int virtual_sz = vsnprintf(buffer->str.cstr, string_buffer_capacity(buffer)/*buffer->str_sz*/, fmt, va); va_end(va); /* If error encountered */ if (virtual_sz == -1) goto clear; /* Successful write */ if (virtual_sz < string_buffer_capacity(buffer)) break; /* Otherwise buffer was too small, so incr. buffer size ant try again. */ if (!IS_SUCCESS(realloc_string_buffer_without_copy(buffer))) goto clear; } cols = number_of_columns_in_format_string(buffer->str.cstr); if (cols == cols_origin) { fort_row_t *row = create_row_from_string(buffer->str.cstr); if (row == NULL) { goto clear; } destroy_string_buffer(buffer); return row; } /* todo: add processing of cols != cols_origin */ clear: destroy_string_buffer(buffer); return NULL; } int snprintf_row(const fort_row_t *row, char *buffer, size_t buf_sz, size_t *col_width_arr, size_t col_width_arr_sz, size_t row_height, const context_t *context) { typedef char char_type; char space_char = ' '; char new_line_char = '\n'; int (*snprint_n_chars_)(char *, size_t , size_t , char) = snprint_n_chars; int (*cell_printf_)(fort_cell_t *, size_t, size_t, char *, size_t, const context_t *) = cell_printf; assert(context); if (row == NULL) return -1; int cols_in_row = columns_in_row(row); if (cols_in_row > col_width_arr_sz) return -1; /* Row separator anatomy * * L data IV data IV data R */ typedef const char (*border_chars_point_t)[BorderItemPosSize]; enum RowType row_type = (enum RowType)get_cell_opt_value_hierarcial(context->table_options, context->row, FT_ANY_COLUMN, FT_OPT_ROW_TYPE); const char (*bord_chars)[BorderItemPosSize] = (row_type == Header) ? (border_chars_point_t) (&context->table_options->border_style.header_border_chars) : (border_chars_point_t) (&context->table_options->border_style.border_chars); const char *L = &(*bord_chars)[LL_bip]; const char *IV = &(*bord_chars)[IV_bip]; const char *R = &(*bord_chars)[RR_bip]; int dev = 0; size_t i = 0; for (i = 0; i < row_height; ++i) { dev += snprint_n_chars_(buffer + dev, buf_sz - dev, 1, (char_type)*L); size_t j = 0; for (j = 0; j < col_width_arr_sz; ++j) { ((context_t *)context)->column = j; if (j < cols_in_row) { fort_cell_t *cell = *(fort_cell_t**)vector_at(row->cells, j); dev += cell_printf_(cell, i, j, buffer + dev, col_width_arr[j] + 1, context); } else { dev += snprint_n_chars_(buffer + dev, buf_sz - dev, col_width_arr[j], space_char); } if (j == col_width_arr_sz - 1) { dev += snprint_n_chars_(buffer + dev, buf_sz - dev, 1, (char_type)*R); } else { dev += snprint_n_chars_(buffer + dev, buf_sz - dev, 1, (char_type)*IV); } } dev += snprint_n_chars_(buffer + dev, buf_sz - dev, 1, new_line_char); } return dev; } int wsnprintf_row(const fort_row_t *row, wchar_t *buffer, size_t buf_sz, size_t *col_width_arr, size_t col_width_arr_sz, size_t row_height, const context_t *context) { typedef wchar_t char_type; char space_char = L' '; char new_line_char = L'\n'; int (*snprint_n_chars_)(wchar_t *, size_t , size_t , wchar_t) = wsnprint_n_chars; int (*cell_printf_)(fort_cell_t *, size_t, size_t, wchar_t *, size_t, const context_t *) = cell_wprintf; assert(context); if (row == NULL) return -1; int cols_in_row = columns_in_row(row); if (cols_in_row > col_width_arr_sz) return -1; /* Row separator anatomy * * L data IV data IV data R */ typedef const char (*border_chars_point_t)[BorderItemPosSize]; enum RowType row_type = (enum RowType)get_cell_opt_value_hierarcial(context->table_options, context->row, FT_ANY_COLUMN, FT_OPT_ROW_TYPE); const char (*bord_chars)[BorderItemPosSize] = (row_type) ? (border_chars_point_t) (&context->table_options->border_style.header_border_chars) : (border_chars_point_t) (&context->table_options->border_style.border_chars); const char *L = &(*bord_chars)[LL_bip]; const char *IV = &(*bord_chars)[IV_bip]; const char *R = &(*bord_chars)[RR_bip]; int dev = 0; size_t i = 0; for (i = 0; i < row_height; ++i) { dev += snprint_n_chars_(buffer + dev, buf_sz - dev, 1, (char_type)*L); size_t j = 0; for (j = 0; j < col_width_arr_sz; ++j) { ((context_t *)context)->column = j; if (j < cols_in_row) { fort_cell_t *cell = *(fort_cell_t**)vector_at(row->cells, j); dev += cell_printf_(cell, i, j, buffer + dev, col_width_arr[j] + 1, context); } else { dev += snprint_n_chars_(buffer + dev, buf_sz - dev, col_width_arr[j], space_char); } if (j == col_width_arr_sz - 1) { dev += snprint_n_chars_(buffer + dev, buf_sz - dev, 1, (char_type)*R); } else { dev += snprint_n_chars_(buffer + dev, buf_sz - dev, 1, (char_type)*IV); } } dev += snprint_n_chars_(buffer + dev, buf_sz - dev, 1, new_line_char); } return dev; }