blob: 171cd58a2915ea0d1486c9bb91b13407b2f7e14b [file] [log] [blame]
/*
**********************************************************************
* Copyright (C) 2001, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Date Name Description
* 05/24/01 aliu Creation.
**********************************************************************
*/
#include "unicode/uchar.h"
#include "unicode/titletrn.h"
U_NAMESPACE_BEGIN
/**
* ID for this transliterator.
*/
const char TitlecaseTransliterator::_ID[] = "Any-Title";
TitlecaseTransliterator::TitlecaseTransliterator(UnicodeFilter* adoptedFilter) :
Transliterator(_ID, adoptedFilter) {
// Need to look back 2 characters in the case of "can't"
setMaximumContextLength(2);
}
/**
* Destructor.
*/
TitlecaseTransliterator::~TitlecaseTransliterator() {}
/**
* Copy constructor.
*/
TitlecaseTransliterator::TitlecaseTransliterator(const TitlecaseTransliterator& o) :
Transliterator(o) {}
/**
* Assignment operator.
*/
TitlecaseTransliterator& TitlecaseTransliterator::operator=(
const TitlecaseTransliterator& o) {
Transliterator::operator=(o);
return *this;
}
/**
* Transliterator API.
*/
Transliterator* TitlecaseTransliterator::clone(void) const {
return new TitlecaseTransliterator(*this);
}
/**
* Implements {@link Transliterator#handleTransliterate}.
*/
void TitlecaseTransliterator::handleTransliterate(
Replaceable& text, UTransPosition& offsets,
UBool isIncremental) const {
// NOTE: This method contains some special case code to handle
// apostrophes between alpha characters. We want to have
// "can't" => "Can't" (not "Can'T"). This may be incorrect
// for some locales, e.g., "l'arbre" => "L'Arbre" (?).
// TODO: Revisit this.
// Determine if there is a preceding letter character in the
// left context (if there is any left context).
UBool wasLastCharALetter = FALSE;
if (offsets.start > offsets.contextStart) {
UChar c = text.charAt(offsets.start - 1);
// Handle the case "Can'|t", where the | marks the context
// boundary. We only handle a single apostrophe.
if (c == 0x0027 /*'*/ && (offsets.start-2) >= offsets.contextStart) {
c = text.charAt(offsets.start - 2);
}
wasLastCharALetter = u_isalpha(c);
}
// The buffer used to batch up changes to be made
UnicodeString buffer;
int32_t bufStart = 0;
int32_t bufLimit = -1;
int32_t start;
for (start = offsets.start; start < offsets.limit; ++start) {
// For each character, if the preceding character was a
// non-letter, and this character is a letter, then apply
// the titlecase transformation. Otherwise apply the
// lowercase transformation.
UChar32 c = text.charAt(start);
if (u_isalpha(c)) {
UChar32 newChar;
if (wasLastCharALetter) {
newChar = u_tolower(c);
} else {
newChar = u_totitle(c);
}
if (c != newChar) {
// This is the simple way of doing this:
//text.replace(start, start+1,
// String.valueOf((char) newChar));
// Instead, we do something more complicated that
// minimizes the number of calls to
// Replaceable.replace(). We batch up the changes
// we want to make in a buffer, recording
// our position and dumping the buffer out when a
// non-contiguous change arrives.
if (bufLimit == start) {
++bufLimit;
// Fall through and append newChar below
} else {
if (buffer.length() > 0) {
text.handleReplaceBetween(bufStart, bufLimit, buffer);
buffer.truncate(0);
}
bufStart = start;
bufLimit = start+1;
// Fall through and append newChar below
}
buffer.append(newChar);
}
wasLastCharALetter = TRUE;
} else if (c == 0x0027 /*'*/ && wasLastCharALetter) {
// Ignore a single embedded apostrophe, so that "can't" =>
// "Can't", not "Can'T".
} else {
wasLastCharALetter = FALSE;
}
}
// assert(start == offsets.limit);
offsets.start = start;
if (buffer.length() > 0) {
text.handleReplaceBetween(bufStart, bufLimit, buffer);
}
}
U_NAMESPACE_END