Coverage for pandalone.xlasso._capture : 92%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
#!/usr/bin/env python # -*- coding: UTF-8 -*- # # Copyright 2014 European Commission (JRC); # Licensed under the EUPL (the 'Licence'); # You may not use this work except in compliance with the Licence. # You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl The algorithmic part of :term:`capturing`.
Prefer accessing the public members from the parent module. """
# TODO: Try different backends providing `colname` function. except ImportError: log.warning( 'One of `xlrd`, `...` libraries is needed, will crash later!')
"""When `True`, most coord-functions accept any 2-tuples."""
"""The key for specifying options within :term:`filters`."""
'L': Coords(0, -1), 'U': Coords(-1, 0), 'R': Coords(0, 1), 'D': Coords(1, 0) }
"""Make *A1* :class:`Cell` from *resolved* coords, with rudimentary error-checking.
Examples::
>>> coords2Cell(row=0, col=0) Cell(row='1', col='A') >>> coords2Cell(row=0, col=26) Cell(row='1', col='AA')
>>> coords2Cell(row=10, col='.') Cell(row='11', col='.')
>>> coords2Cell(row=-3, col=-2) Traceback (most recent call last): AssertionError: negative row!
""" if row not in _special_coord_symbols: assert row >= 0, 'negative row!' row = str(row + 1) if col not in _special_coord_symbols: assert col >= 0, 'negative col!' col = xl_colname(col) return Cell(row=row, col=col)
""" Returns top-left/bottom-down margins of full cells from a :term:`state` matrix.
May be used by :meth:`ABCSheet.get_margin_coords()` if a backend does not report the sheet-margins internally.
:param np.ndarray states_matrix: A 2D-array with `False` wherever cell are blank or empty. Use :meth:`ABCSheet.get_states_matrix()` to derrive it. :return: the 2 coords of the top-left & bottom-right full cells :rtype: (Coords, Coords)
Examples::
>>> states_matrix = np.asarray([ ... [0, 0, 0], ... [0, 1, 0], ... [0, 1, 1], ... [0, 0, 1], ... ]) >>> margins = _margin_coords_from_states_matrix(states_matrix) >>> margins (Coords(row=1, col=1), Coords(row=3, col=2))
Note that the botom-left cell is not the same as `states_matrix` matrix size::
>>> states_matrix = np.asarray([ ... [0, 0, 0, 0], ... [0, 1, 0, 0], ... [0, 1, 1, 0], ... [0, 0, 1, 0], ... [0, 0, 0, 0], ... ]) >>> _margin_coords_from_states_matrix(states_matrix) == margins True
"""
# return indices.min(0), indices.max(0)
""" Resolves special coords or converts Excel 1-based rows to zero-based, reporting invalids.
:param str, int coord: excel-row coordinate or one of ``^_.`` :return: excel row number, >= 0 :rtype: int
Examples::
>>> row = _row2num('1') >>> row 0 >>> row == _row2num(1) True
Negatives (from bottom) are preserved::
>>> _row2num('-1') -1
Fails ugly::
>>> _row2num('.') Traceback (most recent call last): ValueError: invalid literal for int() with base 10: '.' """ msg = 'Uncooked-coord cannot be zero!' raise ValueError(msg.format(coord))
""" Resolves special coords or converts Excel A1 columns to a zero-based, reporting invalids.
:param str coord: excel-column coordinate or one of ``^_.`` :return: excel column number, >= 0 :rtype: int
Examples::
>>> col = _col2num('D') >>> col 3 >>> _col2num('d') == col True >>> _col2num('AaZ') 727 >>> _col2num('10') 9 >>> _col2num(9) 8
Negatives (from left-end) are preserved::
>>> _col2num('AaZ') 727
Fails ugly::
>>> _col2num('%$') Traceback (most recent call last): ValueError: substring not found
>>> _col2num([]) Traceback (most recent call last): TypeError: int() argument must be a string, a bytes-like object or a number, not 'list'
"""
else: msg = 'Uncooked-coord cannot be zero!' raise ValueError(msg.format(coord))
""" Translates special coords or converts Excel string 1-based rows/cols to zero-based, reporting invalids.
:param str cname: the coord-name, one of 'row', 'column' :param function cfunc: the function to convert coord ``str --> int`` :param int, str coord: the "A1" coord to translate :param int up_coord: the resolved *top* or *left* margin zero-based coordinate :param int dn_coord: the resolved *bottom* or *right* margin zero-based coordinate :param int, None base_coords: the resolved basis for dependent coord, if any
:return: the resolved coord or `None` if it were not a special coord.
Row examples::
>>> cname = 'row'
>>> r0 = _resolve_coord(cname, _row2num, '1', 1, 10) >>> r0 0 >>> r0 == _resolve_coord(cname, _row2num, 1, 1, 10) True >>> _resolve_coord(cname, _row2num, '^', 1, 10) 1 >>> _resolve_coord(cname, _row2num, '_', 1, 10) 10 >>> _resolve_coord(cname, _row2num, '.', 1, 10, 13) 13 >>> _resolve_coord(cname, _row2num, '-3', 0, 10) 8
But notice when base-cell missing::
>>> _resolve_coord(cname, _row2num, '.', 0, 10, base_coords=None) Traceback (most recent call last): ValueError: Cannot resolve `relative-row` without `base-coord`!
Other ROW error-checks::
>>> _resolve_coord(cname, _row2num, '0', 0, 10) Traceback (most recent call last): ValueError: invalid row('0') due to: Uncooked-coord cannot be zero!
>>> _resolve_coord(cname, _row2num, 'a', 0, 10) Traceback (most recent call last): ValueError: invalid row('a') due to: invalid literal for int() with base 10: 'a'
>>> _resolve_coord(cname, _row2num, None, 0, 10) Traceback (most recent call last): ValueError: invalid row(None) due to: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
Column examples::
>>> cname = 'column'
>>> _resolve_coord(cname, _col2num, 'A', 1, 10) 0 >>> _resolve_coord(cname, _col2num, 'DADA', 1, 10) 71084 >>> _resolve_coord(cname, _col2num, '.', 1, 10, 13) 13 >>> _resolve_coord(cname, _col2num, '-4', 0, 10) 7
And COLUMN error-checks::
>>> _resolve_coord(cname, _col2num, None, 0, 10) Traceback (most recent call last): ValueError: invalid column(None) due to: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
>>> _resolve_coord(cname, _col2num, 0, 0, 10) Traceback (most recent call last): ValueError: invalid column(0) due to: Uncooked-coord cannot be zero!
""" '^': up_coord, '_': dn_coord } else:
# Resolve negatives as from the end.
msg = 'invalid {}({!r}) due to: {}' # fututils.raise_from(ValueError(msg.format(cname, coord, ex)), ex) see # GH 141 raise ValueError(msg.format(cname, coord, ex))
""" Translates any special coords to absolute ones.
To get the margin_coords, use one of:
* :meth:`ABCSheet.get_margin_coords()` * :func:`_margin_coords_from_states_matrix()`
:param Cell cell: The "A1" cell to translate its coords. :param Coords up_coords: the top-left resolved coords with full-cells :param Coords dn_coords: the bottom-right resolved coords with full-cells :param Coords base_coords: A resolved cell to base dependent coords (``.``). :return: the resolved cell-coords :rtype: Coords
Examples::
>>> up = Coords(1, 2) >>> dn = Coords(10, 6) >>> base = Coords(40, 50)
>>> _resolve_cell(Cell(col='B', row=5), up, dn) Coords(row=4, col=1)
>>> _resolve_cell(Cell('^', '^'), up, dn) Coords(row=1, col=2)
>>> _resolve_cell(Cell('_', '_'), up, dn) Coords(row=10, col=6)
>>> base == _resolve_cell(Cell('.', '.'), up, dn, base) True
>>> _resolve_cell(Cell('-1', '-2'), up, dn) Coords(row=10, col=5)
>>> _resolve_cell(Cell('A', 'B'), up, dn) Traceback (most recent call last): ValueError: invalid cell(Cell(row='A', col='B')) due to: invalid row('A') due to: invalid literal for int() with base 10: 'A'
But notice when base-cell missing::
>>> _resolve_cell(Cell('1', '.'), up, dn) Traceback (most recent call last): ValueError: invalid cell(Cell(row='1', col='.')) due to: Cannot resolve `relative-col` without `base-coord`!
""" else: up_coords[0], dn_coords[0], base_row) up_coords[1], dn_coords[1], base_col)
# fututils.raise_from(ValueError(msg % (cell, ex)), ex) see GH 141
# VECTO_SLICE REVERSE COORD_INDEX 'L': (1, -1, lambda r, c: (r, slice(None, c + 1))), 'U': (0, -1, lambda r, c: (slice(None, r + 1), c)), 'R': (1, 1, lambda r, c: (r, slice(c, None))), 'D': (0, 1, lambda r, c: (slice(r, None), c)), }
"""Extract a slice from the states-matrix by starting from `land` and following `mov`."""
""" Follow moves from `land` and stop on the 1st full-cell.
:param np.ndarray states_matrix: A 2D-array with `False` wherever cell are blank or empty. Use :meth:`ABCSheet.get_states_matrix()` to derrive it. :param Coords dn_coords: the bottom-right for the top-left of full-cells :param Coords land: the landing-cell :param str moves: MUST not be empty :return: the identified target-cell's coordinates :rtype: Coords
Examples::
>>> states_matrix = np.array([ ... [0, 0, 0, 0, 0, 0], ... [0, 0, 0, 0, 0, 0], ... [0, 0, 0, 1, 1, 1], ... [0, 0, 1, 0, 0, 1], ... [0, 0, 1, 1, 1, 1] ... ]) >>> args = (states_matrix, Coords(4, 5))
>>> _target_opposite(*(args + (Coords(0, 0), 'DR'))) Coords(row=3, col=2)
>>> _target_opposite(*(args + (Coords(0, 0), 'RD'))) Coords(row=2, col=3)
It fails if a non-empty target-cell cannot be found, or it ends-up beyond bounds::
>>> _target_opposite(*(args + (Coords(0, 0), 'D'))) Traceback (most recent call last): ValueError: No opposite-target found while moving(D) from landing-Coords(row=0, col=0)!
>>> _target_opposite(*(args + (Coords(0, 0), 'UR'))) Traceback (most recent call last): ValueError: No opposite-target found while moving(UR) from landing-Coords(row=0, col=0)!
But notice that the landing-cell maybe outside of bounds::
>>> _target_opposite(*(args + (Coords(3, 10), 'L'))) Coords(row=3, col=5)
"""
# if states_matrix[target].all(): # return Coords(*target)
# Limit negative coords, since they are valid indices. states_matrix, dn_coords, target, mov1) else:
""" :param np.ndarray states_matrix: A 2D-array with `False` wherever cell are blank or empty. Use :meth:`ABCSheet.get_states_matrix()` to derrive it. :param Coords dn_coords: the bottom-right for the top-left of full-cells :param Coords land: The landing-cell, which MUST be full! """ states_matrix, dn_coords, land, mov) else:
""" Scan term:`exterior` row and column on specified `moves` and stop on the last full-cell.
:param np.ndarray states_matrix: A 2D-array with `False` wherever cell are blank or empty. Use :meth:`ABCSheet.get_states_matrix()` to derrive it. :param Coords dn_coords: the bottom-right for the top-left of full-cells :param Coords land: the landing-cell which MUST be within bounds :param moves: which MUST not be empty :return: the identified target-cell's coordinates :rtype: Coords
Examples::
>>> states_matrix = np.array([ ... [0, 0, 0, 0, 0, 0], ... [0, 0, 0, 0, 0, 0], ... [0, 0, 0, 1, 1, 1], ... [0, 0, 1, 0, 0, 1], ... [0, 0, 1, 1, 1, 1] ... ]) >>> args = (states_matrix, Coords(4, 5))
>>> _target_same(*(args + (Coords(4, 5), 'U'))) Coords(row=2, col=5)
>>> _target_same(*(args + (Coords(4, 5), 'L'))) Coords(row=4, col=2)
>>> _target_same(*(args + (Coords(4, 5), 'UL', ))) Coords(row=2, col=2)
It fails if landing is empty or beyond bounds::
>>> _target_same(*(args + (Coords(2, 2), 'DR'))) Traceback (most recent call last): ValueError: No same-target found while moving(DR) from landing-Coords(row=2, col=2)!
>>> _target_same(*(args + (Coords(10, 3), 'U'))) Traceback (most recent call last): ValueError: No same-target found while moving(U) from landing-Coords(row=10, col=3)!
"""
np.asarray(land), mov)
msg = 'No same-target found while moving({}) from {}landing-{}!' raise ValueError(msg.format(moves, edge_name, land))
""" Sorts rect-vertices in a 2D-array (with vertices in rows).
Example::
>>> _sort_rect((5, 3), (4, 6)) array([[4, 3], [5, 6]]) """
""" Applies the :term:`expansion-moves` based on the `states_matrix`.
:param state: :param Coords r1: any vertice of the rect to expand :param Coords r2: any vertice of the rect to expand :param np.ndarray states_matrix: A 2D-array with `False` wherever cell are blank or empty. Use :meth:`ABCSheet.get_states_matrix()` to derrive it. :param exp_moves: Just the parsed string, and not `None`. :return: a sorted rect top-left/bottom-right
Examples::
>>> states_matrix = np.array([ ... #0 1 2 3 4 5 ... [0, 0, 0, 0, 0, 0], #0 ... [0, 0, 1, 1, 1, 0], #1 ... [0, 1, 0, 0, 1, 0], #2 ... [0, 1, 1, 1, 1, 0], #3 ... [0, 0, 0, 0, 0, 1], #4 ... ], dtype=bool)
>>> r1, r2 = (Coords(2, 1), Coords(2, 1)) >>> _expand_rect(states_matrix, r1, r2, 'U') (Coords(row=2, col=1), Coords(row=2, col=1))
>>> r1, r2 = (Coords(3, 1), Coords(2, 1)) >>> _expand_rect(states_matrix, r1, r2, 'R') (Coords(row=2, col=1), Coords(row=3, col=4))
>>> r1, r2 = (Coords(2, 1), Coords(6, 1)) >>> _expand_rect(states_matrix, r1, r2, 'r') (Coords(row=2, col=1), Coords(row=6, col=5))
>>> r1, r2 = (Coords(2, 3), Coords(2, 3)) >>> _expand_rect(states_matrix, r1, r2, 'LURD') (Coords(row=1, col=1), Coords(row=3, col=4))
"""
'L': np.array([0, 0, -1, 0]), 'R': np.array([0, 0, 0, 1]), 'U': np.array([-1, 0, 0, 0]), 'D': np.array([0, 1, 0, 0]), } 'L': [0, 1, 2, 2], 'R': [0, 1, 3, 3], 'U': [0, 0, 2, 3], 'D': [1, 1, 2, 3], }
# Sort rect's vertices top-left/bottom-right. # # ``[r1, r2, c1, c2]`` to use slices, below slice(*exp_vect_i[2:])]
st_edge, nd_edge=None, exp_moves=None, base_coords=None): """ Performs :term:`targeting`, :term:`capturing` and :term:`expansions` based on the :term:`states-matrix`.
To get the margin_coords, use one of:
* :meth:`ABCSheet.get_margin_coords()` * :func:`_margin_coords_from_states_matrix()`
Its results can be fed into :func:`read_capture_values()`.
:param np.ndarray states_matrix: A 2D-array with `False` wherever cell are blank or empty. Use :meth:`ABCSheet.get_states_matrix()` to derrive it. :param (Coords, Coords) up_dn_margins: the top-left/bottom-right coords with full-cells :param Edge st_edge: "uncooked" as matched by regex :param Edge nd_edge: "uncooked" as matched by regex :param list or none exp_moves: Just the parsed string, and not `None`. :param Coords base_coords: The base for a :term:`dependent` :term:`1st` edge.
:return: a ``(Coords, Coords)`` with the 1st and 2nd :term:`capture-cell` ordered from top-left --> bottom-right. :rtype: tuple
Examples::
>>> states_matrix = np.array([ ... [0, 0, 0, 0, 0, 0], ... [0, 0, 0, 0, 0, 0], ... [0, 0, 0, 1, 1, 1], ... [0, 0, 1, 0, 0, 1], ... [0, 0, 1, 1, 1, 1] ... ], dtype=bool) >>> up, dn = _margin_coords_from_states_matrix(states_matrix)
>>> st_edge = Edge(Cell('1', 'A'), 'DR') >>> nd_edge = Edge(Cell('.', '.'), 'DR') >>> resolve_capture_rect(states_matrix, (up, dn), st_edge, nd_edge) (Coords(row=3, col=2), Coords(row=4, col=2))
Using dependenent coordinates for the 2nd edge::
>>> st_edge = Edge(Cell('_', '_'), None) >>> nd_edge = Edge(Cell('.', '.'), 'UL') >>> rect = resolve_capture_rect(states_matrix, (up, dn), st_edge, nd_edge) >>> rect (Coords(row=2, col=2), Coords(row=4, col=5))
Using sheet's margins::
>>> st_edge = Edge(Cell('^', '_'), None) >>> nd_edge = Edge(Cell('_', '^'), None) >>> rect == resolve_capture_rect(states_matrix, (up, dn), st_edge, nd_edge) True
Walking backwards::
>>> st_edge = Edge(Cell('^', '_'), 'L') # Landing is full, so 'L' ignored. >>> nd_edge = Edge(Cell('_', '_'), 'L', '+') # '+' or would also stop. >>> rect == resolve_capture_rect(states_matrix, (up, dn), st_edge, nd_edge) True
"""
except IndexError: st_state = False
'1st-') else: '1st-')
else:
except IndexError: nd_state = False
nd_edge.land == Cell('.', '.') and nd_edge.mod != '?'): states_matrix, dn_margin, nd, mov, '2nd-') else: states_matrix, dn_margin, nd, mov, '2nd-')
else:
""" A delegating to backend factory and sheet-wrapper with utility methods.
:param np.ndarray _states_matrix: The :term:`states-matrix` cached, so recreate object to refresh it. :param dict _margin_coords: limits used by :func:`_resolve_cell`, cached, so recreate object to refresh it.
Resource management is outside of the scope of this class, and must happen in the backend workbook/sheet instance.
*xlrd* examples::
>>> import xlrd # doctest: +SKIP >>> with xlrd.open_workbook(self.tmp) as wb: # doctest: +SKIP ... sheet = xlasso.xlrdSheet(wb.sheet_by_name('Sheet1')) ... ## Do whatever
*win32* examples::
>>> with dsgdsdsfsd as wb: # doctest: +SKIP ... sheet = xlasso.win32Sheet(wb.sheet['Sheet1']) TODO: Win32 Sheet example """
""" Override it to release resources for this sheet."""
""" Override it to release resources this and all sibling sheets."""
def get_sheet_ids(self): """ :return: a 2-tuple of its wb-name and a sheet-ids of this sheet i.e. name & indx :rtype: ([str or None, [str or int or None]) """
"""Return a sibling sheet by the given index or name"""
def _read_states_matrix(self): """ Read the :term:`states-matrix` of the wrapped sheet.
:return: A 2D-array with `False` wherever cell are blank or empty. :rtype: ndarray """
""" Read and cache the :term:`states-matrix` of the wrapped sheet.
:return: A 2D-array with `False` wherever cell are blank or empty. :rtype: ndarray """
def read_rect(self, st, nd): """ Fecth the actual values from the backend Excel-sheet.
:param Coords st: the top-left edge, inclusive :param Coords, None nd: the bottom-right edge, inclusive(!); when `None`, must return a scalar value. :return: a 1D or 2D-list with the values fenced by the rect, which might be empty if beyond limits. :rtype: list """
""" Override if possible to read (any of the) limits directly from the sheet.
:return: the 2 coords of the top-left & bottom-right full cells; anyone coords can be None. By default returns ``(None, None)``. :rtype: (Coords, Coords)
""" return None, None # pragma: no cover
""" Extract (and cache) margins either internally or from :func:`_margin_coords_from_states_matrix()`.
:return: the resolved top-left and bottom-right :class:`.xlasso.Coords` :rtype: tuple
"""
return '%s%s' % (type(self), self.get_sheet_ids())
"""A sample :class:`ABCSheet` made out of 2D-list or numpy-arrays, for facilitating tests."""
raise NotImplementedError()
|