/// ------------------------------------------------------------------------------------
        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);
            }
        }