@fileverse-dev/formulajs 4.4.19 → 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 +675 -326
- package/lib/browser/formula.min.js +2 -2
- package/lib/browser/formula.min.js.map +1 -1
- package/lib/cjs/index.cjs +400 -1
- package/lib/esm/index.mjs +400 -2
- 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
|
@@ -1278,7 +1278,7 @@ function SORT(inputArray, sortIndex = 1, isAscending, sortByColumn = false) {
|
|
|
1278
1278
|
if (!sortColumnIndex || sortColumnIndex < 1) return value;
|
|
1279
1279
|
sortColumnIndex = sortColumnIndex - 1;
|
|
1280
1280
|
|
|
1281
|
-
const sortDirection = isAscending
|
|
1281
|
+
const sortDirection = isAscending?.toLowerCase() === 'false' ? -1 : 1;
|
|
1282
1282
|
const parsedSortDirection = parseNumber(sortDirection);
|
|
1283
1283
|
if (parsedSortDirection !== 1 && parsedSortDirection !== -1) return value;
|
|
1284
1284
|
|
|
@@ -1441,6 +1441,404 @@ 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, missing_value, isCol,match_mode, search_mode) {
|
|
1445
|
+
// Error constants
|
|
1446
|
+
const ERROR_NA = '#N/A';
|
|
1447
|
+
const ERROR_REF = '#REF!';
|
|
1448
|
+
const ERROR_VALUE = '#VALUE!';
|
|
1449
|
+
const ERROR_NUM = '#NUM!';
|
|
1450
|
+
|
|
1451
|
+
console.log('XLOOKUP parameters:', { search_key, lookup_range, result_range, missing_value, match_mode, search_mode, isCol });
|
|
1452
|
+
|
|
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;
|
|
1529
|
+
|
|
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);
|
|
1621
|
+
|
|
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
|
+
}
|
|
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;
|
|
1651
|
+
|
|
1652
|
+
// Exact match
|
|
1653
|
+
if (processedValue === processedSearchKey) {
|
|
1654
|
+
return i
|
|
1655
|
+
}
|
|
1656
|
+
|
|
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
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
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;
|
|
1675
|
+
|
|
1676
|
+
if (Math.abs(search_mode) === 2) {
|
|
1677
|
+
// Binary search for exact or next smaller
|
|
1678
|
+
return binarySearchNextSmaller(processedSearchKey, lookup_array, search_mode > 0)
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
const indices = getSearchIndices(lookup_array.length, search_mode);
|
|
1682
|
+
let bestIndex = -1;
|
|
1683
|
+
|
|
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
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
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
|
|
1711
|
+
}
|
|
1712
|
+
|
|
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');
|
|
1723
|
+
|
|
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
|
+
}
|
|
1733
|
+
|
|
1734
|
+
return -1
|
|
1735
|
+
}
|
|
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
|
+
}
|
|
1746
|
+
|
|
1747
|
+
function binarySearchExact(search_key, lookup_array, ascending) {
|
|
1748
|
+
let left = 0;
|
|
1749
|
+
let right = lookup_array.length - 1;
|
|
1750
|
+
|
|
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
|
+
}
|
|
1770
|
+
|
|
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;
|
|
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
|
+
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
|
+
}
|
|
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;
|
|
1817
|
+
|
|
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;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
return result
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1444
1842
|
/**
|
|
1445
1843
|
* Returns the character specified by the code number.
|
|
1446
1844
|
*
|
|
@@ -19240,6 +19638,7 @@ exports.WORKDAY = WORKDAY;
|
|
|
19240
19638
|
exports.WORKDAYINTL = WORKDAYINTL;
|
|
19241
19639
|
exports.WORKDAY_INTL = WORKDAY_INTL;
|
|
19242
19640
|
exports.XIRR = XIRR;
|
|
19641
|
+
exports.XLOOKUP = XLOOKUP;
|
|
19243
19642
|
exports.XNPV = XNPV;
|
|
19244
19643
|
exports.XOR = XOR;
|
|
19245
19644
|
exports.YEAR = YEAR;
|