diff --git a/README.md b/README.md index 1770247..e54dcc7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# ⚗️ Lox365: XLOOKUP for LibreOffice +# ⚗️ Lox365: Modern functions for LibreOffice -Lox365 is a LibreOffice Calc extension that adds modern spreadsheet functions like XLOOKUP, FILTER, and more. +Lox365 is a LibreOffice Calc extension that adds modern spreadsheet functions like IMAGE, TEXTSPLIT, and TOCOL. -![Screenshot](image1.png) +![Screenshot](image9.png) -![Screenshot](image2.png) +![Screenshot](image4.png) Do you like using Lox365? Let me know in the [Discussions](https://github.com/goosepirate/lox365/discussions). Maybe [buy me a coffee](https://liberapay.com/goosepirate/). @@ -29,7 +29,7 @@ This is because Lox365 functions are [array functions](https://help.libreoffice. ### Syntax -#### FILTER +#### ~~FILTER~~ (Removed) Filters an array. @@ -63,7 +63,7 @@ Similar to Excel's [IMAGE](https://support.microsoft.com/en-us/office/image-func * `out_cell`: Reference to the cell where the image is to be placed. * `source`: The path of the source that points to the image. -#### SORT +#### ~~SORT~~ (Removed) Sorts an array. @@ -102,7 +102,7 @@ Similar to Excel's [TOCOL](https://support.microsoft.com/en-us/office/tocol-func * `array`: The array to return as a column. * Not supported: `ignore`, `scan_by_column`. -#### UNIQUE +#### ~~UNIQUE~~ (Removed) Returns the unique values from a range or array. @@ -114,7 +114,7 @@ Similar to Excel's [UNIQUE](https://support.microsoft.com/en-us/office/unique-fu * `array`: The array from which to return unique rows. * Not supported: `by_col`, `exactly_once`. -#### XLOOKUP +#### ~~XLOOKUP~~ (Removed) Searches an array for a match and returns the corresponding item from a second array. @@ -129,6 +129,15 @@ Similar to Excel's [XLOOKUP](https://support.microsoft.com/en-us/office/xlookup- * `[if_not_found]`: Where a valid match is not found, return the [if_not_found] text you supply. Optional. * Not supported: `match_mode`, `search_mode`. +## Version notes + +These functions have been removed in Lox365 version 6 because you can now use them in the latest LibreOffice (24.8): + +* FILTER +* SORT +* UNIQUE +* XLOOKUP + ## Why I use these functions quite often in Excel and wanted to use them in LibreOffice too, so I made this. @@ -203,25 +212,29 @@ On a machine without Lox365 installed, you will not be able to view calculation These functions are not in LibreOffice and not provided by Lox365 but are available in the latest Excel: -* RANDARRAY -* SEQUENCE -* SORTBY +* GROUPBY +* LAMBDA +* PIVOTBY * STOCKHISTORY * TOROW -* XMATCH -These functions are not in LibreOffice Calc now, but are planned to be added: - -* XLOOKUP - -These functions are already available in LibreOffice: +These functions are available in the latest LibreOffice: * CONCAT +* FILTER * IFS +* LET * MAXIFS * MINIFS +* RANDARRAY +* SEQUENCE +* SORT +* SORTBY * SWITCH * TEXTJOIN +* UNIQUE +* XLOOKUP +* XMATCH ## References diff --git a/addin.xcu b/addin.xcu index f85efde..76c157e 100644 --- a/addin.xcu +++ b/addin.xcu @@ -5,25 +5,6 @@ - - FILTER - Filters an array. Provided by Lox365. - Add-in - - - Array - The array to filter. - - - Include - An array of booleans where TRUE represents a row or column to retain. - - - [If empty] - Returned if no items are retained. - - - IMAGE Returns an image from a given source. Provided by Lox365. @@ -39,25 +20,6 @@ - - SORT - Sorts an array. Provided by Lox365. - Add-in - - - Array - The array to sort. - - - [Sort index] - A number indicating the row or column to sort by. - - - [Sort order] - A number indicating the desired sort order; 1 for ascending order (default), -1 for descending order. - - - TEXTSPLIT Splits text into columns using delimiters. Provided by Lox365. @@ -84,40 +46,6 @@ - - UNIQUE - Returns the unique values from a range or array. Provided by Lox365. - Add-in - - - Array - The array from which to return unique rows. - - - - - XLOOKUP - Searches an array for a match and returns the corresponding item from a second array. Provided by Lox365. - Add-in - - - Lookup value - The value to search for. - - - Lookup array - The array to search. - - - Return array - The array to return. - - - [If not found] - Where a valid match is not found, return the [if_not_found] text you supply. - - - diff --git a/description.xml b/description.xml index 098b113..1d82e52 100644 --- a/description.xml +++ b/description.xml @@ -4,7 +4,7 @@ xmlns:xlink="http://www.w3.org/1999/xlink"> - + goosepirate Lox365 diff --git a/extension-description.txt b/extension-description.txt index db444cd..c687257 100644 --- a/extension-description.txt +++ b/extension-description.txt @@ -1 +1 @@ -Lox365: XLOOKUP for LibreOffice \ No newline at end of file +Lox365: Modern functions for LibreOffice \ No newline at end of file diff --git a/interface.idl b/interface.idl index 2021eac..cfc015b 100644 --- a/interface.idl +++ b/interface.idl @@ -4,21 +4,11 @@ module org { module openoffice { module sheet { module addin { interface XLox365 : com::sun::star::uno::XInterface { - sequence< sequence< any > > FILTER( - [in] sequence< sequence< any > > array, - [in] sequence< sequence< any > > include, - [in] any ifEmpty - ); any IMAGE( [in] com::sun::star::beans::XPropertySet doc, [in] com::sun::star::table::XCellRange out_cell, [in] string url ); - sequence< sequence< any > > SORT( - [in] sequence< sequence< any > > array, - [in] any sortIndex, - [in] any sortOrder - ); sequence< sequence< any > > TEXTSPLIT( [in] string text, [in] string colDelimiter @@ -26,14 +16,5 @@ module org { module openoffice { module sheet { module addin { sequence< sequence< any > > TOCOL( [in] sequence< sequence< any > > array ); - sequence< sequence< any > > UNIQUE( - [in] com::sun::star::table::XCellRange array - ); - sequence< sequence< any > > XLOOKUP( - [in] any lookupValue, - [in] com::sun::star::table::XCellRange lookupArray, - [in] com::sun::star::table::XCellRange returnArray, - [in] any ifNotFound - ); }; }; }; }; }; diff --git a/loader.py b/loader.py index 1a979c2..61526cb 100644 --- a/loader.py +++ b/loader.py @@ -37,33 +37,10 @@ def _get_shrunk_corners(self, cellrange) -> dict: } return useful_positions - def FILTER (self, *args): return lx.FILTER (*args) def IMAGE (self, *args): return lx.IMAGE (*args) - def SORT (self, *args): return lx.SORT (*args) def TEXTSPLIT(self, *args): return lx.TEXTSPLIT(*args) def TOCOL (self, *args): return lx.TOCOL (*args) - def UNIQUE(self, *args): - shrunk_corners = self._get_shrunk_corners(args[0]) - shrunk_dataarray = self._get_dataarray(args[0], shrunk_corners) - args = (shrunk_dataarray,) - return lx.UNIQUE(*args) - - def XLOOKUP(self, *args): - shrunk_corners1 = self._get_shrunk_corners(args[1]) - shrunk_corners2 = self._get_shrunk_corners(args[2]) - shrunk_corners_common_bottom = max( - shrunk_corners1['bottom'], shrunk_corners2['bottom']) - shrunk_dataarray1 = self._get_dataarray(args[1], { - 'left': 0, 'top': 0, - 'right': shrunk_corners1['right'], - 'bottom': shrunk_corners_common_bottom}) - shrunk_dataarray2 = self._get_dataarray(args[2], { - 'left': 0, 'top': 0, - 'right': shrunk_corners2['right'], - 'bottom': shrunk_corners_common_bottom}) - args = (args[0], shrunk_dataarray1, shrunk_dataarray2, *args[3:],) - return lx.XLOOKUP(*args) def createInstance(ctx): return Lox365(ctx) diff --git a/pythonpath/lox365.py b/pythonpath/lox365.py index 6e7e637..b5d7262 100644 --- a/pythonpath/lox365.py +++ b/pythonpath/lox365.py @@ -4,36 +4,11 @@ ERR_CALC = '#CALC!' ERR_NA = '#N/A' -def FILTER(array, include, ifEmpty=ERR_CALC): - if ifEmpty is None: ifEmpty = ERR_CALC - lookup_direction = 0 # 0 is vertical; 1 is horizontal - if len(include) == 1 and len(include[0]) > 1: lookup_direction = 1 - import itertools - if lookup_direction == 0: - ans = tuple(itertools.compress(array, [i[0] for i in include])) - if len(ans) == 1: - ans = (ans[0], tuple([0] * len(array[0])),) - else: - ans = tuple(tuple(itertools.compress(row, include[0])) for row in array) - return ans if ans else ((ifEmpty,),) - try: import imagefn IMAGE = imagefn.IMAGE except ImportError: pass -def SORT(array, sortIndex=1, sortOrder=1): - if sortIndex is None: sortIndex = 1 - if sortOrder is None or sortOrder == 1: reverse = False - elif sortOrder == -1: reverse = True - else: return ValueError - stringify = True - if all(isinstance(item[sortIndex - 1], float) for item in array): - stringify = False - return tuple(sorted(array, - key=lambda r: str(r[sortIndex - 1]) if stringify else r[sortIndex - 1], - reverse=reverse)) - def TEXTSPLIT(text, colDelimiter): if text == '' or text == '0': return ((0,),) # Compensate for when LO Calc converts a blank cell to a '0' string. t2 = text.split(colDelimiter) @@ -46,24 +21,3 @@ def TOCOL(array): for item in row: result.append((item,)) return tuple(result) - -def UNIQUE(array): - return tuple(dict.fromkeys(array)) - -def XLOOKUP(lookupValue, lookupArray, returnArray, ifNotFound=ERR_NA): - if ifNotFound is None: ifNotFound = ERR_NA - lookup_direction = 0 # 0 is vertical; 1 is horizontal - if len(lookupArray) == 1 and len(lookupArray[0]) > 1: lookup_direction = 1 - try: - if lookup_direction == 0: - return (returnArray[lookupArray.index((lookupValue,))],) - if lookup_direction == 1: - return tuple((row[lookupArray[0].index(lookupValue)],) for row in returnArray) - except ValueError: return ((ifNotFound,),) - -# Too slow -# def XLOOKUP_old1(lookup_value, lookup_array, return_array, if_not_found): -# lookup_item = (lookup_value,) -# for index, item in enumerate(lookup_array): -# if item == lookup_item: return (return_array[index],) -# return ((if_not_found,),) diff --git a/pythonpath/test_lox365.py b/pythonpath/test_lox365.py index a3bcc4e..5aaa21b 100644 --- a/pythonpath/test_lox365.py +++ b/pythonpath/test_lox365.py @@ -1,65 +1,6 @@ # pytest from lox365 import * -def test_FILTER(): - '''Typical''' - assert FILTER( - (('A', 3), ('C', 2), ('B', 4)), - ((1,), (0,), (1,)), - ) == (('A', 3), ('B', 4)) - - '''Single row result''' - assert FILTER((('A', 3), ('C', 2), ('B', 4)), - ((0,), (0,), (1,)), - ) == (('B', 4), (0, 0)) - - '''Not found''' - assert FILTER( - (('A', 3), ('C', 2)), - ((0,), (0,)), - ) == (('#CALC!',),) - - '''Horizontal lookup''' - assert FILTER( - (('A', 3, 'D'), ('C', 2, 'E')), - ((0,1,1),), - ) == ((3, 'D'), (2, 'E')) - - '''Valid booleans''' - assert FILTER( - (('A', 3), ('C', 2), ('B', 4)), - ((2,), (0,), ('e',)), - ) == (('A', 3), ('B', 4)) - - '''Custom message if not found''' - assert FILTER( - (('A', 3), ('C', 2), ('B', 4)), - ((0,), (0,), (0,)), - 'Nope', - ) == (('Nope',),) - -def test_SORT(): - '''Simple''' - assert SORT( - (('C', 1), ('B', 4), ('A', 3)), - ) == (('A', 3), ('B', 4), ('C', 1)) - - '''Only numbers in array''' - assert SORT( - ((3.0, 1), (2.4, 4), (4.1, 3), (1.1, 5)), - ) == ((1.1, 5), (2.4, 4), (3.0, 1), (4.1, 3)) - - '''Multiple datatypes in array''' - assert SORT( - (('C', 1), ('B', 4), ('A', 3), (1, 5)), - ) == ((1, 5), ('A', 3), ('B', 4), ('C', 1)) - - '''Sort by a different column''' - assert SORT( - (('A', 4), ('B', 3), ('D', 2)), - 2, - ) == (('D', 2),('B', 3),('A', 4)) - def test_TEXTSPLIT(): '''Typical''' assert TEXTSPLIT('a/b/c', '/') == (('a', 'b', 'c'),) @@ -79,63 +20,3 @@ def test_TOCOL(): assert TOCOL((('a',0),)) == (('a',),(0,)) assert TOCOL(((0,'b'),('c',0))) == ((0,),('b',),('c',),(0,)) assert TOCOL(((0,),)) == ((0,),) - -def test_UNIQUE(): - '''Typical''' - assert UNIQUE((('b',),('a',),('b',),('a',))) == \ - (('b',),('a',)) - -def test_XLOOKUP(): - '''Typical''' - assert XLOOKUP('C', - (('A',), ('C',), ('E',)), - (('B',), ('D',), ('F',)), - ) == (('D',),) - - '''Blank key''' - assert XLOOKUP('', - (('A',), ('',), ('E',), ('',)), - (('B',), ('D',), ('F',), ('H',)), - ) == (('D',),) - - '''Not found''' - assert XLOOKUP('J', - (('A',), ('C',), ('E',)), - (('B',), ('D',), ('F',)), - ) == (('#N/A',),) - - '''Pick first valid result when multiple matches''' - assert XLOOKUP('A', - (('C',), ('A',), ('A',)), - (('B',), ('D',), ('F',)), - ) == (('D',),) - - '''Vertical lookup, multi-column output''' - assert XLOOKUP('C', - (('A',), ('C',), ('E',)), - (('B', 'G'), ('D', 'H'), ('F', 'I')), - ) == (('D', 'H'),) - - '''Horizontal lookup''' - assert XLOOKUP('C', - (('B', 'C', 'A'),), - (('F', 'E', 'D'),), - ) == (('E',),) - - '''Horizontal lookup, multi-row output''' - assert XLOOKUP('C', - (('B', 'C', 'A'),), - (('F', 'E', 'D'), ('H', 'G', 'I')), - ) == (('E',),('G',)) - - '''Custom message if not found''' - assert XLOOKUP('J', - (('A',), ('C',), ('E',)), - (('B',), ('D',), ('F',)), - 'Not found', - ) == (('Not found',),) - assert XLOOKUP('J', - (('A',), ('C',)), - (('B',), ('D',)), - '', - ) == (('',),) diff --git a/pythonpath/test_perf_lox365.py b/pythonpath/test_perf_lox365.py index 8585792..cd30b73 100644 --- a/pythonpath/test_perf_lox365.py +++ b/pythonpath/test_perf_lox365.py @@ -1,32 +1,6 @@ # pytest-benchmark from lox365 import * -def test_XLOOKUP_all_rows_early_match(benchmark): - benchmark(XLOOKUP, 'C', - tuple([('A',),('B',),('C',)] + [('',)] * (1048576 - 3)), - tuple([('D',),('E',),('F',)] + [('',)] * (1048576 - 3))) - -def test_XLOOKUP_all_rows_late_match(benchmark): - benchmark(XLOOKUP, 'C', - tuple([('',)] * (1048576 - 3) + [('A',),('B',),('C',)]), - tuple([('',)] * (1048576 - 3) + [('D',),('E',),('F',)])) - -# def test_XLOOKUP_repeated_lookups_on_same_array(benchmark): -# import numpy as np -# called = 0 -# rand_lookup_array = tuple() -# def get_rand_lookup_array(): -# nonlocal called -# nonlocal rand_lookup_array -# if called % 100 == 0: -# rand_lookup_array = tuple(map(tuple, np.random.uniform(0,100,100000).reshape(-1,1))) -# called += 1 -# return rand_lookup_array -# benchmark(XLOOKUP, np.random.uniform(0,100), -# get_rand_lookup_array(), -# tuple(map(tuple, np.random.uniform(0,100,100000).reshape(-1,1))) -# ) - # def test_TEXTSPLIT_large_string(benchmark): # import random, string # benchmark(TEXTSPLIT,