/// <summary> /// Extracts a block of text delimited by the specified open and close /// characters. It is assumed the parser is positioned at an /// occurrence of the open character. The open and closing characters /// are not included in the returned string. On return, the parser is /// positioned at the closing character or at the end of the text if /// the closing character was not found. /// </summary> /// <param name="parser">ParsingHelper object</param> /// <param name="openChar">Start-of-block delimiter</param> /// <param name="closeChar">End-of-block delimiter</param> /// <returns>The extracted text</returns> internal static string ExtractBlock(ParsingHelper parser, char openChar, char closeChar) { // Track delimiter depth int depth = 1; // Extract characters between delimiters parser.MoveAhead(); int start = parser.Index; while (!parser.EndOfText) { char ch = parser.Peek(); if (ch == openChar) { // Increase block depth depth++; } else if (ch == closeChar) { // Decrease block depth depth--; // Test for end of block if (depth == 0) { break; } } else if (ch == '"' || ch == '\'') { // Don't count delimiters within quoted text parser.MoveAhead(); parser.SkipWhile(c => c != ch); } // Move to next character parser.MoveAhead(); } return(parser.Extract(start, parser.Index)); }
/// <summary> /// Parses a query segment and converts it to an expression /// tree. /// </summary> /// <param name="query">Query segment to be converted.</param> /// <param name="defaultConjunction">Implicit conjunction type.</param> /// <returns>Root node of expression tree.</returns> internal INode?ParseNode(string?query, ConjunctionType defaultConjunction) { ConjunctionType conjunction = defaultConjunction; TermForm termForm = TermForm.Inflectional; bool termExclude = false; bool resetState = true; INode? root = null; INode? node; string term; ParsingHelper parser = new ParsingHelper(query); while (!parser.EndOfText) { if (resetState) { // Reset modifiers conjunction = defaultConjunction; termForm = TermForm.Inflectional; termExclude = false; resetState = false; } parser.SkipWhitespace(); if (parser.EndOfText) { break; } char ch = parser.Peek(); if (Punctuation.Contains(ch)) { switch (ch) { case '"': case '\'': termForm = TermForm.Literal; parser.MoveAhead(); term = parser.ParseWhile(c => c != ch); root = AddNode(root, term.Trim(), termForm, termExclude, conjunction); resetState = true; break; case '(': // Parse parentheses block term = ExtractBlock(parser, '(', ')'); node = ParseNode(term, defaultConjunction); root = AddNode(root, node, conjunction, true); resetState = true; break; case '<': // Parse angle brackets block term = ExtractBlock(parser, '<', '>'); node = ParseNode(term, ConjunctionType.Near); root = AddNode(root, node, conjunction); resetState = true; break; case '-': // Match when next term is not present termExclude = true; break; case '+': // Match next term exactly termForm = TermForm.Literal; break; case '~': // Match synonyms of next term termForm = TermForm.Thesaurus; break; default: break; } // Advance to next character parser.MoveAhead(); } else { // Parse this query term term = parser.ParseWhile(c => !Punctuation.Contains(c) && !char.IsWhiteSpace(c)); // Allow trailing wildcard if (parser.Peek() == '*') { term += parser.Peek(); parser.MoveAhead(); termForm = TermForm.Literal; } // Interpret term StringComparer comparer = StringComparer.OrdinalIgnoreCase; if (comparer.Compare(term, "AND") == 0) { conjunction = ConjunctionType.And; } else if (comparer.Compare(term, "OR") == 0) { conjunction = ConjunctionType.Or; } else if (comparer.Compare(term, "NEAR") == 0) { conjunction = ConjunctionType.Near; } else if (comparer.Compare(term, "NOT") == 0) { termExclude = true; } else { root = AddNode(root, term, termForm, termExclude, conjunction); resetState = true; } } } return(root); }