diff --git a/README.md b/README.md index 1db9c80..75c7d6f 100644 --- a/README.md +++ b/README.md @@ -161,13 +161,14 @@ sheet = ps.Spreadsheet.create_new_sheet( ``` Other (keywords) arguments: -1. `rows_labels (List[str])`: _(optional)_ List of masks (aliases) -for row names. -2. `columns_labels (List[str])`: _(optional)_ List of masks (aliases) -for column names. +1. `rows_labels (List[Union[str, SkippedLabel]])`: _(optional)_ List of masks +(aliases) for row names. +2. `columns_labels (List[Union[str, SkippedLabel]])`: _(optional)_ List of +masks (aliases) for column names. If the instance of SkippedLabel is +used, the export skips this label. 3. `rows_help_text (List[str])`: _(optional)_ List of help texts for each row. 4. `columns_help_text (List[str])`: _(optional)_ List of help texts for each -column. +column. If the instance of SkippedLabel is used, the export skips this label. 5. `excel_append_row_labels (bool)`: _(optional)_ If True, one column is added on the beginning of the sheet as a offset for labels. 6. `excel_append_column_labels (bool)`: _(optional)_ If True, one row is @@ -303,6 +304,12 @@ or set of rows and columns). Following code, select the third column: ```python sheet.iloc[:,2] ``` +On the other hand +```python +sheet.loc[:,'Handy column'] +``` +selects all the rows in the columns with the label _'Handy column'_. + You can again set the values in the slice to some constant, or the array of constants, or to another cell, or to the result of some computation. ```python @@ -312,6 +319,33 @@ sheet.iloc[:,2] = sheet.iloc[1,3] # Just a reference to a cell ``` Technically the slice is the instance of `CellSlice` class. +There are two ways how to slice, either using `.loc` or `.iloc` attribute. +Where `iloc` uses integer position and `loc` uses label of the position +(as a string). + +By default the right-most value is excluded when defining slices. If you want +to use right-most value indexing, use one of the methods described below. + +#### Slicing using method (with the right-most value included option) +Sometimes, it is quite helpful to use a slice that includes the right-most +value. There are two functions for this purpose: +1. `sheet.iloc.get_slice(ROW_INDEX, COLUMN_INDEX, include_right=[True/False])`: +This way is equivalent to the one presented above with square brackets `[]`. +The difference is the key-value attribute `include_right` that enables the +possibility of including the right-most value of the slice (default value is +False). If you want to use slice as your index, you need to pass some `slice` +object to one (or both) of the indices. For example: +`sheet.iloc.get_slice(slice(0, 7), 3, include_right=True])` selects first nine +rows (because 8th row - right-most one - is included) from the fourth column +of the sheet _(remember, all is indexed from zero)_. + +2. `sheet.iloc.set_slice(ROW_INDEX, COLUMN_INDEX, VALUE, +include_right=[True/False])`: this command set slice to _VALUE_ in the similar +logic as when you call `get_slice` method (see the first point). + +There are again two possibilities, either to use `iloc` with integer position +or to use `loc` with labels. + #### Aggregate functions The slice itself can be used for computations using aggregate functions. @@ -677,7 +711,8 @@ sheet.to_excel( "value": "Value", "description": "Description" }), - values_only: bool = False + values_only: bool = False, + skipped_label_replacement: str = '' ) ``` The only required argument is the path to the destination file (positional @@ -699,6 +734,8 @@ for the sheet with variables (first row in the sheet). Dictionary should look like: `{"name": "Name", "value": "Value", "description": "Description"}`. * `values_only (bool)`: If true, only values (and not formulas) are exported. +* `skipped_label_replacement (str)`: Replacement for the SkippedLabel +instances. ##### Setting the format/style for Excel cells There is a possibility to set the style/format of each cell in the grid @@ -754,6 +791,9 @@ skipped, default value is false (NaN values are included). * `append_dict (dict)`: Append this dictionary to output. * `generate_schema (bool)`: If true, returns the JSON schema. +All the rows and columns with labels that are instances of SkippedLabel are +entirely skipped. + **The return value is:** Dictionary with keys: 1. column/row, 2. row/column, 3. language or @@ -996,7 +1036,8 @@ sheet.to_excel(*, sep: str = ',', line_terminator: str = '\n', na_rep: str = '', - skip_labels: bool = False + skip_labels: bool = False, + skipped_label_replacement: str = '' ) ``` Parameters are (all optional and key-value only): @@ -1011,6 +1052,8 @@ language in each cell instead of values. * `na_rep (str)`: Replacement for the missing data. * `skip_labels (bool)`: If true, first row and column with labels is skipped +* `skipped_label_replacement (str)`: Replacement for the SkippedLabel +instances. **The return value is:** @@ -1034,7 +1077,8 @@ sheet.to_markdown(*, spaces_replacement: str = ' ', top_right_corner_text: str = "Sheet", na_rep: str = '', - skip_labels: bool = False + skip_labels: bool = False, + skipped_label_replacement: str = '' ) ``` Parameters are (all optional, all key-value only): @@ -1047,6 +1091,8 @@ descriptions (labels) are replaced with this string. * `na_rep (str)`: Replacement for the missing data. * `skip_labels (bool)`: If true, first row and column with labels is skipped +* `skipped_label_replacement (str)`: Replacement for the SkippedLabel +instances. **The return value is:** @@ -1071,7 +1117,8 @@ sheet.to_html_table(*, top_right_corner_text: str = "Sheet", na_rep: str = '', language_for_description: str = None, - skip_labels: bool = False + skip_labels: bool = False, + skipped_label_replacement: str = '' ) ``` Parameters are (all optional, all key-value only): @@ -1085,6 +1132,8 @@ of each computational cell is inserted as word of this language (if the property description is not set). * `skip_labels (bool)`: If true, first row and column with labels is skipped +* `skipped_label_replacement (str)`: Replacement for the SkippedLabel +instances. **The return value is:** diff --git a/portable_spreadsheet/__init__.py b/portable_spreadsheet/__init__.py index 2d6d8e7..7de07a5 100644 --- a/portable_spreadsheet/__init__.py +++ b/portable_spreadsheet/__init__.py @@ -4,6 +4,7 @@ from .cell_slice import CellSlice # noqa from .grammars import GRAMMARS # noqa from .grammar_utils import GrammarUtils # noqa +from .skipped_label import SkippedLabel # noqa -__version__ = "0.1.15" +__version__ = "1.0.0" __status__ = "Production" diff --git a/portable_spreadsheet/cell_indices.py b/portable_spreadsheet/cell_indices.py index 497a546..6870ad1 100644 --- a/portable_spreadsheet/cell_indices.py +++ b/portable_spreadsheet/cell_indices.py @@ -129,8 +129,8 @@ def __init__(self, self.columns[language] = cols self.user_defined_languages.append(language) # Define user defined names for rows and columns - self.rows_labels: List[str] = copy.deepcopy(rows_labels) - self.columns_labels: List[str] = copy.deepcopy(columns_labels) + self.rows_labels: list = copy.deepcopy(rows_labels) + self.columns_labels: list = copy.deepcopy(columns_labels) # Or define auto generated aliases as an integer sequence from 0 if rows_labels is None: self.rows_labels = [str(_row_n) for _row_n in @@ -138,6 +138,11 @@ def __init__(self, if columns_labels is None: self.columns_labels = [str(_col_n) for _col_n in range(number_of_columns)] + # String representation of indices + self.rows_labels_str: List[str] = \ + [str(lb) for lb in self.rows_labels] + self.columns_labels_str: List[str] = \ + [str(lb) for lb in self.columns_labels] # assign the help texts self.rows_help_text: List[str] = copy.deepcopy(rows_help_text) self.columns_help_text: List[str] = copy.deepcopy(columns_help_text) @@ -282,14 +287,11 @@ def expand_size(self, expanded.number_of_columns + new_number_of_columns ) ] - # Or define auto generated aliases as an integer sequence from 0 - if new_columns_labels is None: - expanded.columns_labels = [ - str(i) - for i in range( - expanded.number_of_columns + new_number_of_columns - ) - ] + # String representation of indices + expanded.rows_labels_str: List[str] = \ + [str(lb) for lb in expanded.rows_labels] + expanded.columns_labels_str: List[str] = \ + [str(lb) for lb in expanded.columns_labels] # assign the help texts if expanded.rows_help_text is not None and new_number_of_rows > 0: if new_rows_help_text is None: diff --git a/portable_spreadsheet/serialization.py b/portable_spreadsheet/serialization.py index 9e47a7c..c6b0d10 100644 --- a/portable_spreadsheet/serialization.py +++ b/portable_spreadsheet/serialization.py @@ -10,6 +10,7 @@ from .cell import Cell, CellValueError from .cell_type import CellType from .cell_indices import CellIndices +from .skipped_label import SkippedLabel # ==== TYPES ==== # Type for the output dictionary with the logic: @@ -159,7 +160,8 @@ def to_excel(self, "value": "Value", "description": "Description" }), - values_only: bool = False + values_only: bool = False, + skipped_label_replacement: str = '' ) -> None: """Export the values inside Spreadsheet instance to the Excel 2010 compatible .xslx file @@ -180,6 +182,8 @@ def to_excel(self, for the sheet with variables (first row in the sheet). values_only (bool): If true, only values (and not formulas) are exported. + skipped_label_replacement (str): Replacement for the SkippedLabel + instances. """ # Quick sanity check if ".xlsx" not in file_path[-5:]: @@ -276,26 +280,32 @@ def to_excel(self, if self.cell_indices.excel_append_column_labels: # Add labels of column for col_idx in range(self.shape[1]): + col_lbl = self.cell_indices.columns_labels[ + # Reflect the export offset + col_idx + self.export_offset[1] + ].replace(' ', spaces_replacement) + if isinstance(col_lbl, SkippedLabel): + col_lbl = skipped_label_replacement worksheet.write(0, col_idx + int( self.cell_indices.excel_append_row_labels ), - self.cell_indices.columns_labels[ - # Reflect the export offset - col_idx + self.export_offset[1] - ].replace(' ', spaces_replacement), + col_lbl, col_label_format) if self.cell_indices.excel_append_row_labels: # Add labels for rows for row_idx in range(self.shape[0]): + # Reflect the export offset + row_lbl = self.cell_indices.rows_labels[ + row_idx + self.export_offset[0] + ].replace(' ', spaces_replacement) + if isinstance(row_lbl, SkippedLabel): + row_lbl = skipped_label_replacement worksheet.write(row_idx + int( self.cell_indices.excel_append_column_labels ), 0, - self.cell_indices.rows_labels[ - # Reflect the export offset - row_idx + self.export_offset[0] - ].replace(' ', spaces_replacement), + row_lbl, row_label_format) # Store results workbook.close() @@ -416,8 +426,14 @@ def to_dictionary(self, # Export the spreadsheet to the dictionary (that can by JSON-ified) values = {x_start_key: {}} for idx_x in range(x_range): + if isinstance(x[idx_x], SkippedLabel): + # Skip labels that are intended to be skipped + continue y_values = {y_start_key: {}} for idx_y in range(y_range): + if isinstance(y[idx_y], SkippedLabel): + # Skip labels that are intended to be skipped + continue # Select the correct cell if by_row: cell = self._get_cell_at(idx_x, idx_y) @@ -471,6 +487,9 @@ def to_dictionary(self, # Add row description (and labels) data['rows'] = [] for idx, x_label in enumerate(x): + if isinstance(x[idx_x], SkippedLabel): + # Skip labels that are intended to be skipped + continue metadata: dict = {"name": x_label} if x_helptext is not None: # Add the help text (description) for the row @@ -484,6 +503,9 @@ def to_dictionary(self, # Add column description (and labels) data['columns'] = [] for idx, y_label in enumerate(y): + if isinstance(y[idx_y], SkippedLabel): + # Skip labels that are intended to be skipped + continue metadata = {"name": y_label} if y_helptext is not None: # Add the help text (description) for the column @@ -753,6 +775,7 @@ def generate_json_schema() -> dict: def to_string_of_values(self) -> str: """Export values inside table to the Python array definition string. + (Mainly helpful for debugging purposes) Returns: str: Python list definition string. @@ -777,7 +800,8 @@ def to_2d_list(self, *, top_right_corner_text: str = "Sheet", skip_labels: bool = False, na_rep: Optional[object] = None, - spaces_replacement: str = ' ',) -> List[List[object]]: + spaces_replacement: str = ' ', + skipped_label_replacement: str = '') -> List[List[object]]: """Export values 2 dimensional Python array. Args: @@ -790,6 +814,8 @@ def to_2d_list(self, *, equals to None). skip_labels (bool): If true, first row and column with labels is skipped + skipped_label_replacement (str): Replacement for the SkippedLabel + instances. Returns: List[List[object]]: Python array. @@ -806,17 +832,21 @@ def to_2d_list(self, *, row.append(top_right_corner_text) # Insert labels of columns: for col_i in range(self.shape[1]): - col = self.cell_indices.columns_labels[ + col_lbl = self.cell_indices.columns_labels[ col_i + self.export_offset[1] - ] - value = col.replace(' ', spaces_replacement) - row.append(value) + ].replace(' ', spaces_replacement) + if isinstance(col_lbl, SkippedLabel): + col_lbl = skipped_label_replacement + row.append(col_lbl) else: if not skip_labels: # Insert labels of rows - row.append(self.cell_indices.rows_labels[ + row_lbl = self.cell_indices.rows_labels[ row_idx + self.export_offset[0] - ].replace(' ', spaces_replacement)) + ].replace(' ', spaces_replacement) + if isinstance(row_lbl, SkippedLabel): + row_lbl = skipped_label_replacement + row.append(row_lbl) for col_idx in range(self.shape[1]): # Append actual values: cell_at_position = self._get_cell_at(row_idx, col_idx) @@ -844,6 +874,7 @@ def to_csv(self, *, sep: str = ',', line_terminator: str = '\n', + skipped_label_replacement: str = '' ) -> str: """Export values to the string in the CSV logic @@ -858,14 +889,19 @@ def to_csv(self, *, na_rep (str): Replacement for the missing data. skip_labels (bool): If true, first row and column with labels is skipped + skipped_label_replacement (str): Replacement for the SkippedLabel + instances. Returns: str: CSV of the values """ sheet_as_array = self.to_2d_list( - top_right_corner_text=top_right_corner_text, na_rep=na_rep, - skip_labels=skip_labels, spaces_replacement=spaces_replacement, - language=language + top_right_corner_text=top_right_corner_text, + na_rep=na_rep, + skip_labels=skip_labels, + spaces_replacement=spaces_replacement, + language=language, + skipped_label_replacement=skipped_label_replacement ) export = "" for row_idx in range(len(sheet_as_array)): @@ -882,7 +918,8 @@ def to_markdown(self, *, top_right_corner_text: str = "Sheet", skip_labels: bool = False, na_rep: Optional[object] = '', - spaces_replacement: str = ' ' + spaces_replacement: str = ' ', + skipped_label_replacement: str = '' ): """Export values to the string in the Markdown (MD) file logic @@ -895,14 +932,19 @@ def to_markdown(self, *, na_rep (str): Replacement for the missing data. skip_labels (bool): If true, first row and column with labels is skipped + skipped_label_replacement (str): Replacement for the SkippedLabel + instances. Returns: str: Markdown (MD) compatible table of the values """ sheet_as_array = self.to_2d_list( - top_right_corner_text=top_right_corner_text, na_rep=na_rep, - skip_labels=skip_labels, spaces_replacement=spaces_replacement, - language=language + top_right_corner_text=top_right_corner_text, + na_rep=na_rep, + skip_labels=skip_labels, + spaces_replacement=spaces_replacement, + language=language, + skipped_label_replacement=skipped_label_replacement ) export = "" for row_idx in range(len(sheet_as_array)): @@ -979,7 +1021,8 @@ def to_html_table(self, *, top_right_corner_text: str = "Sheet", na_rep: str = '', language_for_description: str = None, - skip_labels: bool = False) -> str: + skip_labels: bool = False, + skipped_label_replacement: str = '') -> str: """Export values to the string in the HTML table logic Args: @@ -992,6 +1035,8 @@ def to_html_table(self, *, language (if the property description is not set). skip_labels (bool): If true, first row and column with labels is skipped + skipped_label_replacement (str): Replacement for the SkippedLabel + instances. Returns: str: HTML table definition @@ -1013,7 +1058,9 @@ def to_html_table(self, *, export += "