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,