1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 05:27:45 +00:00

Spreadsheet: Implement infinit-scroll for columns

This naturally also implements multi-char columns, and also integrates
it into the js runtime (such columns can be named in ranges too).
This commit is contained in:
AnotherTest 2020-11-27 13:55:14 +03:30 committed by Andreas Kling
parent f6ae4edbd2
commit 474453244b
7 changed files with 233 additions and 50 deletions

View file

@ -99,28 +99,57 @@ size_t Sheet::add_row()
return m_rows++;
}
static String convert_to_string(size_t value, unsigned base = 26, StringView map = {})
{
if (map.is_null())
map = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
ASSERT(base >= 2 && base <= map.length());
// The '8 bits per byte' assumption may need to go?
Array<char, round_up_to_power_of_two(sizeof(size_t) * 8 + 1, 2)> buffer;
size_t i = 0;
do {
buffer[i++] = map[value % base];
value /= base;
} while (value > 0);
// NOTE: Weird as this may seem, the thing that comes after 'A' is 'AA', which as a number would be '00'
// to make this work, only the most significant digit has to be in a range of (1..25) as opposed to (0..25),
// but only if it's not the only digit in the string.
if (i > 1)
--buffer[i - 1];
for (size_t j = 0; j < i / 2; ++j)
swap(buffer[j], buffer[i - j - 1]);
return String { ReadonlyBytes(buffer.data(), i) };
}
static size_t convert_from_string(StringView str, unsigned base = 26, StringView map = {})
{
if (map.is_null())
map = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
ASSERT(base >= 2 && base <= map.length());
size_t value = 0;
for (size_t i = str.length(); i > 0; --i) {
auto digit_value = map.find_first_of(str[i - 1]).value_or(0);
// NOTE: Refer to the note in `convert_to_string()'.
if (i == str.length() && str.length() > 1)
++digit_value;
value = value * base + digit_value;
}
return value;
}
String Sheet::add_column()
{
if (m_current_column_name_length == 0) {
m_current_column_name_length = 1;
m_columns.append("A");
return "A";
}
if (m_current_column_name_length == 1) {
auto last_char = m_columns.last()[0];
if (last_char == 'Z') {
m_current_column_name_length = 2;
m_columns.append("AA");
return "AA";
}
last_char++;
m_columns.append({ &last_char, 1 });
return m_columns.last();
}
TODO();
auto next_column = convert_to_string(m_columns.size());
m_columns.append(next_column);
return next_column;
}
void Sheet::update()
@ -207,6 +236,31 @@ Optional<Position> Sheet::parse_cell_name(const StringView& name)
return Position { col, row.to_uint().value() };
}
Optional<size_t> Sheet::column_index(const StringView& column_name) const
{
auto index = convert_from_string(column_name);
if (m_columns.size() <= index || m_columns[index] != column_name)
return {};
return index;
}
Optional<String> Sheet::column_arithmetic(const StringView& column_name, int offset)
{
auto maybe_index = column_index(column_name);
if (!maybe_index.has_value())
return {};
auto index = maybe_index.value() + offset;
if (m_columns.size() > index)
return m_columns[index];
for (size_t i = m_columns.size(); i <= index; ++i)
add_column();
return m_columns.last();
}
Cell* Sheet::from_url(const URL& url)
{
auto maybe_position = position_from_url(url);
@ -335,6 +389,11 @@ RefPtr<Sheet> Sheet::from_json(const JsonObject& object, Workbook& workbook)
return IterationDecision::Continue;
});
if (sheet->m_columns.size() < default_column_count && sheet->columns_are_standard()) {
for (size_t i = sheet->m_columns.size(); i < default_column_count; ++i)
sheet->add_column();
}
auto cells = object.get("cells").as_object();
auto json = sheet->interpreter().global_object().get("JSON");
auto& parse_function = json.as_object().get("parse").as_function();
@ -425,15 +484,28 @@ Position Sheet::written_data_bounds() const
{
Position bound;
for (auto& entry : m_cells) {
if (entry.key.row > bound.row)
if (entry.key.row >= bound.row)
bound.row = entry.key.row;
if (entry.key.column > bound.column)
if (entry.key.column >= bound.column)
bound.column = entry.key.column;
}
return bound;
}
/// The sheet is allowed to have nonstandard column names
/// this checks whether all existing columns are 'standard'
/// (i.e. as generated by 'convert_to_string()'
bool Sheet::columns_are_standard() const
{
for (size_t i = 0; i < m_columns.size(); ++i) {
if (m_columns[i] != convert_to_string(i))
return false;
}
return true;
}
JsonObject Sheet::to_json() const
{
JsonObject object;
@ -448,12 +520,13 @@ JsonObject Sheet::to_json() const
auto bottom_right = written_data_bounds();
auto columns = JsonArray();
for (auto& column : m_columns)
columns.append(column);
object.set("columns", move(columns));
object.set("rows", bottom_right.row);
if (!columns_are_standard()) {
auto columns = JsonArray();
for (auto& column : m_columns)
columns.append(column);
object.set("columns", move(columns));
}
object.set("rows", bottom_right.row + 1);
JsonObject cells;
for (auto& it : m_cells) {
@ -520,12 +593,21 @@ Vector<Vector<String>> Sheet::to_xsv() const
auto bottom_right = written_data_bounds();
// First row = headers.
data.append(m_columns);
size_t column_count = m_columns.size();
if (columns_are_standard()) {
column_count = convert_from_string(bottom_right.column) + 1;
Vector<String> cols;
for (size_t i = 0; i < column_count; ++i)
cols.append(m_columns[i]);
data.append(move(cols));
} else {
data.append(m_columns);
}
for (size_t i = 0; i <= bottom_right.row; ++i) {
Vector<String> row;
row.resize(m_columns.size());
for (size_t j = 0; j < m_columns.size(); ++j) {
row.resize(column_count);
for (size_t j = 0; j < column_count; ++j) {
auto cell = at({ m_columns[j], i });
if (cell)
row[j] = cell->typed_display();
@ -546,6 +628,10 @@ RefPtr<Sheet> Sheet::from_xsv(const Reader::XSV& xsv, Workbook& workbook)
sheet->m_columns = cols;
for (size_t i = 0; i < max(rows, Sheet::default_row_count); ++i)
sheet->add_row();
if (sheet->columns_are_standard()) {
for (size_t i = sheet->m_columns.size(); i < Sheet::default_column_count; ++i)
sheet->add_column();
}
for (auto row : xsv) {
for (size_t i = 0; i < cols.size(); ++i) {