Posted By

FMalk on 01/19/11


Tagged

String


Versions (?)

TitleCase


 / Published in: JavaScript
 

URL: http://it.toolbox.com/blogs/macsploitation/titlecase-in-javascript-24824

Transforms a string to CamelCase. Slightly modified from source's version to accept all-uppercase strings. Update: added matches for some portuguese small words.

  1. /*
  2. String.prototype.titleCase 1.0
  3.  
  4. An implementation of John Gruber's TitleCase.pl in JavaScript.
  5. (http://daringfireball.net/projects/titlecase/TitleCase.pl)
  6.  
  7. TitleCase.pl Documentation:
  8.  
  9.   # This filter changes all words to Title Caps, and attempts to be clever
  10.   # about *un*capitalizing small words like a/an/the in the input.
  11.   #
  12.   # The list of "small words" which are not capped comes from
  13.   # the New York Times Manual of Style, plus 'vs' and 'v'.
  14.   #
  15.   # John Gruber
  16.   # http://daringfireball.net/
  17.   # 10 May 2008
  18.   #
  19.   # License: http://www.opensource.org/licenses/mit-license.php
  20.   #
  21.  
  22.  
  23. Usage:
  24.   js> "Sub-Phrase With a Small Word in Quotes: 'a Trick, Perhaps?'".toTitleCase()
  25.   "Sub-Phrase With a Small Word in Quotes: 'A Trick, Perhaps?'"
  26.  
  27.  
  28.  
  29. Uses Steven Levithan's Cross-Browser Split (note: this replaces your
  30. hosts's native split method).
  31.  
  32. By Jon Hohle <http://hohle.net>
  33. 21.May.2008
  34. MIT Licence <http://www.opensource.org/licenses/mit-license.php>
  35.  
  36. */
  37.  
  38.  
  39. /*
  40.   Cross-Browser Split 0.2.1
  41.   By Steven Levithan <http://stevenlevithan.com>
  42.   MIT license
  43. */
  44.  
  45. var nativeSplit = nativeSplit || String.prototype.split;
  46.  
  47. String.prototype.split = function (s /* separator */, limit) {
  48. // If separator is not a regex, use the native split method
  49. if (!(s instanceof RegExp))
  50. return nativeSplit.apply(this, arguments);
  51.  
  52. /* Behavior for limit: If it's...
  53.   - Undefined: No limit
  54.   - NaN or zero: Return an empty array
  55.   - A positive number: Use limit after dropping any decimal
  56.   - A negative number: No limit
  57.   - Other: Type-convert, then use the above rules */
  58. if (limit === undefined || +limit < 0) {
  59. limit = false;
  60. } else {
  61. limit = Math.floor(+limit);
  62. if (!limit)
  63. return [];
  64. }
  65.  
  66. var flags = (s.global ? "g" : "") + (s.ignoreCase ? "i" : "") +
  67. (s.multiline ? "m" : ""),
  68. s2 = new RegExp("^" + s.source + "$", flags),
  69. output = [],
  70. lastLastIndex = 0,
  71. i = 0,
  72. match;
  73.  
  74. if (!s.global)
  75. s = new RegExp(s.source, "g" + flags);
  76.  
  77. while ((!limit || i++ <= limit) && (match = s.exec(this))) {
  78. var zeroLengthMatch = !match[0].length;
  79.  
  80. // Fix IE's infinite-loop-resistant but incorrect lastIndex
  81. if (zeroLengthMatch && s.lastIndex > match.index)
  82. s.lastIndex = match.index; // The same as s.lastIndex--
  83.  
  84. if (s.lastIndex > lastLastIndex) {
  85. // Fix browsers whose exec methods don't consistently
  86. // return undefined for non-participating capturing groups
  87. if (match.length > 1) {
  88. match[0].replace(s2, function () {
  89. for (var j = 1; j < arguments.length - 2; j++) {
  90. if (arguments[j] === undefined)
  91. match[j] = undefined;
  92. }
  93. });
  94. }
  95.  
  96. output = output.concat(
  97. this.slice(lastLastIndex, match.index),
  98. (match.index === this.length ? [] : match.slice(1)));
  99. lastLastIndex = s.lastIndex;
  100. }
  101.  
  102. if (zeroLengthMatch)
  103. s.lastIndex++;
  104. }
  105.  
  106. return (lastLastIndex === this.length) ?
  107. (s.test("") ? output : output.concat("")) :
  108. (limit ? output : output.concat(this.slice(lastLastIndex)));
  109. };
  110.  
  111.  
  112.  
  113. // give a hoot, don't pollute
  114. var __TitleCase = {
  115. __smallWords: ['a', 'an', 'and', 'as', 'at', 'but',
  116. 'by', 'en', 'for', 'if', 'in', 'of', 'on', 'or',
  117. 'the', 'to', 'v[.]?', 'via', 'vs[.]?',
  118. 'e', 'o[s]?', 'na[s]?', 'no[s]?', 'em', 'de', 'do[s]?', 'da[s]?'
  119. ],
  120.  
  121. init: function() {
  122. this.__smallRE = this.__smallWords.join('|');
  123. this.__lowerCaseWordsRE = new RegExp(
  124. '\\b(' + this.__smallRE + ')\\b', 'gi');
  125. this.__firstWordRE = new RegExp(
  126. '^([^a-zA-Z0-9 \\r\\n\\t]*)(' + this.__smallRE + ')\\b', 'gi');
  127. this.__lastWordRE = new RegExp(
  128. '\\b(' + this.__smallRE + ')([^a-zA-Z0-9 \\r\\n\\t]*)$', 'gi');
  129. },
  130.  
  131. toTitleCase: function(string) {
  132. var line = '';
  133.  
  134. var split = string.split(/([:.;?!][ ]|(?:[ ]|^)["“])/);
  135.  
  136. for (var i = 0; i < split.length; ++i) {
  137. var s = split[i];
  138.  
  139. s = s.replace(
  140. /\b([a-zA-Z][a-z.'’]*)\b/g,
  141. this.__titleCaseDottedWordReplacer);
  142.  
  143. // lowercase the list of small words
  144. s = s.replace(this.__lowerCaseWordsRE, this.__lowerReplacer);
  145.  
  146. // if the first word in the title is a small word then capitalize it
  147. s = s.replace(this.__firstWordRE, this.__firstToUpperCase);
  148.  
  149. // if the last word in the title is a small word, then capitalize it
  150. s = s.replace(this.__lastWordRE, this.__firstToUpperCase);
  151.  
  152. line += s;
  153. }
  154.  
  155. // special cases
  156. line = line.replace(/ V(s?)\. /g, ' v$1. ');
  157. line = line.replace(/(['’])S\b/g, '$1s');
  158. line = line.replace(/\b(AT&T|Q&A)\b/ig, this.__upperReplacer);
  159.  
  160. return line;
  161. },
  162.  
  163.  
  164. __titleCaseDottedWordReplacer: function (w) {
  165. return (w.match(/[a-zA-Z][.][a-zA-Z]/)) ? w : __TitleCase.__firstToUpperCase(w);
  166. },
  167.  
  168. __lowerReplacer: function (w) { return w.toLowerCase() },
  169.  
  170. __upperReplacer: function (w) { return w.toUpperCase() },
  171.  
  172. __firstToUpperCase: function (w) {
  173. var split = w.split(/(^[^a-zA-Z0-9]*[a-zA-Z0-9])(.*)$/);
  174. split[1] = split[1].toUpperCase();
  175. return split.join('');
  176. },
  177.  
  178. test: function() {
  179. var testStrings = [
  180. "Q&A With Steve Jobs: 'That's What Happens In Technology'",
  181. "What Is AT&T's Problem?",
  182. "Apple Deal With AT&T Falls Through",
  183. "this v that",
  184. "this vs that",
  185. "this v. that",
  186. "this vs. that",
  187. "The SEC's Apple Probe: What You Need to Know",
  188. "'by the Way, small word at the start but within quotes.'",
  189. "Small word at end is nothing to be afraid of",
  190. "Starting Sub-Phrase With a Small Word: a Trick, Perhaps?",
  191. "Sub-Phrase With a Small Word in Quotes: 'a Trick, Perhaps?'",
  192. 'Sub-Phrase With a Small Word in Quotes: "a Trick, Perhaps?"',
  193. '"Nothing to Be Afraid of?"',
  194. '"Nothing to Be Afraid Of?"',
  195. 'a thing'];
  196.  
  197.  
  198. var validStrings = [
  199. "Q&A With Steve Jobs: 'That's What Happens in Technology'",
  200. "What Is AT&T's Problem?",
  201. "Apple Deal With AT&T Falls Through",
  202. "This v That",
  203. "This vs That",
  204. "This v. That",
  205. "This vs. That",
  206. "The SEC's Apple Probe: What You Need to Know",
  207. "'By the Way, Small Word at the Start but Within Quotes.'",
  208. "Small Word at End Is Nothing to Be Afraid Of",
  209. "Starting Sub-Phrase With a Small Word: A Trick, Perhaps?",
  210. "Sub-Phrase With a Small Word in Quotes: 'A Trick, Perhaps?'",
  211. 'Sub-Phrase With a Small Word in Quotes: "A Trick, Perhaps?"',
  212. '"Nothing to Be Afraid Of?"',
  213. '"Nothing to Be Afraid Of?"',
  214. 'A Thing'];
  215.  
  216. for (var i = 0; i < testStrings.length ; ++i)
  217. {
  218. var s = testStrings[i].toTitleCase();
  219. if (s != validStrings[i])
  220. {
  221. alert(s + '\ndoes not match\n' + validStrings[i]);
  222. return false;
  223. break;
  224. }
  225. }
  226. return true;
  227. }
  228. };
  229.  
  230. __TitleCase.init();
  231.  
  232. function toTitleCase(string) { return __TitleCase.toTitleCase(string); }
  233.  
  234. String.prototype.toTitleCase = function() { return toTitleCase(this.toLowerCase()); }

Report this snippet  

You need to login to post a comment.