class ExtractionResult { public IsMatch: boolean; public Matches: any[]; constructor(isMatch: boolean) { this.IsMatch = isMatch; this.Matches = []; } } enum FormatStringTokenType { ConstantText, DynamicValue } class FormatStringToken { public Text: string; public Type: FormatStringTokenType; constructor(text: string, type: FormatStringTokenType) { this.Text = text; this.Type = type; } } class FormatStringTokenizer { Tokenize(format: string, includeBracketsForDynamicValues: boolean = false): FormatStringToken[] { const tokens: FormatStringToken[] = []; let currentText = ''; let inDynamicValue = false; for (let i = 0; i < format.length; i++) { const c = format[i]; switch (c) { case '{': if (inDynamicValue) { throw new Error('Incorrect syntax at char ' + i + '! format string can not contain nested dynamic value expression!'); } inDynamicValue = true; if (currentText.length > 0) { tokens.push(new FormatStringToken(currentText, FormatStringTokenType.ConstantText)); currentText = ''; } break; case '}': if (!inDynamicValue) { throw new Error(('Incorrect syntax at char ' + i + '! These is no opening brackets for the closing bracket }.')); } inDynamicValue = false; if (currentText.length <= 0) { throw new Error(('Incorrect syntax at char ' + i + '! Brackets does not containt any chars.')); } let dynamicValue = currentText; if (includeBracketsForDynamicValues) { dynamicValue = '{' + dynamicValue + '}'; } tokens.push(new FormatStringToken(dynamicValue, FormatStringTokenType.DynamicValue)); currentText = ''; break; default: currentText += c; break; } } if (inDynamicValue) { throw new Error(('There is no closing } char for an opened { char.')); } if (currentText.length > 0) { tokens.push(new FormatStringToken(currentText, FormatStringTokenType.ConstantText)); } return tokens; } } export class FormattedStringValueExtracter { Extract(str: string, format: string): ExtractionResult { if (str === format) { return new ExtractionResult(true); } const formatTokens = new FormatStringTokenizer().Tokenize(format); if (!formatTokens) { return new ExtractionResult(str === ''); } const result = new ExtractionResult(true); for (let i = 0; i < formatTokens.length; i++) { const currentToken = formatTokens[i]; const previousToken = i > 0 ? formatTokens[i - 1] : null; if (currentToken.Type === FormatStringTokenType.ConstantText) { if (i === 0) { if (str.indexOf(currentToken.Text) !== 0) { result.IsMatch = false; return result; } str = str.substr(currentToken.Text.length, str.length - currentToken.Text.length); } else { const matchIndex = str.indexOf(currentToken.Text); if (matchIndex < 0) { result.IsMatch = false; return result; } result.Matches.push({ name: previousToken.Text, value: str.substr(0, matchIndex) }); str = str.substring(0, matchIndex + currentToken.Text.length); } } } const lastToken = formatTokens[formatTokens.length - 1]; if (lastToken.Type === FormatStringTokenType.DynamicValue) { result.Matches.push({ name: lastToken.Text, value: str }); } return result; } IsMatch(str: string, format: string): string[] { const result = new FormattedStringValueExtracter().Extract(str, format); if (!result.IsMatch) { return []; } const values = []; for (let i = 0; i < result.Matches.length; i++) { values.push(result.Matches[i].value); } return values; } }