Files

438 lines
22 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DataTableParsingEngine
{
/// <summary>
/// Contains methods that assist with managing ad item names.
/// </summary>
public static class TextFormat
{
//private static readonly FrmLogConsole LogConsole = FrmLogConsole.GetStaticInstance;
/// <summary>
/// Formats an ad item's name / text into a standard format so as to help reduce redundancy in the database.
/// </summary>
/// <param name="adItemText">The ad item's name to be formatted.</param>
/// <param name="preserveAcronyms">Whether or not to preserve case on abbreviations.</param>
/// <returns>A cleaned up and formatted version of the ad item text.</returns>
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<string> abbreviations;
List<string> 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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="adItemText">The full text to be parsed.</param>
/// <param name="startingIndex">Where to start looping through the characters in the text.</param>
/// <param name="abbreviations">A list of abbreviations that were found in the text.</param>
/// <param name="words">A list of words found in the text.</param>
/// <param name="preserveAcronyms">Whether or not to force normal casing rules on abbreviations.</param>
private static void BuildAbbreviationsAndWordsLists(string adItemText, int startingIndex, out List<string> abbreviations, out List<string> words, bool preserveAcronyms = false)
{
abbreviations = new List<string>();
words = new List<string>();
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]);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="text">The text to determine whether or not its a word.</param>
/// <returns>A boolean indicating whether or not the text is a word.</returns>
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;
}
/// <summary>
/// Capitalizes the first letter of the text sent to this method.
/// </summary>
/// <param name="text">The text to be capitalized.</param>
/// <returns>The input text that has the first letter capitalized.</returns>
public static string CapitalizeFirstLetter(string text)
{
if (string.IsNullOrEmpty(text)) return string.Empty;
return text.First().ToString().ToUpperInvariant() + string.Join("", text.Skip(1));
}
/// <summary>
/// Add spaces between all words that have capital letters.
/// </summary>
/// <param name="text">The column header text to be made presentable.</param>
/// <param name="preserveAcronyms">Whether or not to do anything with text that's in all caps.</param>
/// <returns>The text with spaces.</returns>
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();
}
}
}