All files / src/libs/Search SearchParser.ts

100% Statements 60/60
100% Branches 27/27
100% Functions 8/8
100% Lines 58/58

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 1882x 2x           2x 2x         2x             27x 27x 27x 27x     27x 10384x   2x 2x     10382x 17x     17x 8x   10365x   109x 10256x   24x     24x           3x     21x 10232x   40x 17x     10192x     10379x       24x 1x       23x 19x       23x     23x   23x               23x   23x 38x 38x   38x         10x 10x                   23x   23x 42x 42x   42x         8x   8x                     80x                   44x 44x   44x                       21x 21x                 10256x                 10380x      
import SearchOperator from './SearchOperator';
import {
    SearchOperators,
    SearchOperatorType,
    SearchQuotes,
    SearchQuoteType,
} from './SearchOperatorTypes';
import SearchQuery from './SearchQuery';
import SearchTerm from './SearchTerm';
 
/**
 * Class responsible for parsing a search string into a SearchQuery.
 */
export default class SearchParser {
    /**
     * Parses the given search text into a SearchQuery object.
     * @param searchText - The search text to parse.
     * @returns A SearchQuery object containing the parsed search elements.
     */
    public static parse(searchText: string): SearchQuery {
        const query = new SearchQuery();
        let openQuote = false;
        let term = '';
        let escapeNextChar = false;
 
        // Iterate over each character in the search text
        for (const char of searchText) {
            if (char === '\\') {
                // Escape the next character
                escapeNextChar = true;
                continue;
            }
 
            if (!escapeNextChar && this.isQuote(char)) {
                openQuote = !openQuote;
 
                // Add the term if the quote is closed and the term is not empty
                if (!this.isTermEmpty(term)) {
                    term = this.addTerm(query, term);
                }
            } else if (openQuote) {
                // Add the character to the term if inside a quote
                term += char;
            } else if (this.isOperator(char)) {
                // Add the Operator to the query
                const previousSearchElement = query.getElements().slice(-1)[0];
 
                // Check for consecutive operators
                if (
                    previousSearchElement instanceof SearchOperator &&
                    ((previousSearchElement.operator !== '!' && char !== '!') ||
                        (previousSearchElement.operator === '!' &&
                            char === '!'))
                ) {
                    throw new Error('Consecutive operators are not allowed');
                }
 
                this.addOperator(query, char as SearchOperatorType);
            } else if (char === ' ') {
                // Spaces outside quotation marks separate terms
                if (!this.isTermEmpty(term)) {
                    term = this.addTerm(query, term);
                }
            } else {
                term += char;
            }
 
            escapeNextChar = false;
        }
 
        // Check for unmatched quotes
        if (openQuote) {
            throw new Error('Unmatched quote');
        }
 
        // Add the final term to the query
        if (!this.isTermEmpty(term)) {
            term = this.addTerm(query, term);
        }
 
        // Add default AND operators between search terms if no operators are specified
        this.handleDefaultOperator(query);
 
        // Combine negations with search terms and remove negation operators
        this.combineNegationsAndElements(query);
 
        return query;
    }
 
    /**
     * Adds default AND operators between search terms if no operators are specified.
     * @param query The SearchQuery object to add operators to.
     */
    private static handleDefaultOperator(query: SearchQuery): void {
        const searchElements = query.getElements();
 
        for (let i = 0; i < searchElements.length - 1; i++) {
            const currentElement = searchElements[i];
            const nextElement = searchElements[i + 1];
 
            if (
                currentElement instanceof SearchTerm &&
                nextElement instanceof SearchTerm
            ) {
                // Add an AND operator between search terms
                searchElements.splice(i + 1, 0, new SearchOperator('&'));
                i++; // Skip the next element to avoid adding multiple operators
            }
        }
    }
 
    /**
     * Combines negations with search terms and removes negation operators.
     * @param query The SearchQuery object to process.
     */
    private static combineNegationsAndElements(query: SearchQuery): void {
        const searchElements = query.getElements();
 
        for (let i = 0; i < searchElements.length - 1; i++) {
            const currentElement = searchElements[i];
            const nextElement = searchElements[i + 1];
 
            if (
                currentElement instanceof SearchOperator &&
                currentElement.operator === '!'
            ) {
                // remove the negation operator
                searchElements.splice(i, 1);
                // add the negation to the next element
                nextElement.isNegated = true;
            }
        }
    }
 
    /**
     * Checks if the term is empty.
     * @param term The term to check.
     * @returns True if the term is empty, false otherwise.
     */
    private static isTermEmpty(term: string): boolean {
        return term.trim() === '';
    }
 
    /**
     * Adds a search term to the query.
     * @param query The SearchQuery object to add the term to.
     * @param term The term to add.
     * @returns An empty string.
     */
    private static addTerm(query: SearchQuery, term: string): string {
        const searchTerm = new SearchTerm(term);
        query.addElement(searchTerm);
 
        return '';
    }
 
    /**
     * Adds a search operator to the query.
     * @param query The SearchQuery object to add the operator to.
     * @param operator The operator to add.
     */
    private static addOperator(
        query: SearchQuery,
        operator: SearchOperatorType,
    ): void {
        const searchOperator = new SearchOperator(operator);
        query.addElement(searchOperator);
    }
 
    /**
     * Checks if the character is a search operator.
     * @param char - The character to check.
     * @returns True if the character is a search operator, false otherwise.
     */
    private static isOperator(char: string): boolean {
        return SearchOperators.includes(char as SearchOperatorType);
    }
 
    /**
     * Checks if the character is a quote.
     * @param char The character to check.
     * @returns True if the character is a quote, false otherwise.
     */
    private static isQuote(char: string): boolean {
        return SearchQuotes.includes(char as SearchQuoteType);
    }
}
 
Zur TypeDoc-Dokumentation