diff --git a/pysd/py_backend/external.py b/pysd/py_backend/external.py index 26ce7df8..35e4ffae 100644 --- a/pysd/py_backend/external.py +++ b/pysd/py_backend/external.py @@ -1067,9 +1067,45 @@ def get_subscripts_cell(self, row_first, col_first, lastcell): return data - def get_subscripts_name(self, name): + def get_subscripts_name(self, cellname): """Get subscripts from cell range name definition""" - raise NotImplementedError + excel = load_workbook(self.file, read_only=True, data_only=True) + global_cellranges = excel.defined_names + local_cellranges = None + # need to lower the sheetnames as Vensim has no case sensitivity + for sheet in excel.sheetnames: + if sheet.lower() == self.sheet.lower(): + local_cellranges = excel[sheet].defined_names + break + + if local_cellranges is None: + # Error if it is not able to get the localSheetId + raise ValueError( + self.py_name + "\n" + "The sheet doesn't exist...\n" + + self._file_sheet + ) + try: + # Search for local and global names + cellrange = local_cellranges.get(cellname)\ + or global_cellranges.get(cellname) + sheet, cells = next(cellrange.destinations) + + assert sheet.lower() == self.sheet.lower() + self.sheet = sheet # case insensitivity in sheet name + + # Get the cells where the cellrange is defined + first_cell, last_cell = cells.replace("$", '').split(":") + except (AttributeError, AssertionError): + # key error if the cellrange doesn't exist in the file or sheet + raise AttributeError( + self.py_name + "\n" + f"The cellrange name '{cellname}'\n" + "Doesn't exist in:\n" + self._file_sheet + ) + else: + return self.get_subscripts_cell( + *self._split_excel_cell(first_cell), last_cell) @staticmethod def _not_nan(value): diff --git a/tests/data/input.xlsx b/tests/data/input.xlsx index 0b3b8179..82c45985 100644 Binary files a/tests/data/input.xlsx and b/tests/data/input.xlsx differ diff --git a/tests/pytest_types/external/pytest_external.py b/tests/pytest_types/external/pytest_external.py index 60aa4af8..e5296531 100644 --- a/tests/pytest_types/external/pytest_external.py +++ b/tests/pytest_types/external/pytest_external.py @@ -1883,7 +1883,7 @@ def test_subscript_d2(self, _root): sheet = "No monotonous" firstcell = "H10" lastcell = "" - prefix = 'j' + prefix = "j" expected = ['j3', 'j2', 'j1', 'j6', 'j4', 'j8', 'j-1', 'j3', 'j2'] data = pysd.external.ExtSubscript(file_name=file_name, @@ -1895,6 +1895,73 @@ def test_subscript_d2(self, _root): assert data.subscript == expected + def test_subscript_name_h(self, _root): + """ + ExtSubscript test for horizontal subscripts + """ + import pysd + + file_name = "data/input.xlsx" + sheet = "Horizontal missing" + firstcell = "time_missing" + lastcell = "B7" + prefix = "l" + expected = ['l0', 'l1', 'l2', 'l3', + 'l5', 'l6', 'l7', 'l8'] + + data = pysd.external.ExtSubscript(file_name=file_name, + sheet=sheet, + root=_root, + firstcell=firstcell, + lastcell=lastcell, + prefix=prefix) + + assert data.subscript == expected + + def test_subscript_name_v(self, _root): + """ + ExtSubscript test for vertical subscripts + """ + import pysd + + file_name = "data/input.xlsx" + sheet = "Horizontal" + firstcell = "vertical_index" + lastcell = "" + prefix = "p" + expected = ['pA', 'pB', 'pC'] + + data = pysd.external.ExtSubscript(file_name=file_name, + sheet=sheet, + root=_root, + firstcell=firstcell, + lastcell=lastcell, + prefix=prefix) + + assert data.subscript == expected + + def test_subscript_name_d(self, _root): + """ + ExtSubscript test for diagonal subscripts + """ + import pysd + + file_name = "data/input.xlsx" + sheet = "Horizontal missing" + firstcell = "d_names" + lastcell = None + prefix = "" + expected = ['X', 'A', '0', 'B', '0', 'C', '1'] + + data = pysd.external.ExtSubscript(file_name=file_name, + sheet=sheet, + root=_root, + firstcell=firstcell, + lastcell=lastcell, + prefix=prefix) + + assert data.subscript == expected + class TestWarningsErrors(): """ @@ -2008,7 +2075,7 @@ def test_non_existent_cellrange_name_pyxl(self, _root): final_coords=coords, py_name=py_name) - error_message = "The cellrange name '.*'\nDoesn't exist in" + error_message = "The cellrange name 'non_exixtent'\nDoesn't exist in" with pytest.raises(AttributeError, match=error_message): data.initialize() @@ -2032,7 +2099,7 @@ def test_non_existent_cellrange_name_in_sheet_pyxl(self, _root): final_coords=coords, py_name=py_name) - error_message = "The cellrange name '.*'\nDoesn't exist in" + error_message = "The cellrange name 'constant'\nDoesn't exist in" with pytest.raises(AttributeError, match=error_message): data.initialize() @@ -3231,6 +3298,48 @@ def text_openpyxl_str(self, _root): assert np.isnan(data.data) + def test_subscript_name_non_existent_sheet(self, _root): + """ + ExtSubscript test for diagonal subscripts + """ + import pysd + + file_name = "data/input.xlsx" + sheet = "No exit" + firstcell = "d_names" + lastcell = None + prefix = "" + + error_message = r"The sheet doesn't exist\.\.\." + with pytest.raises(ValueError, match=error_message): + pysd.external.ExtSubscript(file_name=file_name, + sheet=sheet, + root=_root, + firstcell=firstcell, + lastcell=lastcell, + prefix=prefix) + + def test_subscript_name_non_existent_cellrange_name(self, _root): + """ + Test for non-existent cellrange name with openpyxl + """ + import pysd + + file_name = "data/input.xlsx" + sheet = "Horizontal" + firstcell = "fake-cell" + lastcell = None + prefix = "" + + error_message = "The cellrange name 'fake-cell'\nDoesn't exist in" + with pytest.raises(AttributeError, match=error_message): + pysd.external.ExtSubscript(file_name=file_name, + sheet=sheet, + root=_root, + firstcell=firstcell, + lastcell=lastcell, + prefix=prefix) + class DownwardCompatibility(): """