using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DataTableParsingEngine { /// /// Contains methods that assist with managing ad item names. /// public static class TextFormat { //private static readonly FrmLogConsole LogConsole = FrmLogConsole.GetStaticInstance; /// /// Formats an ad item's name / text into a standard format so as to help reduce redundancy in the database. /// /// The ad item's name to be formatted. /// Whether or not to preserve case on abbreviations. /// A cleaned up and formatted version of the ad item text. public static string FormatAdItemText(string adItemText, bool preserveAcronyms = false) { //Trim all beginning and trailing whitespace characters to start. adItemText = adItemText.Trim(); var isInsideBrackets = false; //This just makes the code a bit more readable rather then doing "adItemText[currentCharacter - 1]" to access the previous character. var isPreviousCharWhiteSpace = false; var cleanedInputString = ""; var lastnumberStartingIndex = -1; //Create an array of brackets to test for, and either balance out or simply ignore the extras. char[] openBrackets = { '(', '<', '{', '[' }; char[] closedBrackets = { ')', '>', '}', ']' }; for (var currentCharacter = 0; currentCharacter < adItemText.Length; currentCharacter++) { //Capitalize the first character in the string and move to the next character. if (currentCharacter == 0) { cleanedInputString += char.ToUpperInvariant(adItemText[0]); continue; } //IF the current character is a whitespace character, mark it as such, add to the temporary string, and move on to the next. if (char.IsWhiteSpace(adItemText[currentCharacter])) { //Check for more then one space in a row. if (isPreviousCharWhiteSpace) { //If more then one space is found to be in a row, then ignore it and move onto the next character. continue; } //IF the current character is whitespace, then mark it and move onto the next loop. isPreviousCharWhiteSpace = true; lastnumberStartingIndex = -1; //reset cleanedInputString += adItemText[currentCharacter]; continue; } //IF the current character is an open bracket then mark it so we're inside brackets and move to the next character. if (openBrackets.Contains(adItemText[currentCharacter])) { if (isInsideBrackets) { //If we are already inside of brackets then don't add anymore to the string just continue. continue; } isInsideBrackets = true; //Just gonna force parenthesis for now. cleanedInputString += '('; continue; } //IF the current character is a closing bracket then mark it as such and move to the next character (if any). if (closedBrackets.Contains(adItemText[currentCharacter])) { //IF we're not inside brackets then there is an imbalance so discard this parenthesis. if (!isInsideBrackets) { continue; } //Just gonna force parenthesis for now. cleanedInputString += ')'; lastnumberStartingIndex = -1; //reset //Clear the current working word. isInsideBrackets = false; continue; } //IF the current character is not a number and the previous character is a white space character //then capitalize the current character and add it to the string. List abbreviations; List words; if (!char.IsNumber(adItemText[currentCharacter]) && isPreviousCharWhiteSpace) { //Check to see the length of the string and determine if the word with the number needs to be capitalized. if (char.IsNumber(cleanedInputString[cleanedInputString.Length - 2])) { BuildAbbreviationsAndWordsLists(adItemText, currentCharacter, out abbreviations, out words); //Remove the last space if there are any abbreviations or words found. if (abbreviations.Count > 0 || words.Count > 0) { cleanedInputString = cleanedInputString.Remove(cleanedInputString.Length - 1, 1); } //Begin checking to see how to place these items back into the final string. if (abbreviations.Count >= 1 && words.Count == 0) { if (!isInsideBrackets) { cleanedInputString += abbreviations[0] + ")"; if (lastnumberStartingIndex != -1) { cleanedInputString = cleanedInputString.Insert(lastnumberStartingIndex, "("); } } else { cleanedInputString += abbreviations[0] + ")"; } break; } if (abbreviations.Count >= 1 && words.Count == 1) { cleanedInputString += abbreviations[0] + " " + words[0]; //Clear all braces since this format doesn't allow for braces in this set up. cleanedInputString = cleanedInputString.Replace("(", ""); break; } if (abbreviations.Count >= 0 && words.Count >= 1) { if (abbreviations.Count != 0) { cleanedInputString += abbreviations[0]; } cleanedInputString = words.Aggregate(cleanedInputString, (current, word) => current + (" " + word)); //Clear all braces since this format doesn't allow for braces in this set up. cleanedInputString = cleanedInputString.Replace("(", ""); break; } } //If all else fails simply assume this is the beginning of a new word and capitalize it. cleanedInputString += char.ToUpperInvariant(adItemText[currentCharacter]); } //However, if the current character is not a number but the previous character is NOT a white space character //then run a few checks before adding it to the string. else if (!char.IsNumber(adItemText[currentCharacter]) && char.IsLetter(adItemText[currentCharacter])) { //IF the previous character is a number... if (char.IsNumber(cleanedInputString[cleanedInputString.Length - 1])) { //Check to see the length of the string and determine if the word with the number needs to be capitalized. BuildAbbreviationsAndWordsLists(adItemText, currentCharacter, out abbreviations, out words); //Check to see if braces are necessary for the format we're going for. if (abbreviations.Count >= 1 && words.Count == 0) { if (!isInsideBrackets) { cleanedInputString += abbreviations[0] + ")"; if (lastnumberStartingIndex != -1) { cleanedInputString = cleanedInputString.Insert(lastnumberStartingIndex, "("); } } else { cleanedInputString += abbreviations[0] + ")"; } break; } if (abbreviations.Count >= 1 && words.Count == 1) { cleanedInputString += abbreviations[0] + " " + words[0]; //Clear all braces since this format doesn't allow for braces in this set up. cleanedInputString = cleanedInputString.Replace("(", ""); break; } if (abbreviations.Count >= 0 && words.Count >= 1) { if (abbreviations.Count != 0) { cleanedInputString += abbreviations[0]; } cleanedInputString = words.Aggregate(cleanedInputString, (current, word) => current + (" " + word)); //Clear all braces since this format doesn't allow for braces in this set up. cleanedInputString = cleanedInputString.Replace("(", ""); break; } } else if (char.IsLetter(adItemText[currentCharacter - 1]) || adItemText[currentCharacter - 1] == '\'') { cleanedInputString += char.ToLowerInvariant(adItemText[currentCharacter]); } } //IF a number is found, and we're not inside brackets, check to see if there is an opening parenthesis and if there aren't create one. if (char.IsNumber(adItemText[currentCharacter])) { cleanedInputString += adItemText[currentCharacter]; if (lastnumberStartingIndex == -1) { lastnumberStartingIndex = cleanedInputString.Length - 1; //Non-index based system, minus one for the index } } //Check for any allowed punctuation. if (adItemText[currentCharacter] == '\'') { cleanedInputString += adItemText[currentCharacter]; } //Check if the previous character is an "'". else if (cleanedInputString[cleanedInputString.Length - 1] == '\'') { cleanedInputString += adItemText[currentCharacter]; } //Since whitespace booleans are handled above, set the boolean for white spaces false. //IF we've made it this far that means the current character is not a white space character. isPreviousCharWhiteSpace = false; //IF we're at the end of the string and we're inside brackets then balance out the open bracket. if ((currentCharacter + 1) == adItemText.Length && isInsideBrackets) { cleanedInputString += ')'; } //removedCharacterOffset++; } //#if DEBUG // if (abbreviations.Count > 0) // { // LogConsole.WriteToLog(FrmLogConsole.Level.Debug, "Detected abbreviation(s) in input string \"" + adItemText + "\":"); // foreach (var abbreviation in abbreviations) // { // LogConsole.WriteToLog(FrmLogConsole.Level.Debug, abbreviation); // } // } // else if (words.Count > 0) // { // LogConsole.WriteToLog(FrmLogConsole.Level.Debug, "Detected words(s) in input string \"" + adItemText + "\":"); // foreach (var word in words) // { // LogConsole.WriteToLog(FrmLogConsole.Level.Debug, word); // } // } //#endif return cleanedInputString; } /// /// Infrastructure for the TextFormat class, not to be used with external code. /// Parses the full ad item text, starting at the specified index, for abbreviations and words. /// Once either of these objects have been found they are added to their respective Lists /// and returned as outed variables to the calling code. Preserving acronyms is off by default /// but if turned on keeps the cases of abbreviations as they are. /// /// The full text to be parsed. /// Where to start looping through the characters in the text. /// A list of abbreviations that were found in the text. /// A list of words found in the text. /// Whether or not to force normal casing rules on abbreviations. private static void BuildAbbreviationsAndWordsLists(string adItemText, int startingIndex, out List abbreviations, out List words, bool preserveAcronyms = false) { abbreviations = new List(); words = new List(); var currentWorkingString = ""; //Starting at the next character, spin through and find all words or abbreviations that are separated by white space. for (var i = startingIndex; i < adItemText.Length; i++) { //If the current character is a letter and if so add it to the current working string. if (char.IsLetter(adItemText[i])) { currentWorkingString += adItemText[i]; } //Check for the current character being a white space, showing the end of a word or abbreviation. else if (char.IsWhiteSpace(adItemText[i])) { //Block against null values from messing things up. if (string.IsNullOrEmpty(currentWorkingString)) continue; //The end of what ever word we were on has been reached, so check to see what the string is. if (IsWord(currentWorkingString)) { words.Add(currentWorkingString); } else { abbreviations.Add(currentWorkingString); } //Clear the current working string to start on the next. currentWorkingString = ""; } //Run a check to see if this is the last character in the string. if ((i + 1) != adItemText.Length) continue; //The end of whatever word we were on has been reached, so check to see what the string is. if (IsWord(currentWorkingString)) { words.Add(currentWorkingString); } else { abbreviations.Add(currentWorkingString); } } //Clean the cases of the abbreviations and words. for (var i = 0; i < abbreviations.Count; i++) { //If preserve acronyms is set to true then just leave the cases of the abbreviations alone. if (!preserveAcronyms) { abbreviations[i] = abbreviations[i].ToLowerInvariant(); } } for (var i = 0; i < words.Count; i++) { words[i] = words[i].ToLowerInvariant(); words[i] = CapitalizeFirstLetter(words[i]); } } /// /// Determines whether or not a string is an abbreviation or a word. /// A word is defined as being at least three (3) characters long and having /// at least one (1) vowel. Where as an abbreviation is defined as less then or equal to /// three (3) characters long but has at least one (1) character, whether or not /// the string that is two (2) or one (1) characters long has a vowel is meaningless /// or being exactly three characters long but having zero (0) vowels. /// /// The text to determine whether or not its a word. /// A boolean indicating whether or not the text is a word. public static bool IsWord(string text) { if (string.IsNullOrEmpty(text)) return false; var isWord = true; //Most abbreviations do not have vowels in them so check to see if the "abbreviation" //isn't just a short word like "Box", as opposed to "lbs". char[] vowels = { 'a', 'e', 'i', 'o', 'u', 'y' }; //Count the number of vowels the word has. var vowelCount = text.Count(x => vowels.Contains(x)); //IF the string is exactly three (3) characters long and has more then zero (0) vowels then it is considered a word. if (text.Length == 3 && vowelCount == 0) { isWord = false; } else if (text.Length < 3) { isWord = false; } //Return the verdict. return isWord; } public static bool TryParseBinCount(string inputText, out double binCount, out string binCountString) { if (inputText.Length == 0) { binCountString = string.Empty; binCount = 0; return false; } var success = false; binCountString = string.Empty; var number = new StringBuilder(); var binString = new StringBuilder(); // var characterReached = false; for (var i = 0; i < inputText.Length; i++) { if (char.IsNumber(inputText[i]) && !characterReached) { number.Append(inputText[i]); continue; } if (inputText[i] == '-' || inputText[i] == '.') { number.Append(inputText[i]); continue; } if (!char.IsLetter(inputText[i])) continue; binString.Append(inputText[i]); characterReached = true; } if (number.ToString() == string.Empty) { binCount = 0; return false; } if (double.TryParse(number.ToString(), out binCount)) { if (binString.ToString().Equals("bin", StringComparison.InvariantCultureIgnoreCase) || binString.ToString().Equals("bins", StringComparison.InvariantCultureIgnoreCase)) { if (binCount < 1 || binCount > 1) { binCountString = binCount + " Bins"; } else { binCountString = "1 Bin"; } success = true; } } return success; } /// /// Capitalizes the first letter of the text sent to this method. /// /// The text to be capitalized. /// The input text that has the first letter capitalized. public static string CapitalizeFirstLetter(string text) { if (string.IsNullOrEmpty(text)) return string.Empty; return text.First().ToString().ToUpperInvariant() + string.Join("", text.Skip(1)); } /// /// Add spaces between all words that have capital letters. /// /// The column header text to be made presentable. /// Whether or not to do anything with text that's in all caps. /// The text with spaces. public static string AddSpacesToSentence(string text, bool preserveAcronyms) { //http://stackoverflow.com/questions/272633/add-spaces-before-capital-letters if (string.IsNullOrWhiteSpace(text)) return string.Empty; var newText = new StringBuilder(text.Length * 2); newText.Append(text[0]); for (var i = 1; i < text.Length; i++) { if (char.IsUpper(text[i])) if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) || (preserveAcronyms && char.IsUpper(text[i - 1]) && i < text.Length - 1 && !char.IsUpper(text[i + 1]))) newText.Append(' '); newText.Append(text[i]); } return newText.ToString(); } } }