diff --git a/portable_spreadsheet/__init__.py b/portable_spreadsheet/__init__.py index b8a389c..27ea69a 100644 --- a/portable_spreadsheet/__init__.py +++ b/portable_spreadsheet/__init__.py @@ -7,5 +7,5 @@ from .grammar_utils import GrammarUtils # noqa from .skipped_label import SkippedLabel # noqa -__version__ = "2.1.1" +__version__ = "2.1.2" __status__ = "Production" diff --git a/portable_spreadsheet/cell.py b/portable_spreadsheet/cell.py index 6af8175..a22aed2 100644 --- a/portable_spreadsheet/cell.py +++ b/portable_spreadsheet/cell.py @@ -37,7 +37,9 @@ class Cell(object): _description (Optional[str]): Optional description of the cell. _excel_row_position (Optional[int]): Position of the variable in variable sheet in Excel. - _is_computational_variable (bool): If True, variable is computational. + _variable_words (WordConstructor): The word defining computational + operations of (computational) variable. Computational variable is + variable defined by formulas. """ def __init__(self, row: Optional[int] = None, @@ -80,7 +82,6 @@ def __init__(self, self._excel_format: dict = {} self._description: Optional[str] = None self._excel_row_position: Optional[int] = None - self._is_computational_variable: bool = False if words is not None: self._constructing_words: WordConstructor = words @@ -88,6 +89,8 @@ def __init__(self, self._constructing_words: WordConstructor = \ WordConstructor.init_from_new_cell(self) + self._variable_words: WordConstructor = None + # === CLASS METHODS and PROPERTIES: === @property def word(self) -> WordConstructor: @@ -173,6 +176,18 @@ def parse(self) -> Dict[str, str]: """ return self._constructing_words.parse(self) + @property + def parse_variable(self) -> Dict[str, str]: + """Return the dictionary with keys: language, values: constructing + word. This function is called when the cell should be inserted to + a variable spreadsheet. This differs from 'parse' as variables + might be computational. + + Returns: + Dict[str, str]: Words for each language + """ + return self._constructing_words.parse(self, True) + @property def excel_format(self) -> dict: """Return style/format of the cell for Excel. @@ -224,14 +239,13 @@ def excel_row_position(self, value: Optional[int]): @property def computational_variable(self): """Return True, if the variable is computational (contains some - formulas). False, if it is only value. + formulas). False, if it is only a value. + Returns: bool: True, if the variable is computational (contains some formulas). False, if it is only value. """ - if self.is_variable: - return self._is_computational_variable - return False + return self._variable_words is not None @property def description(self) -> Optional[str]: diff --git a/portable_spreadsheet/grammars.py b/portable_spreadsheet/grammars.py index 72617a2..e722752 100644 --- a/portable_spreadsheet/grammars.py +++ b/portable_spreadsheet/grammars.py @@ -38,13 +38,13 @@ "separator": "", "row_first": False }, - # Like $'Sheet 3'.D8 + # Like 'Sheet 3'!D8 (MS Excel format, LibreOffice Calc can parse it) "cross-reference": { "cell-prefix": "", "cell-suffix": "", "cell-separator": "", - "sheet-prefix": "$'", - "sheet-suffix": "'.", + "sheet-prefix": "'", + "sheet-suffix": "'!", "row-first": False, "sheet-first": True }, diff --git a/portable_spreadsheet/serialization.py b/portable_spreadsheet/serialization.py index d724245..af7ec71 100644 --- a/portable_spreadsheet/serialization.py +++ b/portable_spreadsheet/serialization.py @@ -218,7 +218,7 @@ def _excel_write_variables_to_sheet(workbook: object, variables_sheet.write(variable_row_position, offset_columns + 0, var_n) - if var_v['cell'].computational_variable: + if not var_v['cell'].computational_variable: # In the case that variable contains only the value variables_sheet.write( variable_row_position, @@ -231,7 +231,7 @@ def _excel_write_variables_to_sheet(workbook: object, variables_sheet.write_formula( variable_row_position, offset_columns + 1, - var_v['cell'].parse['excel'], + var_v['cell'].parse_variable['excel'], value=var_v['value'], cell_format=variable_style ) diff --git a/portable_spreadsheet/sheet_utils.py b/portable_spreadsheet/sheet_utils.py index 47f02c9..8e0bae5 100644 --- a/portable_spreadsheet/sheet_utils.py +++ b/portable_spreadsheet/sheet_utils.py @@ -457,8 +457,16 @@ def set_variable(self, self.description[name] = description if isinstance(value, Cell): - self._variables[name] = value - self._variables[name]._is_computational_variable = False + self._variables[name] = Cell.variable( + Cell(None, None, # No position + variable_name=name, + value=value.value, + is_variable=True, + cell_type=CellType.computational, + cell_indices=self.spreadsheet.cell_indices + ) + ) + self._variables[name]._variable_words = value._constructing_words else: self._variables[name] = Cell.variable( Cell(None, None, # No position @@ -469,7 +477,6 @@ def set_variable(self, cell_indices=self.spreadsheet.cell_indices ) ) - self._variables[name]._is_computational_variable = True self._variables[name].is_variable = True def get_variables_dict(self, include_cell: bool = True) -> dict: diff --git a/portable_spreadsheet/word_constructor.py b/portable_spreadsheet/word_constructor.py index dd97f6d..cffe6d4 100644 --- a/portable_spreadsheet/word_constructor.py +++ b/portable_spreadsheet/word_constructor.py @@ -502,12 +502,14 @@ def aggregation(cell_start: 'Cell', return instance @staticmethod - def parse(cell: 'Cell') -> T_word: + def parse(cell: 'Cell', variable_word: bool = False) -> T_word: """Parse the cell word. This function is called when the cell should be inserted to spreadsheet. Args: cell (Cell): The cell that is the subject of parsing. + variable_word (bool): If true, variable construction word + is constructed. Returns: T_word: Parsed cell. @@ -521,6 +523,8 @@ def parse(cell: 'Cell') -> T_word: elif cell.cell_type == CellType.computational: # Computational type words: T_word = copy.deepcopy(cell.constructing_words.words) + if variable_word and cell._variable_words is not None: + words = copy.deepcopy(cell._variable_words.words) for language in cell.constructing_words.languages: prefix = GRAMMARS[language]['cells']['operation']['prefix'] suffix = GRAMMARS[language]['cells']['operation']['suffix'] diff --git a/setup.py b/setup.py index d6ded47..7eff011 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="portable-spreadsheet", - version="2.1.1", + version="2.1.2", author="David Salac", author_email="info@davidsalac.eu", description="A simple spreadsheet that keeps tracks of each operation of each cell in defined languages. Logic allows exporting sheets to Excel files (and see how each cell is computed), to the JSON strings with a description of computation of each cell (e. g. in the native language). Other formats, like HTML, CSV and Markdown (MD), are also implemented (user can define own format). It also allows reconstructing behaviours in native Python with NumPy.", # noqa diff --git a/tests/test_serialization.py b/tests/test_serialization.py index df8ec78..a21bb1b 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -132,6 +132,26 @@ def test_to_json(self): computed_parsed_dict: dict = json.loads(self.sheet.to_json()) self.assertDictEqual(expected_parsed_dict, computed_parsed_dict) + def test_variable_serialization(self): + """Test the serialisation of variables""" + self.sheet.var['var_a'] = 11 + self.sheet.var['var_b'] = 22 + self.sheet.var['var_c'] = (self.sheet.var['var_a'] + + self.sheet.var['var_b']) + vars_to_dict = self.sheet.to_dictionary() + vars_to_dict = vars_to_dict['table']['variables'] + self.assertDictEqual(vars_to_dict, {'var_a': {'value': 11, 'description': None}, 'var_b': {'value': 22, 'description': None}, 'var_c': {'value': 33, 'description': None}}) # noqa + # Test computational variable + self.assertDictEqual(self.sheet.var['var_c'].parse_variable, {'python_numpy': 'var_a+var_b', 'native': "value of variable 'var_a (=11)' + value of variable 'var_b (=22)'", 'excel': '=var_a+var_b'}) # noqa + self.assertDictEqual(self.sheet.var['var_c'].parse, {'excel': '=var_c', 'native': "value of variable 'var_c (=33)'", 'python_numpy': 'var_c'}) # noqa + + # Test words of non-computational variables (value-only variables) + self.assertDictEqual(self.sheet.var['var_a'].parse, {'excel': '=var_a', 'native': "value of variable 'var_a (=11)'", 'python_numpy': 'var_a'}) # noqa + self.assertDictEqual(self.sheet.var['var_a'].parse_variable, {'python_numpy': 'var_a', 'excel': '=var_a', 'native': "value of variable 'var_a (=11)'"}) # noqa + + self.assertDictEqual(self.sheet.var['var_b'].parse, {'python_numpy': 'var_b', 'native': "value of variable 'var_b (=22)'", 'excel': '=var_b'}) # noqa + self.assertDictEqual(self.sheet.var['var_b'].parse_variable, {'excel': '=var_b', 'python_numpy': 'var_b', 'native': "value of variable 'var_b (=22)'"}) # noqa + class TestSerializationToArrays(unittest.TestCase): """Test to_numpy serializer."""