TitleFormatter.java

package net.zer0bandwidth.android.lib.text.format;


import android.content.Context;

import net.zer0bandwidth.android.lib.R;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * Formats a string as a title. The class currently supports only an English
 * locale, but other locale supports may be added in the future.
 * @since zer0bandwidth-net/android 0.1.5 (#11)
 */
public class TitleFormatter
{
	/**
	 * Formats a string as a title.
	 * @param ctx a context, so we can fetch localized string resources
	 * @param sInput the input string
	 * @return a new string, formatted as a title
	 */
	public static String format( Context ctx, String sInput )
	{ return format( ctx, sInput, Locale.getDefault() ) ; }

	/**
	 * Formats a string as a title, using the specified locale (eventually...).
	 * @param ctx a context, so we can fetch localized string resources
	 * @param sInput the input string
	 * @param loc the locale to be considered; this is currently ignored, but
	 *            may be used in a future version
	 * @return a new string, formatted as a title
	 */
	@SuppressWarnings("unused") // param "loc" is intended for future support
	public static String format( Context ctx, String sInput, Locale loc )
	{
		if( sInput == null ) return null ; // trivially
		if( sInput.length() == 0 ) return "" ; // trivially

		return formatEnglishTitle( ctx, sInput ) ;
	}

/// English locales ////////////////////////////////////////////////////////////

	/**
	 * A data structure which self-initializes with all the data related to
	 * evaluations in an English-speaking context.
	 * @since zer0bandwidth-net/android 0.1.5 (#11)
	 * @see TitleFormatter#formatEnglishTitle
	 */
	protected static class EnglishLocaleContext
	{
		public Context ctx ;
		public List<String> asArticles ;
		public List<String> asPrepositions ;
		public EnglishLocaleContext( Context ctx )
		{
			this.ctx = ctx ;
			asArticles = Arrays.asList( ctx.getResources()
					.getStringArray( R.array.asEnglishArticles ) ) ;
			asPrepositions = Arrays.asList( ctx.getResources()
					.getStringArray( R.array.asEnglishPrepositions ) ) ;
		}
	}

	/**
	 * Formats a string as a title, assuming an English-speaking locale.
	 *
	 * <p>The criteria used are as follows:</p>
	 *
	 * <ul>
	 *     <li>Capitalize the first and last word.</li>
	 *     <li>Capitalize any word that is not an article, conjunction, or
	 *         preposition shorter than five letters.</li>
	 * </ul>
	 *
	 * <p>This most closely matches the AP Stylebook, as opposed to the Chicago
	 * Manual of Style or the MLA guidelines.</p>
	 *
	 * @param sInput the input string
	 * @return a new string, formatted as a title
	 * @see <a href="http://grammar.yourdictionary.com/capitalization/rules-for-capitalization-in-titles.html">Your Dictionary: Rules for Capitalization in Titles of Articles</a>
	 */
	protected static String formatEnglishTitle( Context ctx, String sInput )
	{
		String[] asTokens = sInput.split( "\\s" ) ;
		StringBuilder sbOutput = new StringBuilder() ;
		EnglishLocaleContext lctx = new EnglishLocaleContext(ctx) ;
		for( int i = 0 ; i < asTokens.length ; i++ )
		{ // Use numeric iterator so we know when we're on the last token.
			String sToken = asTokens[i] ;
			if( i > 0 )
			{ // Not the first word. Append a space, then capitalize.
				sbOutput.append( " " ) ;
				sbOutput.append( resolveEnglishToken( lctx, sToken,
						( i == asTokens.length - 1 ) ) ) ;  // is the last token
			}
			else
				sbOutput.append( resolveEnglishToken( lctx, sToken, true ) ) ;
		}
		return sbOutput.toString() ;
	}

	/**
	 * Resolves capitalization of a single English token.
	 * @param lctx contextual information about English-speaking locales
	 * @param sToken the input token
	 * @param bForce specifies that capitalization should be forced
 	 * @return the resolved token
	 */
	protected static String resolveEnglishToken(
			EnglishLocaleContext lctx, String sToken, boolean bForce )
	{
		String sNormal = sToken.toLowerCase() ;
		char[] acReturn = new char[ sNormal.length() ] ;
		if( ! bForce )
		{ // See if this is one of the words that must remain lower-case.
			if( lctx.asArticles.contains(sNormal) )
				return sNormal ;
			if( sToken.length() < 5 && lctx.asPrepositions.contains(sNormal) )
				return sNormal ;
		}
		for( int i = 0 ; i < sToken.length() ; i++ )
		{
			char c = sNormal.charAt(i) ;
			if( i == 0 )
				c = Character.toUpperCase(c) ;
			else if( i == 3 && sNormal.startsWith( "o'" ) )
				c = Character.toUpperCase(c) ;
			else if( i > 1 && sNormal.charAt(i-1) == '-' )
				c = Character.toUpperCase(c) ;
			acReturn[i] = c ;
		}
		return new String(acReturn) ;
	}

/// Other locales... ///////////////////////////////////////////////////////////

	// TBD

/// Other methods //////////////////////////////////////////////////////////////

	/** This class is static-only; it should not be instantiated. */
	private TitleFormatter() {}
}