diff options
Diffstat (limited to 'Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua')
-rw-r--r-- | Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua new file mode 100644 index 00000000000..1e3c2a76570 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua @@ -0,0 +1,559 @@ +require "luaprogtable-parse.lua" +require "luaprogtable-stringbuffer.lua" + +TeXTable = {} +TeXTable.__index = TeXTable + +function TeXTable:new() + local ret = { + rows = {} + } + setmetatable(ret, TeXTable) + return ret +end + +TeXTableCell = {} +TeXTableCell.__index = TeXTableCell + +local _std_shape = {1, 1} + +function TeXTableCell:new(default_val) + local ret = { + data=default_val, + shape=_std_shape, + parent=nil + } + setmetatable(ret, TeXTableCell) + return ret +end + + +TeXTableRow = {} +TeXTableRow.__index = TeXTableRow + +function TeXTableRow:new(tex_table) + + local ncols = tex_table.ncols + local default_val = rawget(tex_table, "default value") + + local cells = {} + + for i=1,ncols do + table.insert(cells, TeXTableCell:new(default_val)) + end + + local ret = { + ["before line"] = "", + ["after line"] = rawget(tex_table, "default after line"), + ["after spacing"] = rawget(tex_table, "default after spacing"), + ["cells"] = cells + } + + setmetatable(ret, TeXTableRow) + return ret +end + + +function TeXTable:resize(nrows, ncols) + local now_nrows = #self.rows + local now_ncols = self.ncols + + if nrows < now_nrows or ncols < now_ncols then + raise_error("'TeXTable:resize' doesn not support table truncation") + end + + local nrows_delta = nrows - now_nrows + local ncols_delta = ncols - now_ncols + + -- change column for existing rows + self.ncols = ncols + for i=1,now_nrows do + local row = self.rows[i] + for j=1,ncols_delta do + table.insert(row.cells, TeXTableCell:new(rawget(self, "default value"))) + end + end + + -- add new rows + for i=1,nrows_delta do + table.insert(self.rows, TeXTableRow:new(self)) + end +end + +function TeXTable:to_string(no_linebreak) + local ret = {} + local no_linebreak = no_linebreak or false + local lb = "\n" + if no_linebreak then + lb = " " + end + + table.insert(ret, string.format([[\begin{%s}{%s}]], self.backend, rawget(self, "table preamble"))) + + local nrows = #self.rows + for i=1,nrows do + + local row = self.rows[i] + + local cell_data = {} + local j = 1 + + while j <= self.ncols do + local cell = row.cells[j] + + local next_j = nil + + if cell.shape ~= nil then + table.insert(cell_data, cell.data) + next_j = j + cell.shape[2] + else + local parent_row = cell.parent[1] + local parent_col = cell.parent[2] + local parent_cell = self:get_cell(parent_row, parent_col) + table.insert(cell_data, "") + next_j = j + parent_cell.shape[2] + end + + j = next_j + end + + local row_str = "" + + if string.len(rawget(row, "before line")) > 0 then + row_str = rawget(row, "before line") .. lb .. row_str + end + + row_str = row_str .. table.concat(cell_data, " & ") .. [[ \\]] + + if string.len(rawget(row, "after spacing")) > 0 then + row_str = row_str .. "[" .. rawget(row, "after spacing") .. "]" + end + + row_str = row_str .. rawget(row, "after line") + + table.insert(ret, row_str) + end + + table.insert(ret, string.format([[\end{%s}]], self.backend)) + + return table.concat(ret, lb) + +end + + +function TeXTable:get_cell(row_id, cell_id) + local row = self.rows[row_id] + if row == nil then + raise_error(string.format("row id '%d' is invalid for table '%s'", row_id, rawget(self, "table name"))) + end + local cell = row.cells[cell_id] + if cell == nil then + raise_error(string.format("cell location '(%d,%d)' is invalid for table '%s'", row_id, cell_id, rawget(self, "table name"))) + end + return cell +end + +function TeXTable:set_cell(row_id, cell_id, data, shape) + local cell = self:get_cell(row_id, cell_id) + + -- check if we can modify this cell + if cell.parent ~= nil then + local parent_1 = cell.parent[1] + local parent_2 = cell.parent[2] + raise_error(string.format("cell location '(%d,%d)' of table '%s' is not acceesible because it is overlapped by cell '(%d,%d)'", row_id, cell_id, rawget(self, "table name"), parent_1, parent_2)) + end + + + -- check cell boundary + local max_row = row_id + shape[1] - 1 + local max_col = cell_id + shape[2] - 1 + if max_row > #self.rows or max_col > self.ncols then + raise_error(string.format("cell '(%d,%d)' is too large for table '%s'", row_id, col_id, rawget(self, "table name"))) + end + + + local old_shape = cell.shape + + local std_shape = {1, 1} + -- clear control info for children + for i=1, old_shape[1] do + local row_id = row_id + i - 1 + for j = 1, old_shape[2] do + local col_id = cell_id + j - 1 + local other_cell = self:get_cell(row_id, col_id) + other_cell.parent = nil + other_cell.shape = std_shape + end + end + + local parent_t = {row_id, cell_id} + -- adjust control info based on new shape + for i=1, shape[1] do + local row_id = row_id + i - 1 + for j = 1, shape[2] do + local col_id = cell_id + j - 1 + local other_cell = self:get_cell(row_id, col_id) + other_cell.parent = parent_t + other_cell.shape = nil + end + end + + -- adjust control info for this cell + cell.parent = nil + cell.shape = shape + cell.data = data +end + +function TeXTable:fill(data) + local nrows = #self.rows + for i=1,nrows do + for j=1,self.ncols do + local cell = self:get_cell(i, j) + cell.shape = _std_shape + cell.parent = nil + cell.data = data + end + end +end + +function TeXTable:fix_row_index(index_pair) + local ret = {index_pair[1], index_pair[2]} + + if ret[1] < 0 then + ret[1] = #self.rows + ret[1] + 1 + end + if ret[2] < 0 then + ret[2] = #self.rows + ret[2] + 1 + end + + if ret[2] < ret[1] then + raise_error("invalid index expression: second element must not be smaller than the first") + end + + if ret[1] <= 0 then + raise_error("invalid index expression: index must be greater than zero after conversion") + end + + return ret +end + +function TeXTable:fix_col_index(index_pair) + local ret = {index_pair[1], index_pair[2]} + + if ret[1] < 0 then + ret[1] = self.ncols + ret[1] + 1 + end + if ret[2] < 0 then + ret[2] = self.ncols + ret[2] + 1 + end + + if ret[2] < ret[1] then + raise_error("invalid index expression: second element must not be smaller than the first") + end + + if ret[1] <= 0 then + raise_error("invalid index expression: index must be greater than zero after conversion") + end + + return ret +end + +-- fix the index of a single cell +function TeXTable:fix_cell_index(index_expr) + local the_table = self + local index_parse = _lpt_parse_index_expr(index_expr) + if #index_parse ~= 2 then + raise_error(string.format("invalid index expression for cell: '%s'", index_expr)) + end + + local row_expr = the_table:fix_row_index(index_parse[1]) + local col_expr = the_table:fix_col_index(index_parse[2]) + + if row_expr[1] ~= row_expr[2] then + raise_error(string.format("invalid row index expression for cell: '%s'", index_expr)) + end + if col_expr[1] ~= col_expr[2] then + raise_error(string.format("invalid column index expression for cell: '%s'", index_expr)) + end + + return {row_expr[1], col_expr[1]} +end + +-- make sure table views do not cut through multicol/multirow +function TeXTable:check_view_validity(start_row, start_col, row_span, col_span) + local max_row = start_row + row_span - 1 + local max_col = start_col + col_span - 1 + + for i = 1, row_span do + local row_id = start_row + i - 1 + for j=1,col_span do + local col_id = start_col + j - 1 + local cell = self:get_cell(row_id, col_id) + if cell.shape ~= nil then + local boundary_1 = row_id + cell.shape[1] - 1 + local boundary_2 = col_id + cell.shape[2] - 1 + if boundary_1 > max_row or boundary_2 > max_col then + raise_error(string.format("invalid table view: cell '(%d, %d)' has shape '(%d, %d)', which does not fit into viewed region", row_id, col_id, cell.shape[1], cell.shape[2])) + end + end + end + end +end + + +lpt_info = {} + +function _lpt_get_table(table_name) + local table_name = _lpt_strip(table_name) + local the_table = lpt_info[table_name] + if the_table == nil then + raise_error(string.format("table '%s' does not exist", table_name)) + end + return the_table +end + + +_verb_lines = {} +_verb_end_env = "" + +function _lpt_store_verbatim_lines(str) + if string.find(str , _verb_end_env) then + luatexbase.remove_from_callback("process_input_buffer" , "lpt_store_verbatim_lines") + return _verb_end_env + else + if str ~= nil then + table.insert(_verb_lines, str) + end + return "" + end +end + +function _lpt_register_verbatim(end_env) + for k in next, _verb_lines do rawset(_verb_lines, k, nil) end + _verb_end_env = end_env + luatexbase.add_to_callback("process_input_buffer", _lpt_store_verbatim_lines, "lpt_store_verbatim_lines") +end + +local _lpt_ignored_fields = {nrows=true} +local _lpt_numeric_fields = {ncols=true} + + + +function lpt_new_table(param) + local new_table = TeXTable:new() + + for key, val in pairs(param) do + if _lpt_numeric_fields[key] then + rawset(new_table, key, _lpt_tonumber(val)) + elseif _lpt_ignored_fields[key] then + -- do nothing + else + rawset(new_table, key, val) + end + end + + local table_name = new_table["table name"] + + local table_fetch = lpt_info[table_name] + if table_fetch ~= nil then + raise_error(string.format("table '%s' already exists", table_name)) + end + + local nrows = _lpt_tonumber(param["nrows"]) + + -- integrity check + if nrows < 0 then + raise_error(string.format("'nrows' (%d) of table '%s' is invalid", nrows, table_name)) + end + if new_table.ncols <= 0 then + raise_error(string.format("'ncols' (%d) of table '%s' is invalid", new_table.ncols, table_name)) + end + + -- create table + new_table:resize(nrows, new_table.ncols) + + lpt_info[table_name] = new_table +end + +function lpt_table_fill(table_name, index_expr, env_content) + local the_table = _lpt_get_table(table_name) + local env_content = env_content or table.concat(_verb_lines, " ") + + if index_expr:len() == 0 then + the_table:fill(env_content) + else + local index_parse = _lpt_parse_index_expr(index_expr) + if #index_parse ~= 2 then + raise_error(string.format("invalid index expr '%s' for table '%s'", index_expr, table_name)) + end + + local row_expr = the_table:fix_row_index(index_parse[1]) + local col_expr = the_table:fix_col_index(index_parse[2]) + + local row_span = row_expr[2] - row_expr[1] + 1 + local col_span = col_expr[2] - col_expr[1] + 1 + + the_table:check_view_validity(row_expr[1], col_expr[1], row_span, col_span) + + for i=1,row_span do + local row_id = i + row_expr[1] - 1 + for j=1,col_span do + local col_id = j + col_expr[1] - 1 + the_table:set_cell(row_id, col_id, env_content, _std_shape) + end + end + end +end + + +function lpt_table_view(table_name, index_expr) + local env_content = table.concat(_verb_lines, "\n") + local the_table = _lpt_get_table(table_name) + + local index_parse = _lpt_parse_index_expr(index_expr) + if #index_parse ~= 2 then + raise_error(string.format("invalid index expr '%s' for table '%s'", index_expr, table_name)) + end + + local row_expr = the_table:fix_row_index(index_parse[1]) + local col_expr = the_table:fix_col_index(index_parse[2]) + + local row_span = row_expr[2] - row_expr[1] + 1 + local col_span = col_expr[2] - col_expr[1] + 1 + + the_table:check_view_validity(row_expr[1], col_expr[1], row_span, col_span) + + local parsed_table = _lpt_parse_table_view(env_content) + + if #parsed_table ~= row_span then + raise_error(string.format("lpttableview: expecting '%d' rows, but get '%d' instead for table '%s'", row_span, #parsed_table, table_name)) + end + + for i=1,row_span do + local row_id = i + row_expr[1] - 1 + local cols = parsed_table[i] + + -- sum the number of total columns in this row + local n_table_cols = 0 + local max_row_size = 0 + for _, val in ipairs(cols) do + n_table_cols = n_table_cols + val["shape"][2] + max_row_size = math.max(max_row_size, val["shape"][1]) + end + + local max_row = row_id + max_row_size - 1 + if max_row > row_expr[2] then + raise_error("lpttableview: the height of table is greater than table view") + end + + + -- count the numbers of columns needs to be filled in this row + local pending_table_cols = 0 + for j=1,col_span do + local col_id = j + col_expr[1] - 1 + local cell = the_table:get_cell(row_id, col_id) + if cell.shape ~= nil then + pending_table_cols = pending_table_cols + 1 + else + local parent_row = cell.parent[1] + -- if the parent is in the same row, + -- then this space needs to be filled in the same row + if parent_row == row_id then + pending_table_cols = pending_table_cols + 1 + end + end + end + + if n_table_cols ~= pending_table_cols then + raise_error(string.format("lpttableview: expecting '%d' cols for row '%d', but get '%d' instead for table '%s'", pending_table_cols, i, n_table_cols, table_name)) + end + + + --[[ + if n_table_cols ~= col_span then + raise_error(string.format("lpttableview: expecting '%d' cols for row '%d', but get '%d' instead for table '%s'", col_span, i, n_table_cols, table_name)) + end + --]] + + local k = 1 + for j=1,col_span do + local col_id = j + col_expr[1] - 1 + local cell = the_table:get_cell(row_id, col_id) + if cell.shape ~= nil then + local col_data = cols[k]["data"] + local col_shape = cols[k]["shape"] + the_table:set_cell(row_id, col_id, col_data, col_shape) + k = k + 1 + end + end + + end + + +end + +local _s_buf = TeXStringBuffer:new() + +function lpt_use_table(table_name) + local the_table = _lpt_get_table(table_name) + local input_method = rawget(the_table, "input method") + + if input_method == "stringbuffer" then + local table_str = the_table:to_string(true) + _s_buf:clear() + _s_buf:append(table_str) + _s_buf:register_callback() + tex.print([[\input{stringbuffer}]]) + elseif input_method == "file" then + local table_str = the_table:to_string() + local filename = tex.jobname .. "-" .. table_name .. ".table" + local outfile = io.open(filename, "w") + outfile:write(table_str) + outfile:close() + tex.print(string.format([[\input{%s}]], filename)) + else + raise_error(string.format("invalid input method '%s' for table '%s'", input_method, table_name)) + end + +end + +function lpt_set_row_prop(table_name, index_expr, param) + + local the_table = _lpt_get_table(table_name) + local index_parse = _lpt_parse_index_expr(index_expr) + for key, val in ipairs(index_parse) do + local fixed_val = the_table:fix_row_index(val) + + local l = fixed_val[1] + local r = fixed_val[2] + + for i=l,r do + local row = the_table.rows[i] + if row == nil then + raise_error(string.format("lpt_set_row_prop: invalid index expression '%s'", index_expr)) + end + for kkey, vval in pairs(param) do + if rawget(row, kkey) == nil then + raise_error(string.format("row '%d' of table '%s' does not have attribute '%s'", i, table_name, kkey)) + end + rawset(row, kkey, vval) + end + end + end +end + + +function lpt_add_row(table_name, param) + local the_table = _lpt_get_table(table_name) + local nrows = #the_table.rows + local ncols = the_table.ncols + the_table:resize(nrows+1, ncols) + lpt_set_row_prop(table_name, "-1", param) +end + +function lpt_get_table_shape(table_name) + local the_table = _lpt_get_table(table_name) + local nrows = #the_table.rows + local ncols = the_table.ncols + tex.print(string.format("{%d}{%d}", nrows, ncols)) +end
\ No newline at end of file |