Serenity Operating System
at master 773 lines 27 kB view raw
1/* 2 * Copyright (c) 2020-2022, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "Spreadsheet.h" 8#include "JSIntegration.h" 9#include "Workbook.h" 10#include <AK/ByteBuffer.h> 11#include <AK/Debug.h> 12#include <AK/GenericLexer.h> 13#include <AK/JsonArray.h> 14#include <AK/JsonObject.h> 15#include <AK/JsonParser.h> 16#include <AK/ScopeGuard.h> 17#include <AK/TemporaryChange.h> 18#include <AK/URL.h> 19#include <LibCore/DeprecatedFile.h> 20#include <LibJS/Interpreter.h> 21#include <LibJS/Parser.h> 22#include <LibJS/Runtime/AbstractOperations.h> 23#include <LibJS/Runtime/FunctionObject.h> 24#include <ctype.h> 25#include <unistd.h> 26 27namespace Spreadsheet { 28 29Sheet::Sheet(StringView name, Workbook& workbook) 30 : Sheet(workbook) 31{ 32 m_name = name; 33 34 for (size_t i = 0; i < default_row_count; ++i) 35 add_row(); 36 37 for (size_t i = 0; i < default_column_count; ++i) 38 add_column(); 39} 40 41Sheet::Sheet(Workbook& workbook) 42 : m_workbook(workbook) 43 , m_interpreter(JS::Interpreter::create<SheetGlobalObject>(m_workbook.vm(), *this)) 44{ 45 JS::DeferGC defer_gc(m_workbook.vm().heap()); 46 m_global_object = static_cast<SheetGlobalObject*>(&m_interpreter->realm().global_object()); 47 global_object().define_direct_property("workbook", m_workbook.workbook_object(), JS::default_attributes); 48 global_object().define_direct_property("thisSheet", &global_object(), JS::default_attributes); // Self-reference is unfortunate, but required. 49 50 // Sadly, these have to be evaluated once per sheet. 51 constexpr auto runtime_file_path = "/res/js/Spreadsheet/runtime.js"sv; 52 auto file_or_error = Core::DeprecatedFile::open(runtime_file_path, Core::OpenMode::ReadOnly); 53 if (!file_or_error.is_error()) { 54 auto buffer = file_or_error.value()->read_all(); 55 auto script_or_error = JS::Script::parse(buffer, interpreter().realm(), runtime_file_path); 56 if (script_or_error.is_error()) { 57 warnln("Spreadsheet: Failed to parse runtime code"); 58 for (auto& error : script_or_error.error()) { 59 // FIXME: This doesn't print hints anymore 60 warnln("SyntaxError: {}", error.to_deprecated_string()); 61 } 62 } else { 63 auto result = interpreter().run(script_or_error.value()); 64 if (result.is_error()) { 65 warnln("Spreadsheet: Failed to run runtime code:"); 66 auto thrown_value = *result.throw_completion().value(); 67 warn("Threw: {}", MUST(thrown_value.to_string_without_side_effects())); 68 if (thrown_value.is_object() && is<JS::Error>(thrown_value.as_object())) { 69 auto& error = static_cast<JS::Error const&>(thrown_value.as_object()); 70 warnln(" with message '{}'", error.get_without_side_effects(interpreter().vm().names.message)); 71 for (auto& traceback_frame : error.traceback()) { 72 auto& function_name = traceback_frame.function_name; 73 auto& source_range = traceback_frame.source_range; 74 dbgln(" {} at {}:{}:{}", function_name, source_range.filename(), source_range.start.line, source_range.start.column); 75 } 76 } else { 77 warnln(); 78 } 79 } 80 } 81 } 82} 83 84JS::Interpreter& Sheet::interpreter() const 85{ 86 return const_cast<JS::Interpreter&>(*m_interpreter); 87} 88 89size_t Sheet::add_row() 90{ 91 return m_rows++; 92} 93 94static Optional<size_t> convert_from_string(StringView str, unsigned base = 26, StringView map = {}) 95{ 96 if (map.is_null()) 97 map = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"sv; 98 99 VERIFY(base >= 2 && base <= map.length()); 100 101 if (str.is_empty()) 102 return {}; 103 104 size_t value = 0; 105 auto const len = str.length(); 106 for (auto i = 0u; i < len; i++) { 107 auto maybe_index = map.find(str[i]); 108 if (!maybe_index.has_value()) 109 return {}; 110 size_t digit_value = maybe_index.value(); 111 value += (digit_value + 1) * AK::pow<float>(base, len - 1 - i); 112 } 113 114 return value - 1; 115} 116 117DeprecatedString Sheet::add_column() 118{ 119 auto next_column = DeprecatedString::bijective_base_from(m_columns.size()); 120 m_columns.append(next_column); 121 return next_column; 122} 123 124void Sheet::update() 125{ 126 if (m_should_ignore_updates) { 127 m_update_requested = true; 128 return; 129 } 130 m_visited_cells_in_update.clear(); 131 Vector<Cell&> cells_copy; 132 133 // Grab a copy as updates might insert cells into the table. 134 for (auto& it : m_cells) { 135 if (it.value->dirty()) { 136 cells_copy.append(*it.value); 137 m_workbook.set_dirty(true); 138 } 139 } 140 141 for (auto& cell : cells_copy) 142 update(cell); 143 144 m_visited_cells_in_update.clear(); 145} 146 147void Sheet::update(Cell& cell) 148{ 149 if (m_should_ignore_updates) { 150 m_update_requested = true; 151 return; 152 } 153 if (cell.dirty()) { 154 if (has_been_visited(&cell)) { 155 // This may be part of an cyclic reference chain, 156 // so just ignore it. 157 cell.clear_dirty(); 158 return; 159 } 160 m_visited_cells_in_update.set(&cell); 161 cell.update_data({}); 162 } 163} 164 165JS::ThrowCompletionOr<JS::Value> Sheet::evaluate(StringView source, Cell* on_behalf_of) 166{ 167 TemporaryChange cell_change { m_current_cell_being_evaluated, on_behalf_of }; 168 auto name = on_behalf_of ? on_behalf_of->name_for_javascript(*this) : "cell <unknown>"sv; 169 auto script_or_error = JS::Script::parse( 170 source, 171 interpreter().realm(), 172 name); 173 174 if (script_or_error.is_error()) 175 return interpreter().vm().throw_completion<JS::SyntaxError>(TRY_OR_THROW_OOM(interpreter().vm(), script_or_error.error().first().to_string())); 176 177 return interpreter().run(script_or_error.value()); 178} 179 180Cell* Sheet::at(StringView name) 181{ 182 auto pos = parse_cell_name(name); 183 if (pos.has_value()) 184 return at(pos.value()); 185 186 return nullptr; 187} 188 189Cell* Sheet::at(Position const& position) 190{ 191 auto it = m_cells.find(position); 192 193 if (it == m_cells.end()) 194 return nullptr; 195 196 return it->value; 197} 198 199Optional<Position> Sheet::parse_cell_name(StringView name) const 200{ 201 GenericLexer lexer(name); 202 auto col = lexer.consume_while(isalpha); 203 auto row = lexer.consume_while(isdigit); 204 205 if (!lexer.is_eof() || row.is_empty() || col.is_empty()) 206 return {}; 207 208 auto it = m_columns.find(col); 209 if (it == m_columns.end()) 210 return {}; 211 212 return Position { it.index(), row.to_uint().value() }; 213} 214 215Optional<size_t> Sheet::column_index(StringView column_name) const 216{ 217 auto maybe_index = convert_from_string(column_name); 218 if (!maybe_index.has_value()) 219 return {}; 220 221 auto index = maybe_index.value(); 222 if (m_columns.size() <= index || m_columns[index] != column_name) { 223 auto it = m_columns.find(column_name); 224 if (it == m_columns.end()) 225 return {}; 226 index = it.index(); 227 } 228 229 return index; 230} 231 232Optional<DeprecatedString> Sheet::column_arithmetic(StringView column_name, int offset) 233{ 234 auto maybe_index = column_index(column_name); 235 if (!maybe_index.has_value()) 236 return {}; 237 238 if (offset < 0 && maybe_index.value() < (size_t)(0 - offset)) 239 return m_columns.first(); 240 241 auto index = maybe_index.value() + offset; 242 if (m_columns.size() > index) 243 return m_columns[index]; 244 245 for (size_t i = m_columns.size(); i <= index; ++i) 246 add_column(); 247 248 return m_columns.last(); 249} 250 251Cell* Sheet::from_url(const URL& url) 252{ 253 auto maybe_position = position_from_url(url); 254 if (!maybe_position.has_value()) 255 return nullptr; 256 257 return at(maybe_position.value()); 258} 259 260Optional<Position> Sheet::position_from_url(const URL& url) const 261{ 262 if (!url.is_valid()) { 263 dbgln("Invalid url: {}", url.to_deprecated_string()); 264 return {}; 265 } 266 267 if (url.scheme() != "spreadsheet" || url.host() != "cell") { 268 dbgln("Bad url: {}", url.to_deprecated_string()); 269 return {}; 270 } 271 272 // FIXME: Figure out a way to do this cross-process. 273 VERIFY(url.path() == DeprecatedString::formatted("/{}", getpid())); 274 275 return parse_cell_name(url.fragment()); 276} 277 278Position Sheet::offset_relative_to(Position const& base, Position const& offset, Position const& offset_base) const 279{ 280 if (offset.column >= m_columns.size()) { 281 dbgln("Column '{}' does not exist!", offset.column); 282 return base; 283 } 284 if (offset_base.column >= m_columns.size()) { 285 dbgln("Column '{}' does not exist!", offset_base.column); 286 return base; 287 } 288 if (base.column >= m_columns.size()) { 289 dbgln("Column '{}' does not exist!", base.column); 290 return offset; 291 } 292 293 auto new_column = offset.column + base.column - offset_base.column; 294 auto new_row = offset.row + base.row - offset_base.row; 295 296 return { new_column, new_row }; 297} 298 299Vector<CellChange> Sheet::copy_cells(Vector<Position> from, Vector<Position> to, Optional<Position> resolve_relative_to, CopyOperation copy_operation) 300{ 301 Vector<CellChange> cell_changes; 302 // Disallow misaligned copies. 303 if (to.size() > 1 && from.size() != to.size()) { 304 dbgln("Cannot copy {} cells to {} cells", from.size(), to.size()); 305 return cell_changes; 306 } 307 308 Vector<Position> target_cells; 309 for (auto& position : from) 310 target_cells.append(resolve_relative_to.has_value() ? offset_relative_to(to.first(), position, resolve_relative_to.value()) : to.first()); 311 312 auto copy_to = [&](auto& source_position, Position target_position) { 313 auto& target_cell = ensure(target_position); 314 auto* source_cell = at(source_position); 315 auto previous_data = target_cell.data(); 316 317 if (!source_cell) { 318 target_cell.set_data(""); 319 cell_changes.append(CellChange(target_cell, previous_data)); 320 return; 321 } 322 323 target_cell.copy_from(*source_cell); 324 cell_changes.append(CellChange(target_cell, previous_data)); 325 if (copy_operation == CopyOperation::Cut && !target_cells.contains_slow(source_position)) { 326 cell_changes.append(CellChange(*source_cell, source_cell->data())); 327 source_cell->set_data(""); 328 } 329 }; 330 331 // Resolve each index as relative to the first index offset from the selection. 332 auto& target = to.first(); 333 334 auto top_left_most_position_from = from.first(); 335 auto bottom_right_most_position_from = from.first(); 336 for (auto& position : from) { 337 if (position.row > bottom_right_most_position_from.row) 338 bottom_right_most_position_from = position; 339 else if (position.column > bottom_right_most_position_from.column) 340 bottom_right_most_position_from = position; 341 342 if (position.row < top_left_most_position_from.row) 343 top_left_most_position_from = position; 344 else if (position.column < top_left_most_position_from.column) 345 top_left_most_position_from = position; 346 } 347 348 Vector<Position> ordered_from; 349 auto current_column = top_left_most_position_from.column; 350 auto current_row = top_left_most_position_from.row; 351 for ([[maybe_unused]] auto& position : from) { 352 for (auto& position : from) 353 if (position.row == current_row && position.column == current_column) 354 ordered_from.append(position); 355 356 if (current_column >= bottom_right_most_position_from.column) { 357 current_column = top_left_most_position_from.column; 358 current_row += 1; 359 } else { 360 current_column += 1; 361 } 362 } 363 364 if (target.row > top_left_most_position_from.row || (target.row >= top_left_most_position_from.row && target.column > top_left_most_position_from.column)) 365 ordered_from.reverse(); 366 367 for (auto& position : ordered_from) { 368 dbgln_if(COPY_DEBUG, "Paste from '{}' to '{}'", position.to_url(*this), target.to_url(*this)); 369 copy_to(position, resolve_relative_to.has_value() ? offset_relative_to(target, position, resolve_relative_to.value()) : target); 370 } 371 372 return cell_changes; 373} 374 375RefPtr<Sheet> Sheet::from_json(JsonObject const& object, Workbook& workbook) 376{ 377 auto sheet = adopt_ref(*new Sheet(workbook)); 378 auto rows = object.get_u32("rows"sv).value_or(default_row_count); 379 auto columns = object.get_array("columns"sv); 380 auto name = object.get_deprecated_string("name"sv).value_or("Sheet"); 381 if (object.has("cells"sv) && !object.has_object("cells"sv)) 382 return {}; 383 384 sheet->set_name(name); 385 386 for (size_t i = 0; i < max(rows, (unsigned)Sheet::default_row_count); ++i) 387 sheet->add_row(); 388 389 // FIXME: Better error checking. 390 if (columns.has_value()) { 391 columns->for_each([&](auto& value) { 392 sheet->m_columns.append(value.as_string()); 393 return IterationDecision::Continue; 394 }); 395 } 396 397 if (sheet->m_columns.size() < default_column_count && sheet->columns_are_standard()) { 398 for (size_t i = sheet->m_columns.size(); i < default_column_count; ++i) 399 sheet->add_column(); 400 } 401 402 auto json = sheet->global_object().get_without_side_effects("JSON"); 403 auto& parse_function = json.as_object().get_without_side_effects("parse").as_function(); 404 405 auto read_format = [](auto& format, auto const& obj) { 406 if (auto value = obj.get_deprecated_string("foreground_color"sv); value.has_value()) 407 format.foreground_color = Color::from_string(*value); 408 if (auto value = obj.get_deprecated_string("background_color"sv); value.has_value()) 409 format.background_color = Color::from_string(*value); 410 }; 411 412 if (auto cells = object.get_object("cells"sv); cells.has_value()) { 413 cells->for_each_member([&](auto& name, JsonValue const& value) { 414 auto position_option = sheet->parse_cell_name(name); 415 if (!position_option.has_value()) 416 return IterationDecision::Continue; 417 418 auto position = position_option.value(); 419 auto& obj = value.as_object(); 420 auto kind = obj.get_deprecated_string("kind"sv).value_or("LiteralString") == "LiteralString" ? Cell::LiteralString : Cell::Formula; 421 422 OwnPtr<Cell> cell; 423 switch (kind) { 424 case Cell::LiteralString: 425 cell = make<Cell>(obj.get_deprecated_string("value"sv).value_or({}), position, *sheet); 426 break; 427 case Cell::Formula: { 428 auto& vm = sheet->interpreter().vm(); 429 auto value_or_error = JS::call(vm, parse_function, json, JS::PrimitiveString::create(vm, obj.get_deprecated_string("value"sv).value_or({}))); 430 if (value_or_error.is_error()) { 431 warnln("Failed to load previous value for cell {}, leaving as undefined", position.to_cell_identifier(sheet)); 432 value_or_error = JS::js_undefined(); 433 } 434 cell = make<Cell>(obj.get_deprecated_string("source"sv).value_or({}), value_or_error.release_value(), position, *sheet); 435 break; 436 } 437 } 438 439 auto type_name = obj.has("type"sv) ? obj.get_deprecated_string("type"sv).value_or({}) : "Numeric"; 440 cell->set_type(type_name); 441 442 auto type_meta = obj.get_object("type_metadata"sv); 443 if (type_meta.has_value()) { 444 auto& meta_obj = type_meta.value(); 445 auto meta = cell->type_metadata(); 446 if (auto value = meta_obj.get_i32("length"sv); value.has_value()) 447 meta.length = value.value(); 448 if (auto value = meta_obj.get_deprecated_string("format"sv); value.has_value()) 449 meta.format = value.value(); 450 if (auto value = meta_obj.get_deprecated_string("alignment"sv); value.has_value()) { 451 auto alignment = Gfx::text_alignment_from_string(*value); 452 if (alignment.has_value()) 453 meta.alignment = alignment.value(); 454 } 455 read_format(meta.static_format, meta_obj); 456 457 cell->set_type_metadata(move(meta)); 458 } 459 460 auto conditional_formats = obj.get_array("conditional_formats"sv); 461 auto cformats = cell->conditional_formats(); 462 if (conditional_formats.has_value()) { 463 conditional_formats->for_each([&](const auto& fmt_val) { 464 if (!fmt_val.is_object()) 465 return IterationDecision::Continue; 466 467 auto& fmt_obj = fmt_val.as_object(); 468 auto fmt_cond = fmt_obj.get_deprecated_string("condition"sv).value_or({}); 469 if (fmt_cond.is_empty()) 470 return IterationDecision::Continue; 471 472 ConditionalFormat fmt; 473 fmt.condition = move(fmt_cond); 474 read_format(fmt, fmt_obj); 475 cformats.append(move(fmt)); 476 477 return IterationDecision::Continue; 478 }); 479 cell->set_conditional_formats(move(cformats)); 480 } 481 482 auto evaluated_format = obj.get_object("evaluated_formats"sv); 483 if (evaluated_format.has_value()) { 484 auto& evaluated_format_obj = evaluated_format.value(); 485 auto& evaluated_fmts = cell->evaluated_formats(); 486 487 read_format(evaluated_fmts, evaluated_format_obj); 488 } 489 490 sheet->m_cells.set(position, cell.release_nonnull()); 491 return IterationDecision::Continue; 492 }); 493 } 494 495 return sheet; 496} 497 498Position Sheet::written_data_bounds(Optional<size_t> column_index) const 499{ 500 Position bound; 501 for (auto const& entry : m_cells) { 502 if (entry.value->data().is_empty()) 503 continue; 504 if (column_index.has_value() && entry.key.column != *column_index) 505 continue; 506 if (entry.key.row >= bound.row) 507 bound.row = entry.key.row; 508 if (entry.key.column >= bound.column) 509 bound.column = entry.key.column; 510 } 511 512 return bound; 513} 514 515/// The sheet is allowed to have nonstandard column names 516/// this checks whether all existing columns are 'standard' 517/// (i.e. as generated by 'String::bijective_base_from()' 518bool Sheet::columns_are_standard() const 519{ 520 for (size_t i = 0; i < m_columns.size(); ++i) { 521 if (m_columns[i] != DeprecatedString::bijective_base_from(i)) 522 return false; 523 } 524 525 return true; 526} 527 528JsonObject Sheet::to_json() const 529{ 530 JsonObject object; 531 object.set("name", m_name); 532 533 auto save_format = [](auto const& format, auto& obj) { 534 if (format.foreground_color.has_value()) 535 obj.set("foreground_color", format.foreground_color.value().to_deprecated_string()); 536 if (format.background_color.has_value()) 537 obj.set("background_color", format.background_color.value().to_deprecated_string()); 538 }; 539 540 auto bottom_right = written_data_bounds(); 541 542 if (!columns_are_standard()) { 543 auto columns = JsonArray(); 544 for (auto& column : m_columns) 545 columns.append(column); 546 object.set("columns", move(columns)); 547 } 548 object.set("rows", bottom_right.row + 1); 549 550 JsonObject cells; 551 for (auto& it : m_cells) { 552 StringBuilder builder; 553 builder.append(column(it.key.column)); 554 builder.appendff("{}", it.key.row); 555 auto key = builder.to_deprecated_string(); 556 557 JsonObject data; 558 data.set("kind", it.value->kind() == Cell::Kind::Formula ? "Formula" : "LiteralString"); 559 if (it.value->kind() == Cell::Formula) { 560 auto& vm = interpreter().vm(); 561 data.set("source", it.value->data()); 562 auto json = interpreter().realm().global_object().get_without_side_effects("JSON"); 563 auto stringified_or_error = JS::call(vm, json.as_object().get_without_side_effects("stringify").as_function(), json, it.value->evaluated_data()); 564 VERIFY(!stringified_or_error.is_error()); 565 data.set("value", stringified_or_error.release_value().to_string_without_side_effects().release_value_but_fixme_should_propagate_errors().to_deprecated_string()); 566 } else { 567 data.set("value", it.value->data()); 568 } 569 570 // Set type & meta 571 auto& type = it.value->type(); 572 auto& meta = it.value->type_metadata(); 573 data.set("type", type.name()); 574 575 JsonObject metadata_object; 576 metadata_object.set("length", meta.length); 577 metadata_object.set("format", meta.format); 578 metadata_object.set("alignment", Gfx::to_string(meta.alignment)); 579 save_format(meta.static_format, metadata_object); 580 581 data.set("type_metadata", move(metadata_object)); 582 583 // Set conditional formats 584 JsonArray conditional_formats; 585 for (auto& fmt : it.value->conditional_formats()) { 586 JsonObject fmt_object; 587 fmt_object.set("condition", fmt.condition); 588 save_format(fmt, fmt_object); 589 590 conditional_formats.append(move(fmt_object)); 591 } 592 593 data.set("conditional_formats", move(conditional_formats)); 594 595 auto& evaluated_formats = it.value->evaluated_formats(); 596 JsonObject evaluated_formats_obj; 597 598 save_format(evaluated_formats, evaluated_formats_obj); 599 data.set("evaluated_formats", move(evaluated_formats_obj)); 600 601 cells.set(key, move(data)); 602 } 603 object.set("cells", move(cells)); 604 605 return object; 606} 607 608Vector<Vector<DeprecatedString>> Sheet::to_xsv() const 609{ 610 Vector<Vector<DeprecatedString>> data; 611 612 auto bottom_right = written_data_bounds(); 613 614 // First row = headers. 615 size_t column_count = m_columns.size(); 616 if (columns_are_standard()) { 617 column_count = bottom_right.column + 1; 618 Vector<DeprecatedString> cols; 619 for (size_t i = 0; i < column_count; ++i) 620 cols.append(m_columns[i]); 621 data.append(move(cols)); 622 } else { 623 data.append(m_columns); 624 } 625 626 for (size_t i = 0; i <= bottom_right.row; ++i) { 627 Vector<DeprecatedString> row; 628 row.resize(column_count); 629 for (size_t j = 0; j < column_count; ++j) { 630 auto cell = at({ j, i }); 631 if (cell) { 632 auto result = cell->typed_display(); 633 if (result.has_value()) 634 row[j] = result.value(); 635 } 636 } 637 638 data.append(move(row)); 639 } 640 641 return data; 642} 643 644RefPtr<Sheet> Sheet::from_xsv(Reader::XSV const& xsv, Workbook& workbook) 645{ 646 auto cols = xsv.headers(); 647 auto rows = xsv.size(); 648 649 auto sheet = adopt_ref(*new Sheet(workbook)); 650 if (xsv.has_explicit_headers()) { 651 sheet->m_columns = cols; 652 } else { 653 sheet->m_columns.ensure_capacity(cols.size()); 654 for (size_t i = 0; i < cols.size(); ++i) 655 sheet->m_columns.append(DeprecatedString::bijective_base_from(i)); 656 } 657 for (size_t i = 0; i < max(rows, Sheet::default_row_count); ++i) 658 sheet->add_row(); 659 if (sheet->columns_are_standard()) { 660 for (size_t i = sheet->m_columns.size(); i < Sheet::default_column_count; ++i) 661 sheet->add_column(); 662 } 663 664 for (auto row : xsv) { 665 for (size_t i = 0; i < cols.size(); ++i) { 666 auto str = row[i]; 667 if (str.is_empty()) 668 continue; 669 Position position { i, row.index() }; 670 auto cell = make<Cell>(str, position, *sheet); 671 sheet->m_cells.set(position, move(cell)); 672 } 673 } 674 675 return sheet; 676} 677 678JsonObject Sheet::gather_documentation() const 679{ 680 JsonObject object; 681 const JS::PropertyKey doc_name { "__documentation" }; 682 683 auto add_docs_from = [&](auto& it, auto& global_object) { 684 auto value = global_object.get(it.key).release_value(); 685 if (!value.is_function() && !value.is_object()) 686 return; 687 688 auto& value_object = value.is_object() ? value.as_object() : value.as_function(); 689 if (!value_object.has_own_property(doc_name).release_value()) 690 return; 691 692 auto doc = value_object.get(doc_name).release_value(); 693 if (!doc.is_string()) 694 return; 695 696 JsonParser parser(doc.to_string_without_side_effects().release_value_but_fixme_should_propagate_errors()); 697 auto doc_object = parser.parse(); 698 699 if (!doc_object.is_error()) 700 object.set(it.key.to_display_string(), doc_object.value()); 701 else 702 dbgln("Sheet::gather_documentation(): Failed to parse the documentation for '{}'!", it.key.to_display_string()); 703 }; 704 705 for (auto& it : interpreter().realm().global_object().shape().property_table()) 706 add_docs_from(it, interpreter().realm().global_object()); 707 708 for (auto& it : global_object().shape().property_table()) 709 add_docs_from(it, global_object()); 710 711 m_cached_documentation = move(object); 712 return m_cached_documentation.value(); 713} 714 715DeprecatedString Sheet::generate_inline_documentation_for(StringView function, size_t argument_index) 716{ 717 if (!m_cached_documentation.has_value()) 718 gather_documentation(); 719 720 auto& docs = m_cached_documentation.value(); 721 auto entry = docs.get_object(function); 722 if (!entry.has_value()) 723 return DeprecatedString::formatted("{}(...???{})", function, argument_index); 724 725 auto& entry_object = entry.value(); 726 size_t argc = entry_object.get_integer<int>("argc"sv).value_or(0); 727 auto argnames_value = entry_object.get_array("argnames"sv); 728 if (!argnames_value.has_value()) 729 return DeprecatedString::formatted("{}(...{}???{})", function, argc, argument_index); 730 auto& argnames = argnames_value.value(); 731 StringBuilder builder; 732 builder.appendff("{}(", function); 733 for (size_t i = 0; i < (size_t)argnames.size(); ++i) { 734 if (i != 0 && i < (size_t)argnames.size()) 735 builder.append(", "sv); 736 if (i == argument_index) 737 builder.append('<'); 738 else if (i >= argc) 739 builder.append('['); 740 builder.append(argnames[i].to_deprecated_string()); 741 if (i == argument_index) 742 builder.append('>'); 743 else if (i >= argc) 744 builder.append(']'); 745 } 746 747 builder.append(')'); 748 return builder.to_deprecated_string(); 749} 750 751DeprecatedString Position::to_cell_identifier(Sheet const& sheet) const 752{ 753 return DeprecatedString::formatted("{}{}", sheet.column(column), row); 754} 755 756URL Position::to_url(Sheet const& sheet) const 757{ 758 URL url; 759 url.set_scheme("spreadsheet"); 760 url.set_host("cell"); 761 url.set_paths({ DeprecatedString::number(getpid()) }); 762 url.set_fragment(to_cell_identifier(sheet)); 763 return url; 764} 765 766CellChange::CellChange(Cell& cell, DeprecatedString const& previous_data) 767 : m_cell(cell) 768 , m_previous_data(previous_data) 769{ 770 m_new_data = cell.data(); 771} 772 773}