@@ -5,21 +5,28 @@ import { ASTNode, ArrayLiteralNode, Token, TokenType, ValidFuncNames } from "./F
5
5
6
6
export class FormulaEvaluation {
7
7
private webUrl : string ;
8
- constructor ( context : IContext , webUrlOverride ?: string ) {
9
- sp . setup ( { pageContext : context . pageContext } ) ;
10
- this . webUrl = webUrlOverride || context . pageContext . web . absoluteUrl ;
8
+ private _meEmail : string ;
9
+
10
+ constructor ( context ?: IContext , webUrlOverride ?: string ) {
11
+ if ( context ) {
12
+ sp . setup ( { pageContext : context . pageContext } ) ;
13
+ this . _meEmail = context . pageContext . user . email ;
14
+ }
15
+ this . webUrl = webUrlOverride || context ?. pageContext . web . absoluteUrl || '' ;
11
16
}
12
17
13
18
/** Evaluates a formula expression and returns the result, with optional context object for variables */
14
19
public evaluate ( expression : string , context : { [ key : string ] : any } = { } ) : any {
15
- const tokens : Token [ ] = this . _tokenize ( expression , context ) ;
16
- const postfix : Token [ ] = this . _shuntingYard ( tokens ) ;
17
- const ast : ASTNode = this . _buildAST ( postfix ) ;
20
+ context [ 'me' ] = this . _meEmail ;
21
+ context [ 'today' ] = new Date ( ) ;
22
+ const tokens : Token [ ] = this . tokenize ( expression , context ) ;
23
+ const postfix : Token [ ] = this . shuntingYard ( tokens ) ;
24
+ const ast : ASTNode = this . buildAST ( postfix ) ;
18
25
return this . evaluateASTNode ( ast , context ) ;
19
26
}
20
27
21
28
/** Tokenizes an expression into a list of tokens (primatives, operators, variables, function names, arrays etc) */
22
- private _tokenize ( expression : string , context : { [ key : string ] : any } ) : Token [ ] {
29
+ public tokenize ( expression : string , context : { [ key : string ] : any } = { } ) : Token [ ] {
23
30
// Each pattern captures a different token type
24
31
// and are matched in order
25
32
const patterns : [ RegExp , TokenType ] [ ] = [
@@ -89,7 +96,7 @@ export class FormulaEvaluation {
89
96
return tokens ;
90
97
}
91
98
92
- private _shuntingYard ( tokens : Token [ ] ) : Token [ ] {
99
+ public shuntingYard ( tokens : Token [ ] ) : Token [ ] {
93
100
94
101
/** Returns a precedence value for a token or operator */
95
102
function getPrecedence ( op : string ) : { precedence : number , associativity : "left" | "right" } {
@@ -234,7 +241,7 @@ export class FormulaEvaluation {
234
241
}
235
242
}
236
243
237
- private _buildAST ( postfixTokens : Token [ ] ) : ASTNode {
244
+ public buildAST ( postfixTokens : Token [ ] ) : ASTNode {
238
245
239
246
// Tokens are arranged on a stack/array of node objects
240
247
const stack : ( Token | ASTNode | ArrayLiteralNode ) [ ] = [ ] ;
@@ -287,7 +294,7 @@ export class FormulaEvaluation {
287
294
return stack [ 0 ] as ASTNode ;
288
295
}
289
296
290
- public evaluateASTNode ( node : ASTNode | ArrayLiteralNode | string | number , context : { [ key : string ] : any } ) : any {
297
+ public evaluateASTNode ( node : ASTNode | ArrayLiteralNode | string | number , context : { [ key : string ] : any } = { } ) : any {
291
298
292
299
if ( ! node ) return 0 ;
293
300
@@ -430,17 +437,17 @@ export class FormulaEvaluation {
430
437
return arrayToJoin . join ( separator ) ;
431
438
}
432
439
case 'substring' : {
433
- const mainStrSubstring = funcArgs [ 0 ] ;
434
- const start = funcArgs [ 1 ] ;
435
- const end = funcArgs [ 2 ] ;
440
+ const mainStrSubstring = funcArgs [ 0 ] || '' ;
441
+ const start = funcArgs [ 1 ] || 0 ;
442
+ const end = funcArgs [ 2 ] || mainStrSubstring . length ;
436
443
return mainStrSubstring . substr ( start , end ) ;
437
444
}
438
445
case 'toUpperCase' : {
439
- const strToUpper = funcArgs [ 0 ] ;
446
+ const strToUpper = funcArgs [ 0 ] || '' ;
440
447
return strToUpper . toUpperCase ( ) ;
441
448
}
442
449
case 'toLowerCase' : {
443
- const strToLower = funcArgs [ 0 ] ;
450
+ const strToLower = funcArgs [ 0 ] || '' ;
444
451
return strToLower . toLowerCase ( ) ;
445
452
}
446
453
case 'startsWith' : {
@@ -600,14 +607,32 @@ export class FormulaEvaluation {
600
607
601
608
return 0 ; // Default fallback
602
609
}
610
+
611
+ public validate ( expression : string ) : boolean {
612
+ const validFunctionRegex = `(${ ValidFuncNames . map ( fn => `${ fn } \\(` ) . join ( '|' ) } )` ;
613
+ const pattern = new RegExp ( `^(?:@\\w+|\\[\\$?[\\w+.]\\]|\\d+(?:\\.\\d+)?|"(?:[^"]*)"|'(?:[^']*)'|${ validFunctionRegex } |[+\\-*/<>=%!&|?:,()\\[\\]]|\\?|:)` ) ;
614
+
615
+ /* Explanation -
616
+ /@\\w+/ matches variables specified by the form @variableName.
617
+ /\\[\\$?\\w+\\/] matches variables specified by the forms [variableName] and [$variableName].
618
+ /\\d+(?:\\.\\d+)?/ matches numbers, including decimal numbers.
619
+ /"(?:[^"]*)"/ and /'(?:[^']*)'/ match string literals in double and single quotes, respectively.
620
+ /${validFunctionRegex}/ matches valid function names.
621
+ /\\?/ matches the ternary operator ?.
622
+ /:/ matches the colon :.
623
+ /[+\\-*/ //<>=%!&|?:,()\\[\\]]/ matches operators.
624
+
625
+ return pattern . test ( expression ) ;
626
+ }
627
+
603
628
private _getSharePointThumbnailUrl ( imageUrl : string ) : string {
604
629
const filename = imageUrl . split ( '/' ) . pop ( ) ;
605
630
const url = imageUrl . replace ( filename , '' ) ;
606
631
const [ filenameNoExt , ext ] = filename . split ( '.' ) ;
607
- return `${ url } / _t/${ filenameNoExt } _${ ext } .jpg` ;
632
+ return `${ url } _t/${ filenameNoExt } _${ ext } .jpg` ;
608
633
}
609
634
private _getUserImageUrl ( userEmail : string ) : string {
610
- return `${ this . webUrl } /_layouts/15/userphoto.aspx?size=L&username =${ userEmail } `
635
+ return `${ this . webUrl } /_layouts/15/userphoto.aspx?size=L&accountname =${ userEmail } `
611
636
}
612
637
}
613
638
0 commit comments