Processing Ajax...

Title
Close Dialog

Message

Confirm
Close Dialog

Confirm
Close Dialog

Confirm
Close Dialog

Convert to Title Case (Smart Extended)

Description
This macro will convert the selected text to title case, and has a hard coded list of exceptions as well.
Language
C#.net
Minimum Version
Created By
Thomas Malloch (BFS)
Contributors
-
Date Created
Mar 10, 2020
Date Last Modified
Mar 10, 2020

Macro Code

using System;
using System.Text;
using System.Collections.Generic;

public static class ClipboardFusionHelper
{
	private static readonly string[] ExceptionsArray =
	{
		//                                                              Articles                                                   //
		"a",
		"an",
		"the",

		//                                                              Coordinating conjunctions		                           //	
		"and",
		"but",
		"or",
		"nor",
		"for",
		"yet",
		"so",

		//                                                              Postpositions                                              //
		"ago",
		"apart",
		"aside",
		"aslant", //(archaic)
		"away",
		"hence",
		"notwithstanding", //(also prepositional)
		"on",
		"short", //(also prepositional)
		"through",
		"withal",

		//                                                             Single Word Prepositions                                   //
		"aboard",
		"about",
		"above",
		"absent", //(law)
		"across",
		"cross", //(archaic)
		"after",

		"against",

		// (poetic or archaic) 
		"'gainst",
		"gainst",

		// (archaic)
		"again",
		"gain",
		"along",
		"'long", //(abbreviation), 
		"alongst", //(archaic)
		"alongside",
		"amid",

		//(poetic or archaic)
		"amidst",
		"mid",
		"midst",
		"among",
		"amongst", //(in US English poetic or archaic)
		//(abbreviations)
		"'mong",
		"mong",
		"'mongst",
		"apropos", //(rare for apropos of)
		"apud", //(formal)
		"around",

		//(abbreviations)
		"'round",
		"round",
		"as",
		"astride",
		"at",
		"@", //(abbreviation)
		"atop",
		"ontop",
		"bar",
		"before",

		//(dialectal or archaic)
		"afore",
		"tofore",
		"behind",
		"ahind", //(dialectal or archaic)
		"below",
		//(Scotland)
		"ablow",
		"allow",
		"beneath",
		"'neath", //(poetic)
		"neath", //(poetic)
		"beside",
		"besides",
		"between",
		"atween", //(dialectal or archaic)
		"beyond",
		"ayond", //(dialectal or archaic)
		"but",
		"by",
		"chez", //(rare)

		"circa",

		//(abbreviations)
		"c. ",
		"ca.",
		"come",
		"dehors", //(law)
		"despite",
		"spite", //(abbreviation)
		"down",
		"during",
		"except",
		"for",
		"from",
		"in",
		"inside",
		"into",
		"less",
		"like",
		"minus",
		"near",
		"nearer", //(comparative), 
		"nearest", //(superlative)
		"anear", //(archaic)
		"notwithstanding", //(also postpositional)
		"of",
		"o'", //(poetic or eye-dialect)
		"off",
		"on",
		"onto",
		"opposite",
		"out",
		"outen", //(archaic or dialectal)
		"outside",
		"over",
		"o'er", //(poetic)
		"pace", //(formal)
		"past",
		"per",
		"plus",
		"post", //(often hyphenated)
		"pre", //(often hyphenated)
		"pro", //(often hyphenated)
		"qua", //(formal)
		"re", //(often used with colon)
		"sans", //(formal)
		"save",
		"sauf", //(archaic)
		"short",
		"since",
		"sithence", //(archaic)
		"than",
		"through",
		"thru", //(abbreviation)
		"throughout",
		"thruout", //(abbreviation)
		"till",
		"to",
		"toward",
		"towards",
		"under",
		"underneath",
		"unlike",
		"until",

		//(abbreviations)
		"'til",
		"til",
		"unto",
		"up",
		"upon",

		//(abbreviations)
		"'pon",
		"pon",
		"upside",
		"versus",

		//(abbreviations)
		"vs.",
		"v.",
		"via",
		"vice", //(formal)
		"vis-à-vis", //(formal)
		"with",

		//(abbreviations)
		"w/",
		"wi'",
		"c?",
		"within",
		"w/i", //(abbreviation)
		"without",
		"w/o", //(abbreviation)
		"worth",
	};

	private static string[] MultiWordExceptionArray = 
	{
		//                                                               Two-word Prepositions                                          //
		"according to",
		"across from",
		"adjacent to",
		"ahead of",
		"along with",
		"apart from",
		"as for",
		"as of",
		"as per",
		"as regards",
		"aside from",
		"back to",
		"because of",
		"close to",
		"counter to",
		"down on",
		"due to",
		"except for",
		"far from",
		"inside of",
		"instead of",
		"left of",
		"near to",
		"next to",
		"opposite of",
		"opposite to",
		"other than",
		"out from",
		"out of",
		"outside of",
		"owing to",
		"prior to",
		"pursuant to",
		"rather than",
		"regardless of",
		"right of",
		"subsequent to",
		"such as",
		"thanks to",
		"up to",

		//                                                                      Three-word Prepositions                             //       
		"as far as", //is one example of the many expressions which can be analyzed as as+adjective+as rather than a multiword preposition
		"as opposed to",
		"as soon as",
		"as well as",

		//                                                                       Preposition + (article) + noun + preposition               //
		//English has many idiomatic expressions that act as prepositions that can be analyzed as a preposition followed by a noun (sometimes preceded by the definite or, occasionally, indefinite article) followed by another preposition.
		"at the behest of",
		"by means of",
		"by virtue of",
		"for the sake of",
		"for lack of",
		"for want of",
		"in accordance with",
		"in addition to",
		"in case of",
		"in front of",
		"in lieu of",
		"in place of",
		"in point of",
		"in spite of",
		"on account of",
		"on behalf of",
		"on top of",
		"with regard to", //(sometimes written as "w/r/t")
		"with respect to",
		"with a view to",
	};

	private static readonly string[] CaseSensitiveExceptionArray = 
	{
		//                                                             OTHERS                                                   //
		"SaaS",
		"PaaS",
		"B2B",
		"AI",
	};

	private static readonly HashSet<string> Exceptions = new HashSet<string>(ExceptionsArray);
	private static Dictionary<string, string> MultiWordExceptions;
	private static Dictionary<string, string> CaseSensitiveExceptions;

	public static string ProcessText(string text)
	{
		BFS.Clipboard.PasteText(ToTitleCaseSmart(BFS.Clipboard.CopyText()));
        return null;
	}

	private static string ToTitleCaseSmart(string text)
	{
		// build multi-word exception dictionary
		MultiWordExceptions = new Dictionary<string, string>();
		foreach (string exception in MultiWordExceptionArray)
		{
			string key = "_" + exception.GetHashCode().ToString() + "_";
			if (!MultiWordExceptions.ContainsKey(key))
				MultiWordExceptions.Add(key, exception);

			// replace exceptions with something we can easily look for and replace later
			while (true)
			{
				int index = text.IndexOf(exception, StringComparison.OrdinalIgnoreCase);
				if (index == -1)
					break;

				string modified = text.Substring(0, index);
				modified += key;
				modified += text.Substring(index + exception.Length);
				text = modified;
			}
		}

		// build case sensitive exceptions
		CaseSensitiveExceptions = new Dictionary<string, string>();
		foreach (string exception in CaseSensitiveExceptionArray)
		{
			string key = "_" + exception.GetHashCode().ToString() + "_";
			if (!CaseSensitiveExceptions.ContainsKey(key))
				CaseSensitiveExceptions.Add(key, exception);

			// replace exceptions with something we can easily look for and replace later
			text = text.Replace(exception, key);
		}

		// do the normal title case processing
		Queue<string> words = new Queue<string>(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(text.ToLowerInvariant()).Split(new char[] {' '}));
		StringBuilder builder = new StringBuilder(words.Dequeue());
		while (words.Count > 0)
		{
			string word = words.Dequeue();
			string lower = word.ToLower();
			builder.Append(" ");
			if (Exceptions.Contains(lower))
				builder.Append(lower);
			else
				builder.Append(word);
		}

		// go through and replace our other exceptions with the proper values
		text = builder.ToString();
		foreach (string key in MultiWordExceptions.Keys)
			text = text.Replace(key, MultiWordExceptions[key]);
		foreach (string key in CaseSensitiveExceptions.Keys)
			text = text.Replace(key, CaseSensitiveExceptions[key]);

		// return the result
		return text;
	}
}