@@ -259,6 +259,18 @@ function evaluateFormulaFunction(name: string, args: string[], context: Conditio
259259 if ( normalizedName === "MAXIFS" ) {
260260 return aggregateIfs ( args , context , "max" ) ;
261261 }
262+ if ( normalizedName === "INDEX" ) {
263+ return indexFormulaValue ( args , context ) ;
264+ }
265+ if ( normalizedName === "MATCH" ) {
266+ return matchFormulaValue ( args , context ) ;
267+ }
268+ if ( normalizedName === "VLOOKUP" ) {
269+ return vlookupFormulaValue ( args , context ) ;
270+ }
271+ if ( normalizedName === "XLOOKUP" ) {
272+ return xlookupFormulaValue ( args , context ) ;
273+ }
262274 if ( normalizedName === "TODAY" ) {
263275 return currentExcelSerialDay ( ) ;
264276 }
@@ -490,6 +502,80 @@ function formulaRange(expression: string, context: ConditionalFormulaContext): R
490502 return parseCellRange ( formulaRangeTarget ( expression , context ) ) ;
491503}
492504
505+ function indexFormulaValue ( args : string [ ] , context : ConditionalFormulaContext ) : string {
506+ const range = formulaRange ( args [ 0 ] ?? "" , context ) ;
507+ const cells = formulaRangeCells ( args [ 0 ] ?? "" , context ) ;
508+ if ( ! range || ! cells ) return "" ;
509+ const rowOffset = Math . max ( 0 , Math . trunc ( Number ( evaluateFormulaValue ( args [ 1 ] ?? "1" , context ) ) ) - 1 ) ;
510+ const columnOffset = Math . max ( 0 , Math . trunc ( Number ( evaluateFormulaValue ( args [ 2 ] ?? "1" , context ) ) ) - 1 ) ;
511+ return cellsByOffset ( cells , range ) . get ( `${ rowOffset } :${ columnOffset } ` ) ?? "" ;
512+ }
513+
514+ function matchFormulaValue ( args : string [ ] , context : ConditionalFormulaContext ) : number {
515+ const cells = formulaRangeCells ( args [ 1 ] ?? "" , context ) ;
516+ if ( ! cells ) return Number . NaN ;
517+ const lookupValue = evaluateFormulaValue ( args [ 0 ] ?? "" , context ) ;
518+ const matchType = Number ( evaluateFormulaValue ( args [ 2 ] ?? "1" , context ) ) ;
519+ return matchLookupPosition ( lookupValue , cells . map ( ( cell ) => cell . value ) , Number . isFinite ( matchType ) ? Math . trunc ( matchType ) : 1 ) ;
520+ }
521+
522+ function vlookupFormulaValue ( args : string [ ] , context : ConditionalFormulaContext ) : string {
523+ const range = formulaRange ( args [ 1 ] ?? "" , context ) ;
524+ const cells = formulaRangeCells ( args [ 1 ] ?? "" , context ) ;
525+ if ( ! range || ! cells ) return "" ;
526+ const columnOffset = Math . trunc ( Number ( evaluateFormulaValue ( args [ 2 ] ?? "1" , context ) ) ) - 1 ;
527+ if ( ! Number . isFinite ( columnOffset ) || columnOffset < 0 || columnOffset >= range . columnSpan ) return "" ;
528+ const lookupValue = evaluateFormulaValue ( args [ 0 ] ?? "" , context ) ;
529+ const exact = ! valueToBoolean ( evaluateFormulaValue ( args [ 3 ] ?? "TRUE" , context ) ) ;
530+ const byOffset = cellsByOffset ( cells , range ) ;
531+ const firstColumn = cells
532+ . filter ( ( cell ) => cell . columnIndex === range . startColumn )
533+ . sort ( ( left , right ) => left . rowIndex - right . rowIndex ) ;
534+ const rowPosition = exact
535+ ? firstColumn . findIndex ( ( cell ) => formulaLookupValuesEqual ( cell . value , lookupValue ) )
536+ : matchLookupPosition ( lookupValue , firstColumn . map ( ( cell ) => cell . value ) , 1 ) - 1 ;
537+ return rowPosition >= 0 ? byOffset . get ( `${ rowPosition } :${ columnOffset } ` ) ?? "" : "" ;
538+ }
539+
540+ function xlookupFormulaValue ( args : string [ ] , context : ConditionalFormulaContext ) : string {
541+ const lookupCells = formulaRangeCells ( args [ 1 ] ?? "" , context ) ;
542+ const returnRange = formulaRange ( args [ 2 ] ?? "" , context ) ;
543+ const returnCells = formulaRangeCells ( args [ 2 ] ?? "" , context ) ;
544+ if ( ! lookupCells || ! returnRange || ! returnCells ) return asString ( evaluateFormulaValue ( args [ 3 ] ?? "" , context ) ) ;
545+ const lookupValue = evaluateFormulaValue ( args [ 0 ] ?? "" , context ) ;
546+ const matchIndex = lookupCells . findIndex ( ( cell ) => formulaLookupValuesEqual ( cell . value , lookupValue ) ) ;
547+ if ( matchIndex < 0 ) return asString ( evaluateFormulaValue ( args [ 3 ] ?? "" , context ) ) ;
548+ return returnCells [ matchIndex ] ?. value ?? cellsByOffset ( returnCells , returnRange ) . get ( `${ matchIndex } :0` ) ?? "" ;
549+ }
550+
551+ function matchLookupPosition ( lookupValue : unknown , values : string [ ] , matchType : number ) : number {
552+ if ( matchType === 0 ) {
553+ const index = values . findIndex ( ( value ) => formulaLookupValuesEqual ( value , lookupValue ) ) ;
554+ return index >= 0 ? index + 1 : Number . NaN ;
555+ }
556+
557+ let matchedIndex = - 1 ;
558+ for ( let index = 0 ; index < values . length ; index += 1 ) {
559+ const comparison = formulaLookupCompare ( values [ index ] , lookupValue ) ;
560+ if ( matchType < 0 ? comparison >= 0 : comparison <= 0 ) matchedIndex = index ;
561+ }
562+ return matchedIndex >= 0 ? matchedIndex + 1 : Number . NaN ;
563+ }
564+
565+ function formulaLookupValuesEqual ( left : unknown , right : unknown ) : boolean {
566+ const leftNumber = Number ( left ) ;
567+ const rightNumber = Number ( right ) ;
568+ if ( Number . isFinite ( leftNumber ) && Number . isFinite ( rightNumber ) ) return leftNumber === rightNumber ;
569+ return asString ( left ) . trim ( ) . toLowerCase ( ) === asString ( right ) . trim ( ) . toLowerCase ( ) ;
570+ }
571+
572+ function formulaLookupCompare ( left : unknown , right : unknown ) : number {
573+ const leftNumber = Number ( left ) ;
574+ const rightNumber = Number ( right ) ;
575+ if ( Number . isFinite ( leftNumber ) && Number . isFinite ( rightNumber ) ) return leftNumber - rightNumber ;
576+ return asString ( left ) . trim ( ) . toLowerCase ( ) . localeCompare ( asString ( right ) . trim ( ) . toLowerCase ( ) ) ;
577+ }
578+
493579function cellsByOffset ( cells : FormulaCellValue [ ] , range : NonNullable < ReturnType < typeof parseCellRange > > ) : Map < string , string > {
494580 const map = new Map < string , string > ( ) ;
495581 for ( const cell of cells ) {
0 commit comments