Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to code the header at the end? #28

Open
pkbehera opened this issue Jan 3, 2020 · 13 comments
Open

Is it possible to code the header at the end? #28

pkbehera opened this issue Jan 3, 2020 · 13 comments

Comments

@pkbehera
Copy link

pkbehera commented Jan 3, 2020

I am dynamically populating a table in a function which returns ta string by calling to_string() in the end. My logic is such that I may end up in not adding any data at all to the table. In such cases I don't want to add any header to the table, so that to_string() returns an empty string.

Hence a feature to add the header at the end after knowing the number of rows (or whenever we wish) will be helpful. Currently I use workarounds to return an empty string from my function, as adding the header afterwards adds it after all the data added till then.

@seleznevae
Copy link
Owner

seleznevae commented Jan 4, 2020

Hi!
I think it is not possible to do it at the moment.
At the moment it is possible to change current cell where content will be inserted. In current implementation after that new content will overwrite existing one.
As far as I understand you want to get another behavior - new inserted content should be in a brand new row and the old rows should just go below.
Am I correct that you want to do something like that:

// in c++ api
std::string some_function()
{
  fort::char_table tbl;
  // populate tbl with data

  // add header if some data were really added
  if (!tbl.is_empty()) {
     tbl.set_cur_cell(0, 0);
     tbl << fort::header << "hdr1" << "hdr2" << "hdr3";
  }
  return tbl.to_string();
}

So as a generalization: you want somehow change (or be able to change) current strategy "overwrite old content with the new one" with the new strategy "insert new content, old content should be preserved and should be below" for content adding functions. Do I understand your problem correctly?

@pkbehera
Copy link
Author

pkbehera commented Jan 4, 2020

yes the snippet you posted is what I want to do.. For me as an user, the header line should always be the at the top of the table; irrespective of the time when it is added/coded.

int main()
{
    fort::char_table table;
    table << fort::header << "N"
          << "Driver"
          << "Time"
          << "Avg Speed" << fort::endr;
    table << "1"
          << "Ricciardo"
          << "1:25.945"
          << "47.362" << fort::endr;
    table << "2"
          << "Hamilton"
          << "1:26.373"
          << "35.02" << fort::endr;
    std::cout << table.to_string() << std::endl;
}

please note that to add the header we need to do insert a special attribute to the able i.e. table << fort::header

So the table knows which is the header row. So why must we add the header the very beginning? Why can we just add it whenever we want to?

@pkbehera
Copy link
Author

pkbehera commented Jan 4, 2020

In my view all these code samples should produce the same output, but they don't:

int main()
{
    fort::char_table table;
    table << fort::header << "N"
          << "Driver"
          << "Time"
          << "Avg Speed" << fort::endr;
    table << "1"
          << "Ricciardo"
          << "1:25.945"
          << "47.362" << fort::endr;
    table << "2"
          << "Hamilton"
          << "1:26.373"
          << "35.02" << fort::endr;
    std::cout << table.to_string() << std::endl;
}
int main()
{
    fort::char_table table;
    table << "1"
          << "Ricciardo"
          << "1:25.945"
          << "47.362" << fort::endr;
    table << "2"
          << "Hamilton"
          << "1:26.373"
          << "35.02" << fort::endr;
    table << fort::header << "N"
          << "Driver"
          << "Time"
          << "Avg Speed" << fort::endr;
    std::cout << table.to_string() << std::endl;
}
int main()
{
    fort::char_table table;
    table << "1"
          << "Ricciardo"
          << "1:25.945"
          << "47.362" << fort::endr;
    table << fort::header << "N"
          << "Driver"
          << "Time"
          << "Avg Speed" << fort::endr;
    table << "2"
          << "Hamilton"
          << "1:26.373"
          << "35.02" << fort::endr;
    std::cout << table.to_string() << std::endl;
}

@pkbehera
Copy link
Author

pkbehera commented Jan 4, 2020

btw what does << fort::header do? Apparently its just equivalent to << fort::endr << fort::separator

@pkbehera pkbehera changed the title Is it possible to add the header at the end? Is it possible to code the header at the end? Jan 4, 2020
@seleznevae
Copy link
Owner

seleznevae commented Jan 4, 2020

I got your idea.
It doesn't work the way you think because in libfort there is no a distinct entity such as header. All rows (ordinary rows and headers) are pretty much the same. In libfort header is just a property of the row. Rows that have this property just have another border and that's all.
Approach when we treat headers as some kind of special entity is simply not flexible (e.g. you can't have headers at the bottom, headers in the middle of the table, multiple headers etc.).
fort::header just sets property for the row (it equivalent to ft_set_cell_prop(table_, FT_CUR_ROW, FT_ANY_ROW, FT_CPROP_ROW_TYPE, FT_ROW_HEADER); in C API)

@pkbehera
Copy link
Author

pkbehera commented Jan 4, 2020

well, spreadsheet applications for example treat headers as special entities, and allow to customize the header in many ways, for example put it in every page in a printout. :)

@seleznevae
Copy link
Owner

seleznevae commented Jan 4, 2020

Yes, I understand what you mean but as I said this approach with treating headers and ordinary rows differently is more complicated and also I believe current approach is more flexible (e.g. you can have several headers in one table easily:

int main()
{
    fort::char_table table;
    table << fort::header
        << "N" << "Driver" << "Time" << "Avg Speed" << fort::endr
        << "1" << "Ricciardo" << "1:25.945" << "47.362" << fort::endr
        << "2" << "Hamilton" << "1:26.373" << "35.02" << fort::endr
        << "3" << "Verstappen" << "1:26.469" << "29.78" << fort::endr;

    table << fort::header
        << "Some field" << "" << "" << "Avg Speed" << fort::endr
        << "fld1" << "" << "" << "47.362" << fort::endr
        << "fld2" << "" << "" << "29.78" << fort::endr;
    table.cell(4, 0).set_cell_span(3);
    table.cell(5, 0).set_cell_span(3);
    table.cell(6, 0).set_cell_span(3);

    std::cout << table.to_string() << std::endl;
}

output:

+---+------------+----------+-----------+
| N | Driver     | Time     | Avg Speed |
+---+------------+----------+-----------+
| 1 | Ricciardo  | 1:25.945 | 47.362    |
| 2 | Hamilton   | 1:26.373 | 35.02     |
| 3 | Verstappen | 1:26.469 | 29.78     |
+---+------------+----------+-----------+
| Some field                | Avg Speed |
+---------------------------+-----------+
| fld1                      | 47.362    |
| fld2                      | 29.78     |
+---------------------------+-----------+

All that logic with some special behavior can be added externally in some kind of a wrapper(maybe I am wrong) or user functions. So I prefer to keep things simple and implement the most basic functionality.

Returning to you initial question. I can suggest adding new property to the tables (lets call it CONTENT_ADDING_STRATEGY (with 2 possible values REPLACE(default one) and INSERT). I think this approach is rather flexible. Also it will fit well for some other scenarios.

In this case your code will look like this.

// in c++ api
std::string some_function()
{
  fort::char_table tbl;
  // populate tbl with data
  ...

  // add header at the top if some data were really added
  if (!tbl.is_empty()) {
     // you explicitly add header in place of the 0th row,
     // all other rows will be shifted
     tbl.set_adding_strategy(fort::strategy::INSERT);
     tbl.set_cur_cell(0, 0); 
     tbl << fort::header << "hdr1" << "hdr2" << "hdr3";
  }
  return tbl.to_string();
}

In case you don't like to set this property (tbl.set_adding_strategy(fort::strategy::INSERT); )for each table it will be possible set it one time during some initialization procedure:

fort::default_props().set_adding_strategy(fort::strategy::INSERT);

Does it suit you? Or maybe you don't like it?

@seleznevae
Copy link
Owner

seleznevae commented Jan 8, 2020

Hi. I implemented adding_strategies and merged branch to develop.
Now it is possible to add rows (and headers) at any time to any place in the table.
Here is a simple example:

int main()
{
    fort::char_table table;
    table.set_adding_strategy(fort::add_strategy::insert);

    // Fill table with data
    table << "1" << "2" << fort::endr;
    table << "3" << "4" << fort::endr;

    // explicitly add header as the top row
    table.set_cur_cell(0, 0);
    table << fort::header
          << "hdr1" << "hdr2" << fort::endr;

    std::cout << table.to_string() << std::endl;
}

Output:

+------+------+
| hdr1 | hdr2 |
+------+------+
| 1    | 2    |
| 3    | 4    |
+------+------+

The syntax is rather verbose but it is simple and straightforward. You have to explicitly specify adding_strategy as replace. After that when you want to insert some data inside the table you explicitly set current cell with set_cur_cell and then use ordinary functions to fill the cells.

If this solution doesn't solve your problems fill free to write here your suggestions or problems that you encounter.

@pkbehera
Copy link
Author

pkbehera commented Jan 8, 2020

thanks, so now we can code the header either at the start or at the very end. I tried the following

int main()
{
    fort::char_table table;
    table.set_adding_strategy(fort::add_strategy::insert);

    // Fill table with data
    table << "1"
          << "2" << fort::endr;
    // explicitly add header as the top row
    table.set_cur_cell(0, 0);
    table << fort::header << "hdr1"
          << "hdr2" << fort::endr;
    table << "3"
          << "4" << fort::endr;
    std::cout << table.to_string() << std::endl;
}

and this was the output

+------+------+
| hdr1 | hdr2 |
+------+------+
| 3    | 4    |
| 1    | 2    |
+------+------+

adding more data after adding the header gives counter-intuitive results.

@pkbehera pkbehera closed this as completed Jan 8, 2020
@pkbehera
Copy link
Author

pkbehera commented Jan 8, 2020

Pressed the wrong button and the issue got closed...

@pkbehera pkbehera reopened this Jan 8, 2020
@seleznevae
Copy link
Owner

seleznevae commented Jan 8, 2020

That's expected behavior.
As I already said above there is nothing special with headers. They are ordinary rows. That's library design principle.
Functions set_adding_strategy and set_cur_cell also ordinary functions. They don't do any specific for headers. They designed in a general way so that they affect all inserted cells(either in headers or in ordinary rows). There is no reason to limit the functionality of adding cells to random place only for headers. Therefore when you specify that you will insert data to the beginning of the table library will add there all rows (headers and ordinary rows). It's a library user responsibility to specify where to place data. So in your last example if you want to add 3 and 4 to the end you have to explicitly specify it with table.set_curl_cell(2, 0); before pushing these data. By the way it is a good use case to add a function to get the number of the rows from the issue #26 ( so that when you add something in the middle and then decide to continue pushing to the end you can do table.set_cur_cell(table.row_count(), 0);).

However, maybe what you want can be implemented with a little bit different API. Something like that:

int main()
{
    fort::char_table table;
    table.set_adding_strategy(fort::add_strategy::insert);

    // Fill table with data
    table << "1" << "2" << fort::endr;

    // Note that I explicitly specify 0th row (table[0]) where I will insert the header
    table[0] << fort::header << "hdr1"  << "hdr2" << fort::endr;

    table << "3" << "4" << fort::endr;
    std::cout << table.to_string() << std::endl;
}

So the idea of this API is that we can apply modifiing operations not on the table but on a particular row and that in this case this operations won't affect position of the current cell. I need to think about this possibility thoroughly and also how it will look with current modifying operation on distinct cells (like tbl[2][3] = "some_data"; ) which affect position of the current cell. So I am not sure if it will be ok to implement it. Need to think about it anyway.

@brlcad
Copy link
Contributor

brlcad commented Nov 29, 2020

Wouldn't a header at the end typically be called a footer? :)

Since it is just decorative, perhaps renaming it as a cell/row border bolding or callout property? Something like: tables[123] << fort::emphasize << "Total:" << "123" << fort::endr;

@pkbehera
Copy link
Author

pkbehera commented Dec 1, 2020

appending the table at the end will be a footer, but pre-pending the table at the end will still be a header! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants