/// ------------------------------------------------------------------------------------ public void VerifyDiacriticPlaceholders(string pattern) { var match = PatternParser.FindInnerMostSquareBracketPairs(pattern); while (match.Success) { var bracketedText = match.Result("${bracketedText}"); if (bracketedText.Contains(App.DottedCircleC)) { bracketedText = bracketedText.Replace(App.DottedCircle, string.Empty); if (bracketedText.Contains("+") && bracketedText.Contains("*")) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.InvalidDiacriticPlaceholderSyntaxMsg", "The symbols '*' and '+' may not appear between square brackets together " + "with a diacritic placeholder. One or the other is allowed, but not both.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsDiacriticPlaceholders", "hidSearchPatternsExamples" }); Errors.Add(error); } if (bracketedText.Count(s => s == '+') > 1) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.TooManyOneOrMoreSymbolsInDiacriticPlaceholderMsg", "The One-Or-More-Diacritics symbol (+) appears too many times with a diacritic " + "placeholder. Only one is allowed.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsDiacriticPlaceholders", "hidSearchPatternsExamples" }); Errors.Add(error); } if (bracketedText.Count(s => s == '*') > 1) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.TooManyZeroOrMoreSymbolsInDiacriticPlaceholderMsg", "The Zero-Or-More-Diacritics symbol (*) appears too many times with a " + "diacritic placeholder. Only one is allowed.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsDiacriticPlaceholders", "hidSearchPatternsExamples" }); Errors.Add(error); } foreach (var symbolInfo in bracketedText.Where(s => s != '+' && s != '*') .Select(s => App.IPASymbolCache[s]).Where(s => s != null && s.IsBase)) { var msg = LocalizationManager.GetString("PhoneticSearchingMessages.InvalidSymbolInDiacriticPlaceholderMsg", "The symbol '{0}' is a base character and was found between square brackets " + "together with a diacritic placeholder. Base characters are not allowed with " + "diacritic placeholders."); var error = new SearchQueryValidationError(string.Format(msg, symbolInfo.Literal)); error.HelpLinks.AddRange(new[] { "hidSearchPatternsDiacriticPlaceholders", "hidSearchPatternsExamples" }); Errors.Add(error); } } match = match.NextMatch(); } }
/// ------------------------------------------------------------------------------------ public void LookForInvalidOrGroupItems(string orItem) { if ("[]{}()+*_#<>".Contains(orItem)) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.InvalidSymbolsInORGroup", "The symbols '[]{}()<>+*_#' are not allowed in OR groups.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsOrGroups", "hidSearchPatternsExamples" }); Errors.Add(error); } if (orItem.Contains(App.DottedCircle)) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.InvalidDiacriticPlaceholderInORGroup", "Diacritic placeholders are not valid in OR groups.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsOrGroups", "hidSearchPatternsDiacriticPlaceholders", "hidSearchPatternsExamples" }); Errors.Add(error); } var phonesInMember = _project.PhoneticParser.Parse(orItem, true, false); if (phonesInMember == null) { var msg = LocalizationManager.GetString("PhoneticSearchingMessages.InvalidORGroupMember", "The text '{0}' is not recognized as valid phonetic data."); var error = new SearchQueryValidationError(string.Format(msg, orItem)); error.HelpLinks.AddRange(new[] { "hidTroubleshootingUndefinedPhoneticCharacters", "hidSearchPatternsExamples" }); Errors.Add(error); } }
/// ------------------------------------------------------------------------------------ public void LookForMalformedItems(string[] orItems) { foreach (var item in orItems) { var text = item.Trim(); LookForInvalidOrGroupItems(text); var phones = _project.PhoneticParser.Parse(text, true, false); if (phones.Length == 1 || (text.StartsWith("(", StringComparison.Ordinal) && text.EndsWith(")", StringComparison.Ordinal))) { continue; } text = string.Empty; text = phones.Aggregate(text, (curr, phone) => curr + phone); text = TranslateTokenizedTextToReadableText(text); var msg = LocalizationManager.GetString("PhoneticSearchingMessages.OrGroupContainsPhoneRunMsg", "A match on the pattern '{0}' will include more than one phone. This text was " + "found in an OR group and as such, it is invalid. OR groups should contain " + "multiple items separated by commas and each item may represent a match on only " + "one phone unless the item is surrounded by parentheses. Either one or more " + "commas are missing or the item should be placed between parentheses."); var error = new SearchQueryValidationError(string.Format(msg, text)); error.HelpLinks.AddRange(new[] { "hidSearchPatternsOrGroups", "hidSearchPatternsExamples" }); Errors.Add(error); } }
/// ------------------------------------------------------------------------------------ public void VerifyPhonesAndSymbols(SearchQuery query) { var phonesNotInCache = GetPhonesNotInCache(query).ToArray(); if (phonesNotInCache.Length > 0) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.PatternPhonesNotInDataMsg", "The following phone(s) are not found in the data:")); foreach (var phone in phonesNotInCache) { error.PhonesNotInCache.Add(phone); } Errors.Add(error); } var symbolsNotInCache = GetSymbolsNotInInventory(query.Pattern).ToArray(); if (symbolsNotInCache.Length > 0) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.UnknownSymbolsFoundInPatternMsg", "The following undefined phonetic symbol(s) were found:")); foreach (var symbol in symbolsNotInCache) { error.SymbolsNotInInventory.Add(symbol); } error.HelpLinks.AddRange(new[] { "hidSearchPatternsTroubleshooting", "hidTroubleshootingUndefinedPhoneticCharacters" }); Errors.Add(error); } }
/// ------------------------------------------------------------------------------------ public void VerifyTextInSquareBrackets(string pattern) { var match = PatternParser.FindInnerMostSquareBracketPairs(pattern); while (match.Success) { var bracketedText = match.Result("${bracketedText}"); if (bracketedText != string.Empty && !bracketedText.Contains(App.DottedCircle) && bracketedText != "C" && bracketedText != "V" && !App.AFeatureCache.Keys.Any(f => f == bracketedText) && !App.BFeatureCache.Keys.Any(f => f == bracketedText) && !bracketedText.Contains(App.ProportionalToSymbol)) { var msg = LocalizationManager.GetString("PhoneticSearchingMessages.InvalidTextInSquareBracketsMsg", "The text '{0}' in square brackets is invalid. Other than surrounding " + "'AND' groups, square brackets are only used to surround descriptive or " + "distinctive features, the designators for consonant or vowel classes " + "('[C]' and '[V]'), or a diacritic placeholder with its diacritics and wildcards."); var error = new SearchQueryValidationError(string.Format(msg, bracketedText)); error.HelpLinks.AddRange(new[] { "hidSearchPatternsAndGroups", "hidSearchPatternsExamples" }); Errors.Add(error); } match = match.NextMatch(); } }
/// ------------------------------------------------------------------------------------ public override void Verify(string orGroupPattern) { base.Verify(orGroupPattern); var pattern = TranslateTextBetweenOpenAndCloseSymbolsToTokens( orGroupPattern.Trim('{', '}'), PatternParser.FindInnerAngleBracketPairs); pattern = TranslateTextBetweenOpenAndCloseSymbolsToTokens( pattern, PatternParser.FindInnerMostSquareBracketPairs); var orItems = pattern.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); LookForMalformedItems(orItems); if (HasErrors || orItems.Length > 1) { return; } var msg = LocalizationManager.GetString("PhoneticSearchingMessages.OrGroupContainsOnlyOneItemMsg", "The OR group '{0}' only contains one item. OR groups should contain multiple items " + "separated by commas."); var error = new SearchQueryValidationError(string.Format(msg, orGroupPattern)); error.HelpLinks.AddRange(new[] { "hidSearchPatternsOrGroups", "hidSearchPatternsExamples" }); Errors.Add(error); }
/// ------------------------------------------------------------------------------------ public SearchEngine(SearchQuery query) { CurrentSearchQuery = query; _errors.Clear(); try { var parser = new PatternParser(App.Project); SrchItemPatternGroup = parser.Parse(query.SearchItem, EnvironmentType.Item); EnvBeforePatternGroup = parser.Parse(query.PrecedingEnvironment, EnvironmentType.Before); EnvAfterPatternGroup = parser.Parse(query.FollowingEnvironment, EnvironmentType.After); } catch { var error = new SearchQueryValidationError( string.Format(GetPatternSyntaxErrorMsg(), App.kEmptyDiamondPattern)); _errors.Add(error); } m_srchItemStr = query.SearchItem; m_envBeforeStr = query.PrecedingEnvironment; m_envAfterStr = query.FollowingEnvironment; if (_errors != null && _errors.Count > 0) { query.Errors.AddRange(_errors); } }
/// ------------------------------------------------------------------------------------ public bool GetIsValid(SearchQuery query) { try { Errors.Clear(); VerifyMatchingSymbolPairs(query.Pattern); var pattern = query.SearchItem + "/" + query.PrecedingEnvironment + "_" + query.FollowingEnvironment; if (!VerifyGeneralPatternStructure(pattern)) { var error = new SearchQueryValidationError(GetPatternSyntaxErrorMsg()); Errors.Add(error); } VerifySearchItem(query.SearchItem); VerifyPrecedingEnvironment(query.PrecedingEnvironment); VerifyFollowingEnvironment(query.FollowingEnvironment); VerifyPhonesAndSymbols(query); foreach (var item in new[] { query.SearchItem, query.PrecedingEnvironment, query.FollowingEnvironment }) { VerifyTextInSquareBrackets(item); VerifyTextInAngleBrackets(item); VerifyDiacriticPlaceholders(item); VerifyOneDiacriticPlaceholderPerAndGroup(item); VerifyNoEmptyTextBetweenOpenAndCloseSymbols(item, PatternParser.FindInnerMostSquareBracketPairs, string.Format(LocalizationManager.GetString("PhoneticSearchingMessages.EmptySquareBracketsMsg", "The pattern '{0}' contains at least one set of empty square brackets."), item)); VerifyNoEmptyTextBetweenOpenAndCloseSymbols(item, PatternParser.FindInnerMostBracesPair, string.Format(LocalizationManager.GetString("PhoneticSearchingMessages.EmptyBracesMsg", "The pattern '{0}' contains at least one set of empty braces."), item)); VerifyNoEmptyTextBetweenOpenAndCloseSymbols(item, PatternParser.FindInnerAngleBracketPairs, string.Format(LocalizationManager.GetString("PhoneticSearchingMessages.EmptyAngleBracketsMsg", "The pattern '{0}' contains at least one set of empty angle brackets."), item)); VerifyMatchingSymbolPairs(item); ValidateOrGroups(item); var andGroupValidator = new AndGroupValidator(_project); andGroupValidator.Verify(item); if (andGroupValidator.HasErrors) { Errors.AddRange(andGroupValidator.Errors); } } } catch (Exception e) { Errors.Add(SearchQueryValidationError.MakeErrorFromException(e, query.Pattern)); } query.Errors.AddRange(Errors); return(!HasErrors); }
/// ------------------------------------------------------------------------------------ public SearchQueryValidationError Copy() { var error = new SearchQueryValidationError(Message); error.HelpLinks.AddRange(HelpLinks); error.Exception = Exception; error.PhonesNotInCache = PhonesNotInCache.ToList(); error.SymbolsNotInInventory = SymbolsNotInInventory.ToList(); return(error); }
/// ------------------------------------------------------------------------------------ public void VerifyMatchingOpenAndCloseSymbols(string pattern, char open, char close, string errorMsg) { if (pattern.Count(c => c == open) != pattern.Count(c => c == close)) { var error = new SearchQueryValidationError(errorMsg); error.HelpLinks.AddRange(new[] { "hidSearchPatternsOverview", "hidSearchPatternsExamples" }); Errors.Add(error); } }
/// ------------------------------------------------------------------------------------ public static SearchQueryValidationError MakeErrorFromException(Exception e, string pattern) { var error = new SearchQueryValidationError(e); var msg = LocalizationManager.GetString( "PhoneticSearchingMessages.UnhandledExceptionWhileValidatingMsg", "Validating the pattern '{0}' caused the following exception:"); error.Message = string.Format(msg, pattern); return(error); }
/// ------------------------------------------------------------------------------------ private void LookForMalformedGroup(string andGroupPattern) { var origAndGroupPattern = andGroupPattern; andGroupPattern = andGroupPattern.Trim('[', ']'); // Convert classes within the AND group to tokens. // TODO: Add a check for AND groups containing more than one phone class or // one phone class and another phone. That is an invalid combination. andGroupPattern = TranslateTextBetweenOpenAndCloseSymbolsToTokens( andGroupPattern, PatternParser.FindInnerAngleBracketPairs); // Convert OR groups within the AND group to tokens. andGroupPattern = TranslateTextBetweenOpenAndCloseSymbolsToTokens( andGroupPattern, PatternParser.FindInnerMostBracesPair); // At this point any commas found are invalid. if (andGroupPattern.Contains(',')) { var msg = string.Format(LocalizationManager.GetString("PhoneticSearchingMessages.AndGroupContainsCommaMsg", "The AND group '{0}' contains a comma. Commas are only valid in OR groups."), TranslateTokenizedTextToReadableText(origAndGroupPattern)); var error = new SearchQueryValidationError(msg); error.HelpLinks.AddRange(new[] { "hidSearchPatternsAndGroups", "hidSearchPatternsOrGroups", "hidSearchPatternsExamples" }); Errors.Add(error); andGroupPattern = andGroupPattern.Replace(",", string.Empty); } // Collect all the characters that are not tokens and parse them into phones. If // more than one phone is found (i.e. a run), then the AND group is malformed. var text = andGroupPattern.Where(c => c < kMinToken) .Aggregate(string.Empty, (curr, c) => curr + c.ToString()); if (text == string.Empty) { return; } var phones = _project.PhoneticParser.Parse(text, true, false); if (phones.Length > 1) { var msg = string.Format(LocalizationManager.GetString("PhoneticSearchingMessages.AndGroupContainsPhoneRunMsg", "The AND group '{0}' contains more than one literal phone, which is invalid and does " + "not make sense in an AND group. Only single phones are allowed in AND groups."), TranslateTokenizedTextToReadableText(origAndGroupPattern)); var error = new SearchQueryValidationError(msg); error.HelpLinks.AddRange(new[] { "hidSearchPatternsAndGroups", "hidSearchPatternsExamples" }); Errors.Add(error); } }
/// ------------------------------------------------------------------------------------ public void VerifyNoEmptyTextBetweenOpenAndCloseSymbols(string pattern, Func <string, Match> groupingSymbolRegexProvider, string errorMsg) { var match = groupingSymbolRegexProvider(pattern); while (match.Success) { var bracketedText = match.Result("${bracketedText}"); if (bracketedText == string.Empty) { var error = new SearchQueryValidationError(string.Format(errorMsg, pattern)); error.HelpLinks.AddRange(new[] { "hidSearchPatternsAndGroups", "hidSearchPatternsOrGroups", "hidSearchPatternsOverview" }); Errors.Add(error); return; } match = match.NextMatch(); } }
/// ------------------------------------------------------------------------------------ public void VerifyOneDiacriticPlaceholderPerAndGroup(string pattern) { var originalPattern = pattern; var match = PatternParser.FindInnerMostSquareBracketPairs(pattern); while (match.Success) { var newPattern = pattern.Substring(0, match.Index) + (match.Value.Contains(App.DottedCircleC) ? '$' : '@'); newPattern += new string('@', match.Length - 1); if (match.Index + match.Length < pattern.Length) { newPattern += pattern.Substring(match.Index + match.Length); } pattern = newPattern; match = match.NextMatch(); } pattern = pattern.Replace("@", string.Empty); match = PatternParser.FindInnerMostSquareBracketPairs(pattern); while (match.Success) { if (match.Value.Count(c => c == '$') > 1) { var msg = LocalizationManager.GetString("PhoneticSearchingMessages.TooManyDiacriticPlaceholderMsg", "The pattern '{0}' contains too many diacritic placeholders in one of the AND groups. " + "Only one diacritic placeholder is allowed per AND group."); var error = new SearchQueryValidationError(string.Format(msg, originalPattern)); error.HelpLinks.AddRange(new[] { "hidSearchPatternsDiacriticPlaceholders", "hidSearchPatternsExamples" }); Errors.Add(error); return; } match = match.NextMatch(); } }
/// ------------------------------------------------------------------------------------ public void VerifyTextInAngleBrackets(string pattern) { var match = PatternParser.FindInnerAngleBracketPairs(pattern); while (match.Success) { var bracketedText = match.Result("${bracketedText}"); if (!_project.SearchClasses.Any(c => c.Name == bracketedText)) { var msg = LocalizationManager.GetString("PhoneticSearchingMessages.InvalidTextInAngleBracketsMsg", "The text '{0}' in angled brackets is not a valid class name."); var error = new SearchQueryValidationError(string.Format(msg, bracketedText)); error.HelpLinks.Add("hidSearchPatternsExamples"); Errors.Add(error); } match = match.NextMatch(); } }
/// ------------------------------------------------------------------------------------ public void VerifySearchItem(string srchItemPattern) { if (string.IsNullOrEmpty(srchItemPattern)) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.SearchItemMissingMsg", "You must specify a search item.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsOverview" }); Errors.Add(error); } if (StripOutStuffWithValidPlusAndStarSymbols(srchItemPattern).Count(c => "#*+".Contains(c)) > 0) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.InvalidCharactersInSearchItemMsg", "The search item portion of the search pattern contains an illegal symbol. " + "The symbols '#', '+' and '*' are not valid in the search item.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsZeroOrMore", "hidSearchPatternsOneOrMore", "hidSearchPatternsSpaceOrWrdBoundary", "hidSearchPatternsExamples" }); Errors.Add(error); } }
/// ------------------------------------------------------------------------------------ public void VerifyFollowingEnvironment(string followingEnv) { var envWithoutPlusSymbols = StripOutStuffWithValidPlusAndStarSymbols(followingEnv); if (envWithoutPlusSymbols.Count(c => "#*+".Contains(c)) > 1) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.InvalidCharactersInFollowingEnvironmentMsg", "The following environment portion of the search pattern contains an illegal combination of characters. " + "The symbols '#', '+' or '*' are not allowed together in the following environment.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsZeroOrMore", "hidSearchPatternsOneOrMore", "hidSearchPatternsSpaceOrWrdBoundary", "hidSearchPatternsExamples" }); Errors.Add(error); } var count = followingEnv.Count(c => c == '#'); if (count > 1) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.TooManyWordBoundarySymbolsInFollowingEnvironmentMsg", "The following environment portion of the search pattern contains too many word boundary symbols (#). " + "Only one is allowed and it must be at the end.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsSpaceOrWrdBoundary", "hidSearchPatternsExamples" }); Errors.Add(error); } if (count == 1 && !followingEnv.EndsWith("#", StringComparison.Ordinal)) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.MisplacedWordBoundarySymbolInFollowingEnvironmentMsg", "The following environment portion of the search pattern contains a misplaced word boundary symbol (#). " + "It must be at the end.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsSpaceOrWrdBoundary", "hidSearchPatternsExamples" }); Errors.Add(error); } count = followingEnv.Count(c => c == '*'); if (count > 1) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.TooManyZeroOrMoreSymbolsInFollowingEnvironmentMsg", "The following environment portion of the search pattern contains too many zero or more' symbols (*). " + "Only one is allowed and it must be at the end.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsZeroOrMore", "hidSearchPatternsExamples" }); Errors.Add(error); } if (count == 1 && !followingEnv.EndsWith("*", StringComparison.Ordinal)) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.MisplacedZeroOrMoreSymbolInFollowingEnvironmentMsg", "The following environment portion of the search pattern contains a misplaced 'zero or more' symbol (*). " + "It must be at the end.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsZeroOrMore", "hidSearchPatternsExamples" }); Errors.Add(error); } count = envWithoutPlusSymbols.Count(c => c == '+'); if (count > 1) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.TooManyOneOrMoreSymbolsInFollowingEnvironmentMsg", "The following environment portion of the search pattern contains too many 'one or more' symbols (+). " + "Only one is allowed and it must be at the end.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsOneOrMore", "hidSearchPatternsExamples" }); Errors.Add(error); } if (count == 1 && !followingEnv.EndsWith("+", StringComparison.Ordinal)) { var error = new SearchQueryValidationError( LocalizationManager.GetString("PhoneticSearchingMessages.MisplacedOneOrMoreSymbolInFollowingEnvironmentMsg", "The following environment portion of the search pattern contains a misplaced 'one or more' symbol (+). " + "It must be at the end.")); error.HelpLinks.AddRange(new[] { "hidSearchPatternsOneOrMore", "hidSearchPatternsExamples" }); Errors.Add(error); } }