A variation on the title-case theme, this time written in JavaScript, and using lists of:
- four word,
- three word,
- two word, and
- single word prepositions
drawn from https://en.wikipedia.org/wiki/List_of_English_prepositions
(as well as lists of basic articles and conjunctions)
Title case (JS) with an editable lexicon of prepositions.kmmacros (30.6 KB)
These word lists can give slightly different results from:
- the
[Gruber title case script]
(https://daringfireball.net/2008/05/title_case) which I believe is used in the KM Filter (title case) action, and also from - @mrpasini's macro at: Improved Title Case Macro - Macro Library - Keyboard Maestro Discourse
Mileage and applications vary, and the source code and word lists are there to be edited : -)
JS Source
(() => {
"use strict";
// Rob Trew @2021
//
// titleCase ver 0.01
//
// A custom title-case function using word lists from:
// https://en.wikipedia.org/wiki/List_of_English_prepositions
//
// and modelled on:
// [Convert English Words to Title Case – titlecase](
// https://hackage.haskell.org/package/titlecase
// )
// main :: IO ()
const main = () =>
titleCase(
Application("Keyboard Maestro Engine")
.getvariable("rawTitle")
);
// ------------------- TITLE CASE --------------------
// titleCase :: String -> String
const titleCase = phrase => {
// go :: Int -> [String] -> String
const go = i =>
xs => {
const lng = xs.length;
return 0 < lng ? (
3 < lng ? (() => {
const [a, b, c, d] = xs.slice(0, 4);
return parse4(go)(i)(a)(b)(c)(d)(
xs.slice(4)
);
})() : 2 < lng ? (() => {
const [a, b, c] = xs.slice(0, 3);
return parse3(go)(i)(a)(b)(c)(
xs.slice(3)
);
})() : 1 < lng ? (() => {
const [a, b] = xs.slice(0, 2);
return parse2(go)(i)(a)(b)(
xs.slice(2)
);
})() : parse1(go)(i)(xs[0])(
xs.slice(1)
)
) : "";
};
return go(1)(
words(phrase)
).trim();
};
// parse4 :: (Int -> [String] -> String) ->
// Int -> String -> String -> String -> String
// [String] -> String
const parse4 = f => i =>
a => b => c => d => residue => {
const go = s =>
appendWords(s)(
f(1 + i)(residue)
);
return isFourWordPreposition(a)(b)(c)(d) ? (
(1 === i) ? (
0 === residue.length ? (
unwords([
toTitle(a), b, c, toTitle(d)
])
) : go(
unwords([toTitle(a), b, c, d])
)
) : 0 === residue.length ? (
unwords([a, b, c, toTitle(d)])
) : go(
unwords([a, b, c, d])
)
) : parse3(f)(i)(a)(b)(c)(
[d].concat(residue)
);
};
// parse3 :: (Int -> [String] -> String) ->
// Int -> String -> String -> String
// [String] -> String
const parse3 = f => i =>
a => b => c => residue => {
const go = s =>
appendWords(s)(
f(1 + i)(residue)
);
return isThreeWordPreposition(a)(b)(c) ? (
(1 === i) ? (
0 === residue.length ? (
unwords([
toTitle(a), b, toTitle(c)
])
) : go(
unwords([toTitle(a), b, c])
)
) : 0 === residue.length ? (
unwords([a, b, toTitle(c)])
) : go(
unwords([a, b, c])
)
) : parse2(f)(i)(a)(b)(
[c].concat(residue)
);
};
// parse2 :: (Int -> [String] -> String) ->
// Int -> String -> String
// [String] -> String
const parse2 = f => i => a => b => residue => {
const go = s =>
appendWords(s)(
f(1 + i)(residue)
);
return isTwoWordPreposition(a)(b) ? (
(1 === i) ? (
0 === residue.length ? (
unwords([toTitle(a), toTitle(b)])
) : go(
unwords([toTitle(a), b])
)
) : 0 === residue.length ? (
unwords([a, toTitle(b)])
) : go(
unwords([a, b])
)
) : parse1(f)(i)(a)(
[b].concat(residue)
);
};
// parse1 :: (Int -> [String] -> String) ->
// Int -> String -> [String] -> String
const parse1 = f => i => a => residue => {
const
lng = residue.length,
iLast = lng - 1;
return appendWords(
[
isOneWordPreposition,
isConjunction,
isArticle
]
.some(p => p(a)) ? (
(1 === i || iLast === i) ? (
toTitle(a)
) : a
) : toTitle(a)
)(
f(1 + i)(residue)
);
};
// appendWords :: String -> String -> String
const appendWords = a =>
b => `${a} ${b}`;
// ------------------- PREDICATES --------------------
// isFourWordPreposition :: String -> String ->
// String -> String -> Bool
const isFourWordPreposition = a =>
b => c => d => {
const k = toLower(unwords([a, b, c, d]));
return fourWordPrepositions.some(
ws => k === unwords(ws)
);
};
// isThreeWordPreposition :: String ->
// String -> String -> Bool
const isThreeWordPreposition = a =>
b => c => {
const k = toLower(unwords([a, b, c]));
return threeWordPrepositions.some(
ws => k === unwords(ws)
);
};
// isTwoWordPreposition :: String -> String -> Bool
const isTwoWordPreposition = a =>
b => {
const k = toLower(unwords([a, b]));
return twoWordPrepositions.some(
ws => k === unwords(ws)
);
};
// isOneWordPreposition :: String -> Bool
const isOneWordPreposition = a =>
oneWordPrepositions.includes(
toLower(a)
);
// isArticle :: String -> Bool
const isArticle = s =>
isElem(articles)(s);
// isConjunction :: String -> Bool
const isConjunction = s =>
isElem(conjunctions)(s);
// isElem :: [String] -> String -> Bool
const isElem = xs =>
s => xs.includes(toLower(s));
// --------------------- LEXICON ---------------------
// articles :: [String]
const articles = ["a", "an", "the"];
// conjunctions :: [String]
const conjunctions = [
"for", "and", "nor", "but", "or", "yet", "so"
];
// oneWordPrepositions :: [String]
const oneWordPrepositions = [
"a", "abaft", "abeam", "aboard", "about", "above",
"absent", "across", "afore", "against", "along",
"alongside", "amid", "amidst", "among", "amongst",
"an", "anenst", "apropos", "apud", "around",
"aside", "astride", "at", "athwart", "atop",
"barring", "behind", "below", "beneath", "beside",
"besides", "between", "beyond", "but", "by",
"chez", "circa", "concerning", "considering",
"despite", "down", "during", "except", "excluding",
"failing", "following", "for", "forenenst", "from",
"given", "in", "including", "inside", "into",
"like", "mid", "midst", "minus", "modulo", "near",
"next", "notwithstanding", "of", "off", "on",
"onto", "opposite", "out", "outside", "over",
"pace", "past", "per", "plus", "pro", "qua",
"regarding", "round", "sans", "save", "through",
"throughout", "till", "times", "to", "toward",
"towards", "under", "underneath", "unlike",
"unto", "up", "upon", "versus", "vs.", "vs",
"v.", "via", "vice", "vis-à-vis", "w/",
"within", "w/in", "w/i", "without", "w/o", "worth"
];
// twoWordPrepositions :: [[String]]
const twoWordPrepositions = [
["according", "to"],
["ahead", "of"],
["apart", "from"],
["as", "for"],
["as", "of"],
["as", "per"],
["as", "regards"],
["aside", "from"],
["astern", "of"],
["back", "to"],
["close", "to"],
["due", "to"],
["except", "for"],
["far", "from"],
["in", "to"],
["inside", "of"],
["instead", "of"],
["left", "of"],
["near", "to"],
["next", "to"],
["on", "to"],
["opposite", "of"],
["opposite", "to"],
["out", "from"],
["out", "of"],
["outside", "of"],
["owing", "to"],
["prior", "to"],
["pursuant", "to"],
["rather", "than"],
["regardless", "of"],
["right", "of"],
["subsequent", "to"],
["such", "as"],
["thanks", "to"],
["that", "of"],
["up", "to"]
];
// threeWordPrepositions :: [[String]]
const threeWordPrepositions = [
["as", "opposed", "to"],
["as", "well", "as"],
["by", "means", "of"],
["by", "virtue", "of"],
["in", "accordance", "with"],
["in", "addition", "to"],
["in", "case", "of"],
["in", "front", "of"],
["in", "lieu", "of"],
["in", "order", "to"],
["in", "place", "of"],
["in", "point", "of"],
["in", "spite", "of"],
["on", "account", "of"],
["on", "behalf", "of"],
["on", "top", "of"],
["with", "regard", "to"],
["with", "respect", "to"]
];
// fourWordPrepositions :: [[String]]
const fourWordPrepositions = [
["at", "the", "behest", "of"],
["for", "the", "sake", "of"],
["with", "a", "view", "to"]
];
// ------------------- GENERIC -------------------
// toLower :: String -> String
const toLower = s =>
// Lower-case version of string.
s.toLocaleLowerCase();
// toUpper :: String -> String
const toUpper = s =>
s.toLocaleUpperCase();
// toTitle :: String -> String
const toTitle = s =>
0 < s.length ? (
`${toUpper(s[0])}${s.slice(1)}`
) : "";
// words :: String -> [String]
const words = s =>
// List of space-delimited sub-strings.
s.split(/\s+/u);
// unwords :: [String] -> String
const unwords = xs =>
// A space-separated string derived
// from a list of words.
xs.join(" ");
// MAIN ---
return main();
})();