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