@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/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;