diff options
Diffstat (limited to 'doc/cpp/quickstart/using_model_context.rst')
-rw-r--r-- | doc/cpp/quickstart/using_model_context.rst | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/doc/cpp/quickstart/using_model_context.rst b/doc/cpp/quickstart/using_model_context.rst new file mode 100644 index 0000000..f37bd5f --- /dev/null +++ b/doc/cpp/quickstart/using_model_context.rst @@ -0,0 +1,318 @@ + +.. highlight:: cpp + +.. _quickstart-model-context: + +Using model_context class +========================= + +Create a model context instance +------------------------------- + +When using ixion, the very first step is to create a :cpp:class:`~ixion::model_context` +instance:: + + ixion::model_context cxt; + +The :cpp:class:`~ixion::model_context` class represents a document model data +store that stores cell values spreading over one or more sheets. At the time of construction, +the model contains no sheets. So the obvious next step is to insert a sheet:: + + // First and foremost, insert a sheet. + cxt.append_sheet("MySheet"); + +The :cpp:func:`~ixion::model_context::append_sheet` method will append a new sheet to +the model. You need to give a name when appending a sheet, and the name must be unique +for each sheet. + +.. note:: + + Each sheet has a fixed size which cannot be changed once the :cpp:class:`~ixion::model_context` + object is instantiated. The default sheet size is 1048576 rows by 16384 columns. You can + specify a custom sheet size by passing a desired sheet size value to the + :cpp:class:`~ixion::model_context` constructor at the time of instantiation. + + +Populate model context with values +---------------------------------- + +Now that you have your first sheet inserted, let's put in some numeric values. In this example, +we'll insert into A1:A10 their respective row positions. To insert a numeric value, you use +:cpp:func:`~ixion::model_context::set_numeric_cell` which takes the position of the cell as its +first argument and the value to set as its second argument. You need to use :cpp:class:`~ixion::abs_address_t` +to specify a cell position. + +:: + + // Now, populate it with some numeric values in A1:A10. + for (ixion::abs_address_t pos(0, 0, 0); pos.row <= 9; ++pos.row) + { + double value = pos.row + 1.0; // Set the row position + 1 as the cell value. + cxt.set_numeric_cell(pos, value); + } + +Note that, since row and column positions are internally 0-based, we add one to emulate how the row +positions are presented in typical spreadsheet program. + +Inserting a string value can be done via :cpp:func:`~ixion::model_context::set_string_cell` in one +of two ways. The first way is to store the value to a type that decays to ``std::string_view``, such +as ``std::string``, char array, or string literal, and pass it to the method directly:: + + // Insert a string value into B2. + ixion::abs_address_t B2(0, 1, 1); + std::string s = "This cell contains a string value."; + cxt.set_string_cell(B2, s); + + // Insert a literal string value into B3. + ixion::abs_address_t B3(0, 2, 1); + cxt.set_string_cell(B3, "This too contains a string value."); + +The second way is to add your string to the model_context's internal string pool first which will return its +string ID, and pass that ID to the method:: + + // Insert a string value into B4 via string identifier. + ixion::string_id_t sid = cxt.add_string("Yet another string value."); + ixion::abs_address_t B4(0, 3, 1); + cxt.set_string_cell(B4, sid); + +The model_context class has two methods for inserting a string to the string pool: +:cpp:func:`~ixion::model_context::add_string` and :cpp:func:`~ixion::model_context::append_string`. The +:cpp:func:`~ixion::model_context::add_string` method checks for an existing entry with the same string value +upon each insertion attempt, and it will not insert the new value if the value already exists in the pool. +The :cpp:func:`~ixion::model_context::append_string` method, on the other hand, does not check the pool for +an existing value and always inserts the value. The :cpp:func:`~ixion::model_context::append_string` method +is appropriate if you know all your string entries ahead of time and wish to bulk-insert them. Otherwise +using :cpp:func:`~ixion::model_context::add_string` is appropriate in most cases. + + +Insert a formula cell into model context +---------------------------------------- + +Inserting a formula cell requires a few extra steps. First, you need to tokenize your formula string, and +to do that, you need to create an instance of :cpp:class:`~ixion::formula_name_resolver`. The +formula_name_resolver class is responsible for resolving "names" into references, functions, and named +expressions names. Ixion provides multiple types of name resolvers, and you specify its type when passing +an enum value of type :cpp:enum:`~ixion::formula_name_resolver_t` when calling its static +:cpp:func:`ixion::formula_name_resolver::get` function. In this example, we'll be using the Excel A1 +syntax:: + + // Tokenize formula string first. + std::unique_ptr<ixion::formula_name_resolver> resolver = + ixion::formula_name_resolver::get(ixion::formula_name_resolver_t::excel_a1, &cxt); + +You can also optionally pass a memory address of your :cpp:class:`~ixion::model_context` instance which is +required for resolving sheet names. You can pass a ``nullptr`` if you don't need to resolve sheet names. + +Next, let's create a formula string we want to tokenize. Here, we are inserting a formula expression +**SUM(A1:A10)** into cell A11:: + + ixion::abs_address_t A11(0, 10, 0); + ixion::formula_tokens_t tokens = ixion::parse_formula_string(cxt, A11, *resolver, "SUM(A1:A10)"); + +To tokenize a formula string, you call the :cpp:func:`ixion::parse_formula_string` function and pass + +* a model_context instance +* the position of the cell to insert the formula into, +* a formula_name_resolver instance, and +* the formula string to tokenize. + +The function will then return a sequence of tokens representing the original formula string. Once you +have the tokens, you can finally pass them to your model_context instance via +:cpp:func:`~ixion::model_context::set_formula_cell`:: + + // Set the tokens into the model. + const ixion::formula_cell* cell = cxt.set_formula_cell(A11, std::move(tokens)); + +There is a few things to note. First, you need to *move* your tokens to the method since instances of +type :cpp:type:`ixion::formula_tokens_t` are non-copyable and only movable. Second, the method returns +a pointer to the formula cell instance that just got inserted into the model. We are saving it here +to use it in the next step below. + +When inserting a formula cell, you need to "register" it so that the model can record its reference +dependencies via :cpp:func:`~ixion::register_formula_cell`:: + + // Register this formula cell for automatic dependency tracking. + ixion::register_formula_cell(cxt, A11, cell); + +Without registering formula cells, you won't be able to determine which formula cells to re-calculate +for given modified cells. Here we are passing the pointer to the formula cell returned from the previous +call. This is optional, and you can pass a ``nullptr`` instead. But by passing it you will avoid the +overhead of searching for the cell instance from the model. + + +Calculate formula cell +---------------------- + +Now that we have the formula cell in, let's run our first calculation. To calcualte formula cells, you +need to first specify a range of modified cells in order to query for all formula cells affected by it +either directly or indirectly, which we refer to as "dirty" formula cells. Since this is our initial +calculation, we can simply specify the entire sheet to be "modified" which will effectively trigger all +formula cells:: + + ixion::rc_size_t sheet_size = cxt.get_sheet_size(); + ixion::abs_range_t entire_sheet(0, 0, 0, sheet_size.row, sheet_size.column); // sheet, row, column, row span, column span + ixion::abs_range_set_t modified_cells{entire_sheet}; + +We will then pass it to :cpp:func:`~ixion::query_and_sort_dirty_cells` to get a sequence of formula cell +addresses to calculate:: + + // Determine formula cells that need re-calculation given the modified cells. + // There should be only one formula cell in this example. + std::vector<ixion::abs_range_t> dirty_cells = ixion::query_and_sort_dirty_cells(cxt, modified_cells); + cout << "number of dirty cells: " << dirty_cells.size() << endl; + +Since so far we only have one formula cell, this should only return one range with the size of one row and one column. You +will see the following output: + +.. code-block:: text + + number of dirty cells: 1 + +Let's inspect which cell it actually refers to:: + + cout << "dirty cell: " << dirty_cells[0] << endl; + +which will print: + +.. code-block:: text + + dirty cell: (sheet:0; row:10; column:0)-(sheet:0; row:10; column:0) + +confirming that it certainly points to cell A11. Finally, pass this to :cpp:func:`~ixion::calculate_sorted_cells`:: + + // Now perform calculation. + ixion::calculate_sorted_cells(cxt, dirty_cells, 0); + +to calculate cell A11. After that, you can retrieve the result of the calculation by calling +:cpp:func:`~ixion::model_context::get_numeric_value` for A11:: + + double value = cxt.get_numeric_value(A11); + cout << "value of A11: " << value << endl; + +You will see the following output: + +.. code-block:: text + + value of A11: 55 + + +Modify formula cell +------------------- + +Let's say you need to overwrite the formula in A11 to something else. The steps you need to take +are very similar to the steps for inserting a brand-new formula cell, the only difference being +that you need to "unregister" the old formula cell before overwriting it. + +Let's go through this step by step. First, create new tokens to insert:: + + // Insert a new formula to A11. + tokens = ixion::parse_formula_string(cxt, A11, *resolver, "AVERAGE(A1:A10)"); + +This time we are inserting the formula **AVERAGE(A1:A10)** in A11 to overwrite the previous one +**SUM(A1:A10)**. Before inserting these tokens, first unregister the current formula cell:: + + // Before overwriting, make sure to UN-register the old cell. + ixion::unregister_formula_cell(cxt, A11); + +This will remove the dependency information of the old formula from the model's internal tracker. +Once that's done, the rest is the same as inserting a new formula:: + + // Set and register the new formula cell. + cell = cxt.set_formula_cell(A11, std::move(tokens)); + ixion::register_formula_cell(cxt, A11, cell); + +Let's re-calculate the new formula cell. The re-calculation steps are also very similar to the initial +calculation steps. The first step is to query for all dirty formula cells. This time, however, we don't +query based on which formula cells are affected by modified cells, which we'll specify as none. Instead, +we query based on which formula cells have been modified, which in this case is A11:: + + // This time, we know that none of the cell values have changed, but the + // formula A11 is updated & needs recalculation. + ixion::abs_range_set_t modified_formula_cells{A11}; + dirty_cells = ixion::query_and_sort_dirty_cells(cxt, ixion::abs_range_set_t(), &modified_formula_cells); + cout << "number of dirty cells: " << dirty_cells.size() << endl; + +As is the first calculation, you should only get one dirty cell address from the :cpp:func:`~ixion::query_and_sort_dirty_cells` +call. Running the above code should produce: + +.. code-block:: text + + number of dirty cells: 1 + +The rest should be familiar:: + + // Perform calculation again. + ixion::calculate_sorted_cells(cxt, dirty_cells, 0); + + value = cxt.get_numeric_value(A11); + cout << "value of A11: " << value << endl; + +You should see the following output when finished: + +.. code-block:: text + + value of A11: 5.5 + + +Formula cell with no references +------------------------------- + +Next example shows a scenario where you want to overwrite a cell in A10, which +currently stores a numeric value, with a formula cell that references no other +cells. Let's add the new formula cell first:: + + // Overwrite A10 with a formula cell with no references. + ixion::abs_address_t A10(0, 9, 0); + tokens = ixion::parse_formula_string(cxt, A10, *resolver, "(100+50)/2"); + cxt.set_formula_cell(A10, std::move(tokens)); + +Here, we are not registering this cell since it contains no references hence it +does not need to be tracked by dependency tracker. Also, since the previous +cell in A10 is not a formula cell, there is no cell to unregister. + +.. warning:: + + Technically speaking, every formula cell that contains references to other + cells or contains at least one volatile function needs to be registered. + Since registering a formula cell that doesn't need to be registered is + entirely harmless (albeit a slight overhead), it's generally a good idea to + register every new formula cell regardless of its content. + + Likewise, unregistering a formula cell that didn't need to be registered + (or wasn't registered) is entirely harmless. Even unregistering a cell + that didn't contain a formula cell is harmless, and essentially does + nothing. As such, it's probably a good idea to unregister a cell whenever + a new cell value is being placed. + +Let's obtain all formula cells in need to re-calculation:: + + modified_formula_cells = { A10 }; + dirty_cells = ixion::query_and_sort_dirty_cells(cxt, ixion::abs_range_set_t(), &modified_formula_cells); + cout << "number of dirty cells: " << dirty_cells.size() << endl; + +Here, we are only passing one modified formula cell which is A10, and no other +cells being modified. Since cell A11 references ``A1:A10`` and A10's value has +changed, this should also trigger A11 for re-calculation. Running this code +should produce the following output: + +.. code-block:: text + + number of dirty cells: 2 + +Let's calculate all affected formula cells and check the results of A10 and A11:: + + ixion::calculate_sorted_cells(cxt, dirty_cells, 0); + value = cxt.get_numeric_value(A10); + cout << "value of A10: " << value << endl; + value = cxt.get_numeric_value(A11); + cout << "value of A11: " << value << endl; + +Running this code should produce the following output: + +.. code-block:: text + + value of A10: 75 + value of A11: 12 + +The complete source code of this example is avaiable `here <https://gitlab.com/ixion/ixion/-/blob/master/doc_example/model_context_simple.cpp>`_. + |