@fileverse-dev/formulajs 4.4.20-mod-1 → 4.4.20-mod-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/formula.js +657 -401
- package/lib/browser/formula.min.js +2 -2
- package/lib/browser/formula.min.js.map +1 -1
- package/lib/cjs/index.cjs +353 -76
- package/lib/esm/index.mjs +353 -76
- package/package.json +1 -1
- package/types/cjs/index.d.cts +1 -1
- package/types/esm/index.d.mts +1 -1
package/lib/cjs/index.cjs
CHANGED
|
@@ -1441,115 +1441,392 @@ function VLOOKUP(lookup_value, table_array, col_index_num, range_lookup) {
|
|
|
1441
1441
|
return result
|
|
1442
1442
|
}
|
|
1443
1443
|
|
|
1444
|
-
function XLOOKUP(
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1444
|
+
function XLOOKUP(search_key, lookup_range, result_range, missing_value, isCol,match_mode, search_mode) {
|
|
1445
|
+
console.log('XLOOKUP parameters:', { search_key, lookup_range, result_range, missing_value, match_mode, search_mode, isCol });
|
|
1446
|
+
|
|
1447
|
+
// Validate required parameters
|
|
1448
|
+
if (search_key === undefined || search_key === null) {
|
|
1449
|
+
console.log('Error: search_key is required');
|
|
1450
|
+
return new Error('Error: search_key is required')
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (!lookup_range || !result_range) {
|
|
1454
|
+
console.log('Error: lookup_range and result_range are required');
|
|
1455
|
+
return new Error('Error: lookup_range and result_range are required')
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// Validate and normalize lookup_range (must be singular row or column)
|
|
1459
|
+
let lookup_array = normalizeLookupRange(lookup_range);
|
|
1460
|
+
if (!lookup_array) {
|
|
1461
|
+
console.log('Error: lookup_range must be a singular row or column');
|
|
1462
|
+
return new Error('Error: lookup_range must be a singular row or column')
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// Validate and normalize result_range
|
|
1466
|
+
let result_array = normalizeResultRange(result_range);
|
|
1467
|
+
if (!result_array) {
|
|
1468
|
+
console.log('Error: Invalid result_range');
|
|
1469
|
+
return new Error('Error: Invalid result_range')
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Validate that lookup and result ranges have compatible dimensions
|
|
1473
|
+
// Exception: if result_range is a single row, it can be returned regardless of lookup_range length
|
|
1474
|
+
result_array.map((row) => {
|
|
1475
|
+
if (row.length !== lookup_array.length) {
|
|
1476
|
+
console.log('Error: lookup_range and result_range must have the same number of columns');
|
|
1477
|
+
return new Error('Error: lookup_range and result_range must have the same number of columns/rows')
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
// Set default parameter values
|
|
1482
|
+
missing_value = missing_value !== undefined ? missing_value : new Error("Error: Didn't find value in XLOOKUP evaluation");
|
|
1483
|
+
match_mode = match_mode !== undefined ? match_mode : 0;
|
|
1484
|
+
search_mode = search_mode !== undefined ? search_mode : 1;
|
|
1485
|
+
isCol = isCol !== undefined ? isCol : false;
|
|
1486
|
+
|
|
1487
|
+
// Validate match_mode
|
|
1488
|
+
if (![0, 1, -1, 2].includes(match_mode)) {
|
|
1489
|
+
console.log('Error: match_mode must be 0, 1, -1, or 2');
|
|
1490
|
+
return new Error('Error: match_mode must be 0, 1, -1, or 2')
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// Validate search_mode
|
|
1494
|
+
if (![1, -1, 2, -2].includes(search_mode)) {
|
|
1495
|
+
console.log('Error: search_mode must be 1, -1, 2, or -2');
|
|
1496
|
+
return new Error('Error: search_mode must be 1, -1, 2, or -2')
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Validate binary search requirements
|
|
1500
|
+
if (Math.abs(search_mode) === 2 && match_mode === 2) {
|
|
1501
|
+
console.log('Error: Binary search (search_mode ±2) cannot be used with wildcard matching (match_mode 2)');
|
|
1502
|
+
return new Error('Error: Binary search (search_mode ±2) cannot be used with wildcard matching (match_mode 2)')
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
console.log('Normalized arrays:', { lookup_array, result_array });
|
|
1506
|
+
|
|
1507
|
+
let res = performLookup(search_key, lookup_array, result_array, missing_value, match_mode, search_mode, isCol);
|
|
1508
|
+
res = isCol ? Array.isArray(res)?res.map((item) => [item.toString()]):res : res;
|
|
1509
|
+
return res
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function normalizeLookupRange(lookup_range) {
|
|
1513
|
+
if (!Array.isArray(lookup_range)) {
|
|
1514
|
+
return null
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// If it's a 1D array, it's already a column
|
|
1518
|
+
if (!Array.isArray(lookup_range[0])) {
|
|
1519
|
+
return lookup_range
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// If it's a 2D array, check if it's a single row or single column
|
|
1523
|
+
const rows = lookup_range.length;
|
|
1524
|
+
const cols = lookup_range[0].length;
|
|
1525
|
+
|
|
1526
|
+
if (rows === 1) {
|
|
1527
|
+
// Single row - extract as array
|
|
1528
|
+
return lookup_range[0]
|
|
1529
|
+
} else if (cols === 1) {
|
|
1530
|
+
// Single column - extract first element of each row
|
|
1531
|
+
return lookup_range.map(row => row[0])
|
|
1532
|
+
} else {
|
|
1533
|
+
// Multiple rows and columns - not allowed
|
|
1534
|
+
return null
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function normalizeResultRange(result_range) {
|
|
1539
|
+
if (!Array.isArray(result_range)) {
|
|
1540
|
+
return null
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// If it's a 1D array, convert to 2D single column for consistency
|
|
1544
|
+
if (!Array.isArray(result_range[0])) {
|
|
1545
|
+
return result_range.map(value => [value])
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// If it's already 2D, return as is
|
|
1549
|
+
return result_range
|
|
1550
|
+
}
|
|
1448
1551
|
|
|
1449
|
-
|
|
1552
|
+
function performLookup(search_key, lookup_array, result_array, missing_value, match_mode, search_mode, isCol) {
|
|
1450
1553
|
|
|
1451
|
-
console.log('
|
|
1554
|
+
console.log('performLookup called with:', { search_key, lookup_array, result_array, missing_value, match_mode, search_mode, isCol });
|
|
1452
1555
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1556
|
+
let foundIndex = -1;
|
|
1557
|
+
const isSingleResultRow = result_array.length === 1;
|
|
1558
|
+
|
|
1559
|
+
// Handle different match modes
|
|
1560
|
+
switch (match_mode) {
|
|
1561
|
+
case 0: // Exact match
|
|
1562
|
+
foundIndex = findExactMatch(search_key, lookup_array, search_mode);
|
|
1563
|
+
break
|
|
1564
|
+
case 1: // Exact match or next larger
|
|
1565
|
+
foundIndex = findExactOrNextLarger(search_key, lookup_array, search_mode);
|
|
1566
|
+
break
|
|
1567
|
+
case -1: // Exact match or next smaller
|
|
1568
|
+
foundIndex = findExactOrNextSmaller(search_key, lookup_array, search_mode);
|
|
1569
|
+
break
|
|
1570
|
+
case 2: // Wildcard match
|
|
1571
|
+
foundIndex = findWildcardMatch(search_key, lookup_array, search_mode);
|
|
1572
|
+
break
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
if (foundIndex === -1) {
|
|
1576
|
+
// Return missing_value (single value): "yoo"
|
|
1577
|
+
return missing_value
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// Return the result
|
|
1581
|
+
if (isSingleResultRow) {
|
|
1582
|
+
// Single result row - return the entire row regardless of where match was found
|
|
1583
|
+
const resultRow = result_array[0];
|
|
1584
|
+
if (isCol) {
|
|
1585
|
+
return resultRow.map(val => [val])
|
|
1586
|
+
} else {
|
|
1587
|
+
return resultRow
|
|
1588
|
+
}
|
|
1589
|
+
} else {
|
|
1590
|
+
// Multiple result rows
|
|
1591
|
+
if (isCol) {
|
|
1592
|
+
// Return the foundIndex column from all rows: ["e", "r"]
|
|
1593
|
+
const columnValues = result_array.map(row => row[foundIndex]);
|
|
1594
|
+
return columnValues
|
|
1595
|
+
} else {
|
|
1596
|
+
// Return the entire matched row: ["e", 3, "s", "hj"]
|
|
1597
|
+
return result_array[foundIndex]
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function findExactMatch(search_key, lookup_array, search_mode) {
|
|
1603
|
+
const processedSearchKey = typeof search_key === 'string' ? search_key.toLowerCase().trim() : search_key;
|
|
1604
|
+
|
|
1605
|
+
if (Math.abs(search_mode) === 2) {
|
|
1606
|
+
// Binary search
|
|
1607
|
+
return binarySearchExact(processedSearchKey, lookup_array, search_mode > 0)
|
|
1608
|
+
} else {
|
|
1609
|
+
// Linear search
|
|
1610
|
+
const indices = getSearchIndices(lookup_array.length, search_mode);
|
|
1459
1611
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1612
|
+
for (const i of indices) {
|
|
1613
|
+
const value = lookup_array[i];
|
|
1614
|
+
const processedValue = typeof value === 'string' ? value.toLowerCase().trim() : value;
|
|
1615
|
+
|
|
1616
|
+
if (processedValue === processedSearchKey) {
|
|
1617
|
+
console.log(`Exact match found at index ${i}:`, value);
|
|
1618
|
+
return i
|
|
1619
|
+
}
|
|
1463
1620
|
}
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
return -1
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function findExactOrNextLarger(search_key, lookup_array, search_mode) {
|
|
1627
|
+
const isNumber = typeof search_key === 'number';
|
|
1628
|
+
const processedSearchKey = typeof search_key === 'string' ? search_key.toLowerCase().trim() : search_key;
|
|
1629
|
+
|
|
1630
|
+
if (Math.abs(search_mode) === 2) {
|
|
1631
|
+
// Binary search for exact or next larger
|
|
1632
|
+
return binarySearchNextLarger(processedSearchKey, lookup_array, search_mode > 0)
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
const indices = getSearchIndices(lookup_array.length, search_mode);
|
|
1636
|
+
let bestIndex = -1;
|
|
1637
|
+
|
|
1638
|
+
for (const i of indices) {
|
|
1639
|
+
const value = lookup_array[i];
|
|
1640
|
+
const processedValue = typeof value === 'string' ? value.toLowerCase().trim() : value;
|
|
1464
1641
|
|
|
1465
|
-
//
|
|
1466
|
-
|
|
1467
|
-
|
|
1642
|
+
// Exact match
|
|
1643
|
+
if (processedValue === processedSearchKey) {
|
|
1644
|
+
return i
|
|
1645
|
+
}
|
|
1468
1646
|
|
|
1469
|
-
|
|
1647
|
+
// Next larger value
|
|
1648
|
+
if (isNumber && typeof value === 'number' && value > search_key) {
|
|
1649
|
+
if (bestIndex === -1 || value < lookup_array[bestIndex]) {
|
|
1650
|
+
bestIndex = i;
|
|
1651
|
+
}
|
|
1652
|
+
} else if (!isNumber && typeof value === 'string' && processedValue > processedSearchKey) {
|
|
1653
|
+
if (bestIndex === -1 || processedValue < (typeof lookup_array[bestIndex] === 'string' ? lookup_array[bestIndex].toLowerCase().trim() : lookup_array[bestIndex])) {
|
|
1654
|
+
bestIndex = i;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1470
1657
|
}
|
|
1471
1658
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1659
|
+
return bestIndex
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function findExactOrNextSmaller(search_key, lookup_array, search_mode) {
|
|
1663
|
+
const isNumber = typeof search_key === 'number';
|
|
1664
|
+
const processedSearchKey = typeof search_key === 'string' ? search_key.toLowerCase().trim() : search_key;
|
|
1474
1665
|
|
|
1475
|
-
if (
|
|
1476
|
-
|
|
1477
|
-
return
|
|
1666
|
+
if (Math.abs(search_mode) === 2) {
|
|
1667
|
+
// Binary search for exact or next smaller
|
|
1668
|
+
return binarySearchNextSmaller(processedSearchKey, lookup_array, search_mode > 0)
|
|
1478
1669
|
}
|
|
1479
1670
|
|
|
1480
|
-
|
|
1481
|
-
let
|
|
1482
|
-
let return_col = return_array;
|
|
1671
|
+
const indices = getSearchIndices(lookup_array.length, search_mode);
|
|
1672
|
+
let bestIndex = -1;
|
|
1483
1673
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1674
|
+
for (const i of indices) {
|
|
1675
|
+
const value = lookup_array[i];
|
|
1676
|
+
const processedValue = typeof value === 'string' ? value.toLowerCase().trim() : value;
|
|
1677
|
+
|
|
1678
|
+
// Exact match
|
|
1679
|
+
if (processedValue === processedSearchKey) {
|
|
1680
|
+
return i
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// Next smaller value
|
|
1684
|
+
if (isNumber && typeof value === 'number' && value < search_key) {
|
|
1685
|
+
if (bestIndex === -1 || value > lookup_array[bestIndex]) {
|
|
1686
|
+
bestIndex = i;
|
|
1687
|
+
}
|
|
1688
|
+
} else if (!isNumber && typeof value === 'string' && processedValue < processedSearchKey) {
|
|
1689
|
+
if (bestIndex === -1 || processedValue > (typeof lookup_array[bestIndex] === 'string' ? lookup_array[bestIndex].toLowerCase().trim() : lookup_array[bestIndex])) {
|
|
1690
|
+
bestIndex = i;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1488
1693
|
}
|
|
1489
1694
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1695
|
+
return bestIndex
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
function findWildcardMatch(search_key, lookup_array, search_mode) {
|
|
1699
|
+
if (typeof search_key !== 'string') {
|
|
1700
|
+
return -1 // Wildcard only works with strings
|
|
1494
1701
|
}
|
|
1495
1702
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1703
|
+
// Convert wildcard pattern to regex
|
|
1704
|
+
const pattern = search_key
|
|
1705
|
+
.toLowerCase()
|
|
1706
|
+
.replace(/\*/g, '.*') // * matches any sequence of characters
|
|
1707
|
+
.replace(/\?/g, '.') // ? matches any single character
|
|
1708
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape other regex chars
|
|
1709
|
+
.replace(/\\\.\*/g, '.*') // Restore our wildcards
|
|
1710
|
+
.replace(/\\\./g, '.');
|
|
1711
|
+
|
|
1712
|
+
const regex = new RegExp(`^${pattern}$`, 'i');
|
|
1500
1713
|
|
|
1501
|
-
|
|
1502
|
-
if_not_found = if_not_found !== undefined ? if_not_found : ERROR_NA;
|
|
1503
|
-
match_mode = match_mode || 0; // 0 = exact match, -1 = exact or next smallest
|
|
1504
|
-
search_mode = search_mode || 1; // 1 = first to last, -1 = last to first
|
|
1714
|
+
const indices = getSearchIndices(lookup_array.length, search_mode);
|
|
1505
1715
|
|
|
1506
|
-
|
|
1716
|
+
for (const i of indices) {
|
|
1717
|
+
const value = lookup_array[i];
|
|
1718
|
+
if (typeof value === 'string' && regex.test(value)) {
|
|
1719
|
+
console.log(`Wildcard match found at index ${i}:`, value);
|
|
1720
|
+
return i
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
return -1
|
|
1507
1725
|
}
|
|
1508
1726
|
|
|
1727
|
+
function getSearchIndices(length, search_mode) {
|
|
1728
|
+
if (search_mode === -1) {
|
|
1729
|
+
// Last to first
|
|
1730
|
+
return Array.from({ length }, (_, i) => length - 1 - i)
|
|
1731
|
+
} else {
|
|
1732
|
+
// First to last (default)
|
|
1733
|
+
return Array.from({ length }, (_, i) => i)
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1509
1736
|
|
|
1510
|
-
function
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
let bestMatchIndex = -1;
|
|
1737
|
+
function binarySearchExact(search_key, lookup_array, ascending) {
|
|
1738
|
+
let left = 0;
|
|
1739
|
+
let right = lookup_array.length - 1;
|
|
1514
1740
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1741
|
+
while (left <= right) {
|
|
1742
|
+
const mid = Math.floor((left + right) / 2);
|
|
1743
|
+
const midValue = lookup_array[mid];
|
|
1744
|
+
const processedMidValue = typeof midValue === 'string' ? midValue.toLowerCase().trim() : midValue;
|
|
1745
|
+
|
|
1746
|
+
if (processedMidValue === search_key) {
|
|
1747
|
+
return mid
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
const comparison = ascending ?
|
|
1751
|
+
(processedMidValue < search_key) :
|
|
1752
|
+
(processedMidValue > search_key);
|
|
1753
|
+
|
|
1754
|
+
if (comparison) {
|
|
1755
|
+
left = mid + 1;
|
|
1756
|
+
} else {
|
|
1757
|
+
right = mid - 1;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1523
1760
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1761
|
+
return -1
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
function binarySearchNextLarger(search_key, lookup_array, ascending) {
|
|
1765
|
+
let left = 0;
|
|
1766
|
+
let right = lookup_array.length - 1;
|
|
1767
|
+
let result = -1;
|
|
1528
1768
|
|
|
1529
|
-
|
|
1530
|
-
const
|
|
1531
|
-
const
|
|
1769
|
+
while (left <= right) {
|
|
1770
|
+
const mid = Math.floor((left + right) / 2);
|
|
1771
|
+
const midValue = lookup_array[mid];
|
|
1772
|
+
const processedMidValue = typeof midValue === 'string' ? midValue.toLowerCase().trim() : midValue;
|
|
1532
1773
|
|
|
1533
|
-
|
|
1774
|
+
if (processedMidValue === search_key) {
|
|
1775
|
+
return mid // Exact match
|
|
1776
|
+
}
|
|
1534
1777
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1778
|
+
if (ascending) {
|
|
1779
|
+
if (processedMidValue > search_key) {
|
|
1780
|
+
result = mid;
|
|
1781
|
+
right = mid - 1;
|
|
1782
|
+
} else {
|
|
1783
|
+
left = mid + 1;
|
|
1784
|
+
}
|
|
1785
|
+
} else {
|
|
1786
|
+
if (processedMidValue < search_key) {
|
|
1787
|
+
result = mid;
|
|
1788
|
+
left = mid + 1;
|
|
1789
|
+
} else {
|
|
1790
|
+
right = mid - 1;
|
|
1791
|
+
}
|
|
1539
1792
|
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
return result
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
function binarySearchNextSmaller(search_key, lookup_array, ascending) {
|
|
1799
|
+
let left = 0;
|
|
1800
|
+
let right = lookup_array.length - 1;
|
|
1801
|
+
let result = -1;
|
|
1802
|
+
|
|
1803
|
+
while (left <= right) {
|
|
1804
|
+
const mid = Math.floor((left + right) / 2);
|
|
1805
|
+
const midValue = lookup_array[mid];
|
|
1806
|
+
const processedMidValue = typeof midValue === 'string' ? midValue.toLowerCase().trim() : midValue;
|
|
1540
1807
|
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1808
|
+
if (processedMidValue === search_key) {
|
|
1809
|
+
return mid // Exact match
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
if (ascending) {
|
|
1813
|
+
if (processedMidValue < search_key) {
|
|
1814
|
+
result = mid;
|
|
1815
|
+
left = mid + 1;
|
|
1816
|
+
} else {
|
|
1817
|
+
right = mid - 1;
|
|
1818
|
+
}
|
|
1819
|
+
} else {
|
|
1820
|
+
if (processedMidValue > search_key) {
|
|
1821
|
+
result = mid;
|
|
1822
|
+
right = mid - 1;
|
|
1823
|
+
} else {
|
|
1824
|
+
left = mid + 1;
|
|
1547
1825
|
}
|
|
1548
1826
|
}
|
|
1549
1827
|
}
|
|
1550
1828
|
|
|
1551
|
-
|
|
1552
|
-
return bestMatchIndex !== -1 ? return_array[bestMatchIndex] : if_not_found
|
|
1829
|
+
return result
|
|
1553
1830
|
}
|
|
1554
1831
|
|
|
1555
1832
|
/**
|