Spreadsheet: Add getUsedRange() and getNonEmptyRange()

This commit is contained in:
Chris Hennes
2022-11-04 15:45:52 -05:00
parent f06ec1f28b
commit 0c162d7f09
5 changed files with 126 additions and 2 deletions

View File

@@ -151,6 +151,38 @@ bool PropertySheet::isValidAlias(const std::string &candidate)
return false;
}
namespace
{
// A utility function that gets the range (the minimum and maximum row and column) out of a
// vector of cell addresses. Note that it's possible that neither cell in the tuple itself
// has data in it, but operating on all cells in the inclusive range specified by the tuple
// is guaranteed to include all cells in the passed-in vector, and no cells exist to the
// left, right, above, or below the block specified.
std::tuple<CellAddress, CellAddress> extractRange(const std::vector<CellAddress> &cells)
{
CellAddress firstRowAndColumn;
CellAddress lastRowAndColumn;
for (const auto & cell : cells) {
int row = cell.row();
int column = cell.col();
if (row < firstRowAndColumn.row() || !firstRowAndColumn.isValid()) {
firstRowAndColumn.setRow(row);
}
if (column < firstRowAndColumn.col() || !firstRowAndColumn.isValid()) {
firstRowAndColumn.setCol(column);
}
if (row > lastRowAndColumn.row() || !lastRowAndColumn.isValid()) {
lastRowAndColumn.setRow(row);
}
if (column > lastRowAndColumn.col() || !lastRowAndColumn.isValid()) {
lastRowAndColumn.setCol(column);
}
}
return std::make_tuple(firstRowAndColumn, lastRowAndColumn);
}
}// namespace
std::vector<CellAddress> PropertySheet::getUsedCells() const
{
std::vector<CellAddress> usedSet;
@@ -163,6 +195,12 @@ std::vector<CellAddress> PropertySheet::getUsedCells() const
return usedSet;
}
std::tuple<CellAddress, CellAddress> PropertySheet::getUsedRange() const
{
auto usedCells = getUsedCells();
return extractRange(usedCells);
}
std::vector<CellAddress> PropertySheet::getNonEmptyCells() const
{
std::vector<CellAddress> usedSet;
@@ -177,6 +215,12 @@ std::vector<CellAddress> PropertySheet::getNonEmptyCells() const
return usedSet;
}
std::tuple<CellAddress, CellAddress> PropertySheet::getNonEmptyRange() const
{
auto nonEmptyCells = getNonEmptyCells();
return extractRange(nonEmptyCells);
}
void PropertySheet::setDirty(CellAddress address)
{
/* Merged cells will automatically force an update of the top left cell

View File

@@ -115,8 +115,12 @@ public:
std::vector<App::CellAddress> getUsedCells() const;
std::tuple<App::CellAddress, App::CellAddress> getUsedRange() const;
std::vector<App::CellAddress> getNonEmptyCells() const;
std::tuple<App::CellAddress, App::CellAddress> getNonEmptyRange() const;
Sheet * sheet() const { return owner; }
const std::set<App::CellAddress> & getDirty() { return dirty; }

View File

@@ -201,5 +201,32 @@ Get a list of the names of all cells with data in them.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="getUsedRange">
<Documentation>
<UserDocu>
getUsedRange()
Get a the total range of the used cells in a sheet, as a pair of strings
representing the lowest row and column that are used, and the highest row and
column that are used (inclusive). Note that the actual first and last cell
of the block are not necessarily used.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="getNonEmptyRange">
<Documentation>
<UserDocu>
getNonEmptyRange()
Get a the total range of the used cells in a sheet, as a pair of cell addresses
representing the lowest row and column that contain data, and the highest row and
column that contain data (inclusive). Note that the actual first and last cell
of the block do not necessarily contain anything.
</UserDocu>
</Documentation>
</Methode>
</PythonExport>
</GenerateModel>

View File

@@ -990,16 +990,34 @@ PyObject *SheetPy::getUsedCells(PyObject *args)
return Py::new_reference_to(pyCellList);
}
PyObject *SheetPy::getUsedRange(PyObject *args)
{
auto usedRange = getSheetPtr()->getCells()->getUsedRange();
Py::Tuple pyTuple(2);
pyTuple[0] = Py::String(std::get<0>(usedRange).toString());
pyTuple[1] = Py::String(std::get<1>(usedRange).toString());
return Py::new_reference_to(pyTuple);
}
PyObject *SheetPy::getNonEmptyCells(PyObject *args)
{
auto usedCells = getSheetPtr()->getCells()->getNonEmptyCells();
auto nonEmptyCells = getSheetPtr()->getCells()->getNonEmptyCells();
Py::List pyCellList;
for (const auto &cell : usedCells) {
for (const auto &cell : nonEmptyCells) {
pyCellList.append(Py::String(cell.toString()));
}
return Py::new_reference_to(pyCellList);
}
PyObject *SheetPy::getNonEmptyRange(PyObject *args)
{
auto nonEmptyRange = getSheetPtr()->getCells()->getNonEmptyRange();
Py::Tuple pyTuple(2);
pyTuple[0] = Py::String(std::get<0>(nonEmptyRange).toString());
pyTuple[1] = Py::String(std::get<1>(nonEmptyRange).toString());
return Py::new_reference_to(pyTuple);
}
// +++ custom attributes implementer ++++++++++++++++++++++++++++++++++++++++

View File

@@ -1362,6 +1362,20 @@ class SpreadsheetCases(unittest.TestCase):
non_empty_cells = sheet.getUsedCells()
self.assertEqual(len(non_empty_cells), len(test_cells)) # Alignment counts as "used"
def testGetUsedRange(self):
sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet')
test_cells = ['C5','Z3','D10','E20']
for i,cell in enumerate(test_cells):
sheet.set(cell,str(i))
used_range = sheet.getUsedRange()
self.assertEquals(used_range,("C3","Z20"))
for i,cell in enumerate(test_cells):
sheet.set(cell,"")
sheet.setAlignment(cell,"center")
used_range = sheet.getUsedRange()
self.assertEquals(used_range,("C3","Z20"))
def testGetNonEmptyCells(self):
sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet')
test_cells = ['B13','C14','D15']
@@ -1379,6 +1393,23 @@ class SpreadsheetCases(unittest.TestCase):
non_empty_cells = sheet.getNonEmptyCells()
self.assertEqual(len(non_empty_cells), 0) # Alignment does not count as "non-empty"
def testGetNonEmptyRange(self):
sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet')
test_cells = ['C5','Z3','D10','E20']
for i,cell in enumerate(test_cells):
sheet.set(cell,str(i))
non_empty_range = sheet.getNonEmptyRange()
self.assertEquals(non_empty_range,("C3","Z20"))
for i,cell in enumerate(test_cells):
sheet.set(cell,"")
sheet.setAlignment(cell,"center")
more_cells = ['D10','X5','E10','K15']
for i,cell in enumerate(more_cells):
sheet.set(cell,str(i))
non_empty_range = sheet.getNonEmptyRange()
self.assertEquals(non_empty_range,("D5","X15"))
def tearDown(self):
#closing doc
FreeCAD.closeDocument(self.doc.Name)