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