michael@0: /*
michael@0: **********************************************************************
michael@0: * Copyright (C) 1999-2012, International Business Machines
michael@0: * Corporation and others. All Rights Reserved.
michael@0: **********************************************************************
michael@0: * Date Name Description
michael@0: * 11/17/99 aliu Creation.
michael@0: **********************************************************************
michael@0: */
michael@0:
michael@0: #include "utypeinfo.h" // for 'typeid' to work
michael@0:
michael@0: #include "unicode/utypes.h"
michael@0:
michael@0: #if !UCONFIG_NO_TRANSLITERATION
michael@0:
michael@0: #include "unicode/putil.h"
michael@0: #include "unicode/translit.h"
michael@0: #include "unicode/locid.h"
michael@0: #include "unicode/msgfmt.h"
michael@0: #include "unicode/rep.h"
michael@0: #include "unicode/resbund.h"
michael@0: #include "unicode/unifilt.h"
michael@0: #include "unicode/uniset.h"
michael@0: #include "unicode/uscript.h"
michael@0: #include "unicode/strenum.h"
michael@0: #include "unicode/utf16.h"
michael@0: #include "cpdtrans.h"
michael@0: #include "nultrans.h"
michael@0: #include "rbt_data.h"
michael@0: #include "rbt_pars.h"
michael@0: #include "rbt.h"
michael@0: #include "transreg.h"
michael@0: #include "name2uni.h"
michael@0: #include "nortrans.h"
michael@0: #include "remtrans.h"
michael@0: #include "titletrn.h"
michael@0: #include "tolowtrn.h"
michael@0: #include "toupptrn.h"
michael@0: #include "uni2name.h"
michael@0: #include "brktrans.h"
michael@0: #include "esctrn.h"
michael@0: #include "unesctrn.h"
michael@0: #include "tridpars.h"
michael@0: #include "anytrans.h"
michael@0: #include "util.h"
michael@0: #include "hash.h"
michael@0: #include "mutex.h"
michael@0: #include "ucln_in.h"
michael@0: #include "uassert.h"
michael@0: #include "cmemory.h"
michael@0: #include "cstring.h"
michael@0: #include "uinvchar.h"
michael@0:
michael@0: static const UChar TARGET_SEP = 0x002D; /*-*/
michael@0: static const UChar ID_DELIM = 0x003B; /*;*/
michael@0: static const UChar VARIANT_SEP = 0x002F; // '/'
michael@0:
michael@0: /**
michael@0: * Prefix for resource bundle key for the display name for a
michael@0: * transliterator. The ID is appended to this to form the key.
michael@0: * The resource bundle value should be a String.
michael@0: */
michael@0: static const char RB_DISPLAY_NAME_PREFIX[] = "%Translit%%";
michael@0:
michael@0: /**
michael@0: * Prefix for resource bundle key for the display name for a
michael@0: * transliterator SCRIPT. The ID is appended to this to form the key.
michael@0: * The resource bundle value should be a String.
michael@0: */
michael@0: static const char RB_SCRIPT_DISPLAY_NAME_PREFIX[] = "%Translit%";
michael@0:
michael@0: /**
michael@0: * Resource bundle key for display name pattern.
michael@0: * The resource bundle value should be a String forming a
michael@0: * MessageFormat pattern, e.g.:
michael@0: * "{0,choice,0#|1#{1} Transliterator|2#{1} to {2} Transliterator}".
michael@0: */
michael@0: static const char RB_DISPLAY_NAME_PATTERN[] = "TransliteratorNamePattern";
michael@0:
michael@0: /**
michael@0: * Resource bundle key for the list of RuleBasedTransliterator IDs.
michael@0: * The resource bundle value should be a String[] with each element
michael@0: * being a valid ID. The ID will be appended to RB_RULE_BASED_PREFIX
michael@0: * to obtain the class name in which the RB_RULE key will be sought.
michael@0: */
michael@0: static const char RB_RULE_BASED_IDS[] = "RuleBasedTransliteratorIDs";
michael@0:
michael@0: /**
michael@0: * The mutex controlling access to registry object.
michael@0: */
michael@0: static UMutex registryMutex = U_MUTEX_INITIALIZER;
michael@0:
michael@0: /**
michael@0: * System transliterator registry; non-null when initialized.
michael@0: */
michael@0: static icu::TransliteratorRegistry* registry = 0;
michael@0:
michael@0: // Macro to check/initialize the registry. ONLY USE WITHIN
michael@0: // MUTEX. Avoids function call when registry is initialized.
michael@0: #define HAVE_REGISTRY(status) (registry!=0 || initializeRegistry(status))
michael@0:
michael@0: U_NAMESPACE_BEGIN
michael@0:
michael@0: UOBJECT_DEFINE_ABSTRACT_RTTI_IMPLEMENTATION(Transliterator)
michael@0:
michael@0: /**
michael@0: * Return TRUE if the given UTransPosition is valid for text of
michael@0: * the given length.
michael@0: */
michael@0: static inline UBool positionIsValid(UTransPosition& index, int32_t len) {
michael@0: return !(index.contextStart < 0 ||
michael@0: index.start < index.contextStart ||
michael@0: index.limit < index.start ||
michael@0: index.contextLimit < index.limit ||
michael@0: len < index.contextLimit);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Default constructor.
michael@0: * @param theID the string identifier for this transliterator
michael@0: * @param theFilter the filter. Any character for which
michael@0: * filter.contains() returns FALSE will not be
michael@0: * altered by this transliterator. If filter is
michael@0: * null then no filtering is applied.
michael@0: */
michael@0: Transliterator::Transliterator(const UnicodeString& theID,
michael@0: UnicodeFilter* adoptedFilter) :
michael@0: UObject(), ID(theID), filter(adoptedFilter),
michael@0: maximumContextLength(0)
michael@0: {
michael@0: // NUL-terminate the ID string, which is a non-aliased copy.
michael@0: ID.append((UChar)0);
michael@0: ID.truncate(ID.length()-1);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Destructor.
michael@0: */
michael@0: Transliterator::~Transliterator() {
michael@0: if (filter) {
michael@0: delete filter;
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * Copy constructor.
michael@0: */
michael@0: Transliterator::Transliterator(const Transliterator& other) :
michael@0: UObject(other), ID(other.ID), filter(0),
michael@0: maximumContextLength(other.maximumContextLength)
michael@0: {
michael@0: // NUL-terminate the ID string, which is a non-aliased copy.
michael@0: ID.append((UChar)0);
michael@0: ID.truncate(ID.length()-1);
michael@0:
michael@0: if (other.filter != 0) {
michael@0: // We own the filter, so we must have our own copy
michael@0: filter = (UnicodeFilter*) other.filter->clone();
michael@0: }
michael@0: }
michael@0:
michael@0: Transliterator* Transliterator::clone() const {
michael@0: return NULL;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Assignment operator.
michael@0: */
michael@0: Transliterator& Transliterator::operator=(const Transliterator& other) {
michael@0: ID = other.ID;
michael@0: // NUL-terminate the ID string
michael@0: ID.getTerminatedBuffer();
michael@0:
michael@0: maximumContextLength = other.maximumContextLength;
michael@0: adoptFilter((other.filter == 0) ? 0 : (UnicodeFilter*) other.filter->clone());
michael@0: return *this;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Transliterates a segment of a string. Transliterator
API.
michael@0: * @param text the string to be transliterated
michael@0: * @param start the beginning index, inclusive; 0 <= start
michael@0: * <= limit
.
michael@0: * @param limit the ending index, exclusive; start <= limit
michael@0: * <= text.length()
.
michael@0: * @return the new limit index, or -1
michael@0: */
michael@0: int32_t Transliterator::transliterate(Replaceable& text,
michael@0: int32_t start, int32_t limit) const {
michael@0: if (start < 0 ||
michael@0: limit < start ||
michael@0: text.length() < limit) {
michael@0: return -1;
michael@0: }
michael@0:
michael@0: UTransPosition offsets;
michael@0: offsets.contextStart= start;
michael@0: offsets.contextLimit = limit;
michael@0: offsets.start = start;
michael@0: offsets.limit = limit;
michael@0: filteredTransliterate(text, offsets, FALSE, TRUE);
michael@0: return offsets.limit;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Transliterates an entire string in place. Convenience method.
michael@0: * @param text the string to be transliterated
michael@0: */
michael@0: void Transliterator::transliterate(Replaceable& text) const {
michael@0: transliterate(text, 0, text.length());
michael@0: }
michael@0:
michael@0: /**
michael@0: * Transliterates the portion of the text buffer that can be
michael@0: * transliterated unambiguosly after new text has been inserted,
michael@0: * typically as a result of a keyboard event. The new text in
michael@0: * insertion
will be inserted into text
michael@0: * at index.contextLimit
, advancing
michael@0: * index.contextLimit
by insertion.length()
.
michael@0: * Then the transliterator will try to transliterate characters of
michael@0: * text
between index.start
and
michael@0: * index.contextLimit
. Characters before
michael@0: * index.start
will not be changed.
michael@0: *
michael@0: *
Upon return, values in index
will be updated.
michael@0: * index.contextStart
will be advanced to the first
michael@0: * character that future calls to this method will read.
michael@0: * index.start
and index.contextLimit
will
michael@0: * be adjusted to delimit the range of text that future calls to
michael@0: * this method may change.
michael@0: *
michael@0: *
Typical usage of this method begins with an initial call
michael@0: * with index.contextStart
and index.contextLimit
michael@0: * set to indicate the portion of text
to be
michael@0: * transliterated, and index.start == index.contextStart
.
michael@0: * Thereafter, index
can be used without
michael@0: * modification in future calls, provided that all changes to
michael@0: * text
are made via this method.
michael@0: *
michael@0: *
This method assumes that future calls may be made that will michael@0: * insert new text into the buffer. As a result, it only performs michael@0: * unambiguous transliterations. After the last call to this michael@0: * method, there may be untransliterated text that is waiting for michael@0: * more input to resolve an ambiguity. In order to perform these michael@0: * pending transliterations, clients should call {@link michael@0: * #finishKeyboardTransliteration} after the last call to this michael@0: * method has been made. michael@0: * michael@0: * @param text the buffer holding transliterated and untransliterated text michael@0: * @param index an array of three integers. michael@0: * michael@0: *
index.contextStart
: the beginning index,
michael@0: * inclusive; 0 <= index.contextStart <= index.contextLimit
.
michael@0: *
michael@0: * index.contextLimit
: the ending index, exclusive;
michael@0: * index.contextStart <= index.contextLimit <= text.length()
.
michael@0: * insertion
is inserted at
michael@0: * index.contextLimit
.
michael@0: *
michael@0: * index.start
: the next character to be
michael@0: * considered for transliteration; index.contextStart <=
michael@0: * index.start <= index.contextLimit
. Characters before
michael@0: * index.start
will not be changed by future calls
michael@0: * to this method.index.contextLimit
. If null
then no text
michael@0: * is inserted.
michael@0: * @see #START
michael@0: * @see #LIMIT
michael@0: * @see #CURSOR
michael@0: * @see #handleTransliterate
michael@0: * @exception IllegalArgumentException if index
michael@0: * is invalid
michael@0: */
michael@0: void Transliterator::transliterate(Replaceable& text,
michael@0: UTransPosition& index,
michael@0: const UnicodeString& insertion,
michael@0: UErrorCode &status) const {
michael@0: _transliterate(text, index, &insertion, status);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Transliterates the portion of the text buffer that can be
michael@0: * transliterated unambiguosly after a new character has been
michael@0: * inserted, typically as a result of a keyboard event. This is a
michael@0: * convenience method; see {@link
michael@0: * #transliterate(Replaceable, int[], String)} for details.
michael@0: * @param text the buffer holding transliterated and
michael@0: * untransliterated text
michael@0: * @param index an array of three integers. See {@link
michael@0: * #transliterate(Replaceable, int[], String)}.
michael@0: * @param insertion text to be inserted and possibly
michael@0: * transliterated into the translation buffer at
michael@0: * index.contextLimit
.
michael@0: * @see #transliterate(Replaceable, int[], String)
michael@0: */
michael@0: void Transliterator::transliterate(Replaceable& text,
michael@0: UTransPosition& index,
michael@0: UChar32 insertion,
michael@0: UErrorCode& status) const {
michael@0: UnicodeString str(insertion);
michael@0: _transliterate(text, index, &str, status);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Transliterates the portion of the text buffer that can be
michael@0: * transliterated unambiguosly. This is a convenience method; see
michael@0: * {@link #transliterate(Replaceable, int[], String)} for
michael@0: * details.
michael@0: * @param text the buffer holding transliterated and
michael@0: * untransliterated text
michael@0: * @param index an array of three integers. See {@link
michael@0: * #transliterate(Replaceable, int[], String)}.
michael@0: * @see #transliterate(Replaceable, int[], String)
michael@0: */
michael@0: void Transliterator::transliterate(Replaceable& text,
michael@0: UTransPosition& index,
michael@0: UErrorCode& status) const {
michael@0: _transliterate(text, index, 0, status);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Finishes any pending transliterations that were waiting for
michael@0: * more characters. Clients should call this method as the last
michael@0: * call after a sequence of one or more calls to
michael@0: * transliterate()
.
michael@0: * @param text the buffer holding transliterated and
michael@0: * untransliterated text.
michael@0: * @param index the array of indices previously passed to {@link
michael@0: * #transliterate}
michael@0: */
michael@0: void Transliterator::finishTransliteration(Replaceable& text,
michael@0: UTransPosition& index) const {
michael@0: if (!positionIsValid(index, text.length())) {
michael@0: return;
michael@0: }
michael@0:
michael@0: filteredTransliterate(text, index, FALSE, TRUE);
michael@0: }
michael@0:
michael@0: /**
michael@0: * This internal method does keyboard transliteration. If the
michael@0: * 'insertion' is non-null then we append it to 'text' before
michael@0: * proceeding. This method calls through to the pure virtual
michael@0: * framework method handleTransliterate() to do the actual
michael@0: * work.
michael@0: */
michael@0: void Transliterator::_transliterate(Replaceable& text,
michael@0: UTransPosition& index,
michael@0: const UnicodeString* insertion,
michael@0: UErrorCode &status) const {
michael@0: if (U_FAILURE(status)) {
michael@0: return;
michael@0: }
michael@0:
michael@0: if (!positionIsValid(index, text.length())) {
michael@0: status = U_ILLEGAL_ARGUMENT_ERROR;
michael@0: return;
michael@0: }
michael@0:
michael@0: // int32_t originalStart = index.contextStart;
michael@0: if (insertion != 0) {
michael@0: text.handleReplaceBetween(index.limit, index.limit, *insertion);
michael@0: index.limit += insertion->length();
michael@0: index.contextLimit += insertion->length();
michael@0: }
michael@0:
michael@0: if (index.limit > 0 &&
michael@0: U16_IS_LEAD(text.charAt(index.limit - 1))) {
michael@0: // Oops, there is a dangling lead surrogate in the buffer.
michael@0: // This will break most transliterators, since they will
michael@0: // assume it is part of a pair. Don't transliterate until
michael@0: // more text comes in.
michael@0: return;
michael@0: }
michael@0:
michael@0: filteredTransliterate(text, index, TRUE, TRUE);
michael@0:
michael@0: #if 0
michael@0: // TODO
michael@0: // I CAN'T DO what I'm attempting below now that the Kleene star
michael@0: // operator is supported. For example, in the rule
michael@0:
michael@0: // ([:Lu:]+) { x } > $1;
michael@0:
michael@0: // what is the maximum context length? getMaximumContextLength()
michael@0: // will return 1, but this is just the length of the ante context
michael@0: // part of the pattern string -- 1 character, which is a standin
michael@0: // for a Quantifier, which contains a StringMatcher, which
michael@0: // contains a UnicodeSet.
michael@0:
michael@0: // There is a complicated way to make this work again, and that's
michael@0: // to add a "maximum left context" protocol into the
michael@0: // UnicodeMatcher hierarchy. At present I'm not convinced this is
michael@0: // worth it.
michael@0:
michael@0: // ---
michael@0:
michael@0: // The purpose of the code below is to keep the context small
michael@0: // while doing incremental transliteration. When part of the left
michael@0: // context (between contextStart and start) is no longer needed,
michael@0: // we try to advance contextStart past that portion. We use the
michael@0: // maximum context length to do so.
michael@0: int32_t newCS = index.start;
michael@0: int32_t n = getMaximumContextLength();
michael@0: while (newCS > originalStart && n-- > 0) {
michael@0: --newCS;
michael@0: newCS -= U16_LENGTH(text.char32At(newCS)) - 1;
michael@0: }
michael@0: index.contextStart = uprv_max(newCS, originalStart);
michael@0: #endif
michael@0: }
michael@0:
michael@0: /**
michael@0: * This method breaks up the input text into runs of unfiltered
michael@0: * characters. It passes each such run to
michael@0: * getInstance()
, it
michael@0: * will return this object, if it has been registered.
michael@0: * @see #registerInstance
michael@0: * @see #getAvailableIDs
michael@0: */
michael@0: const UnicodeString& Transliterator::getID(void) const {
michael@0: return ID;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns a name for this transliterator that is appropriate for
michael@0: * display to the user in the default locale. See {@link
michael@0: * #getDisplayName(Locale)} for details.
michael@0: */
michael@0: UnicodeString& U_EXPORT2 Transliterator::getDisplayName(const UnicodeString& ID,
michael@0: UnicodeString& result) {
michael@0: return getDisplayName(ID, Locale::getDefault(), result);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns a name for this transliterator that is appropriate for
michael@0: * display to the user in the given locale. This name is taken
michael@0: * from the locale resource data in the standard manner of the
michael@0: * java.text
package.
michael@0: *
michael@0: * If no localized names exist in the system resource bundles,
michael@0: * a name is synthesized using a localized
michael@0: * MessageFormat
pattern from the resource data. The
michael@0: * arguments to this pattern are an integer followed by one or two
michael@0: * strings. The integer is the number of strings, either 1 or 2.
michael@0: * The strings are formed by splitting the ID for this
michael@0: * transliterator at the first TARGET_SEP. If there is no TARGET_SEP, then the
michael@0: * entire ID forms the only string.
michael@0: * @param inLocale the Locale in which the display name should be
michael@0: * localized.
michael@0: * @see java.text.MessageFormat
michael@0: */
michael@0: UnicodeString& U_EXPORT2 Transliterator::getDisplayName(const UnicodeString& id,
michael@0: const Locale& inLocale,
michael@0: UnicodeString& result) {
michael@0: UErrorCode status = U_ZERO_ERROR;
michael@0:
michael@0: ResourceBundle bundle(U_ICUDATA_TRANSLIT, inLocale, status);
michael@0:
michael@0: // Suspend checking status until later...
michael@0:
michael@0: result.truncate(0);
michael@0:
michael@0: // Normalize the ID
michael@0: UnicodeString source, target, variant;
michael@0: UBool sawSource;
michael@0: TransliteratorIDParser::IDtoSTV(id, source, target, variant, sawSource);
michael@0: if (target.length() < 1) {
michael@0: // No target; malformed id
michael@0: return result;
michael@0: }
michael@0: if (variant.length() > 0) { // Change "Foo" to "/Foo"
michael@0: variant.insert(0, VARIANT_SEP);
michael@0: }
michael@0: UnicodeString ID(source);
michael@0: ID.append(TARGET_SEP).append(target).append(variant);
michael@0:
michael@0: // build the char* key
michael@0: if (uprv_isInvariantUString(ID.getBuffer(), ID.length())) {
michael@0: char key[200];
michael@0: uprv_strcpy(key, RB_DISPLAY_NAME_PREFIX);
michael@0: int32_t length=(int32_t)uprv_strlen(RB_DISPLAY_NAME_PREFIX);
michael@0: ID.extract(0, (int32_t)(sizeof(key)-length), key+length, (int32_t)(sizeof(key)-length), US_INV);
michael@0:
michael@0: // Try to retrieve a UnicodeString from the bundle.
michael@0: UnicodeString resString = bundle.getStringEx(key, status);
michael@0:
michael@0: if (U_SUCCESS(status) && resString.length() != 0) {
michael@0: return result = resString; // [sic] assign & return
michael@0: }
michael@0:
michael@0: #if !UCONFIG_NO_FORMATTING
michael@0: // We have failed to get a name from the locale data. This is
michael@0: // typical, since most transliterators will not have localized
michael@0: // name data. The next step is to retrieve the MessageFormat
michael@0: // pattern from the locale data and to use it to synthesize the
michael@0: // name from the ID.
michael@0:
michael@0: status = U_ZERO_ERROR;
michael@0: resString = bundle.getStringEx(RB_DISPLAY_NAME_PATTERN, status);
michael@0:
michael@0: if (U_SUCCESS(status) && resString.length() != 0) {
michael@0: MessageFormat msg(resString, inLocale, status);
michael@0: // Suspend checking status until later...
michael@0:
michael@0: // We pass either 2 or 3 Formattable objects to msg.
michael@0: Formattable args[3];
michael@0: int32_t nargs;
michael@0: args[0].setLong(2); // # of args to follow
michael@0: args[1].setString(source);
michael@0: args[2].setString(target);
michael@0: nargs = 3;
michael@0:
michael@0: // Use display names for the scripts, if they exist
michael@0: UnicodeString s;
michael@0: length=(int32_t)uprv_strlen(RB_SCRIPT_DISPLAY_NAME_PREFIX);
michael@0: for (int j=1; j<=2; ++j) {
michael@0: status = U_ZERO_ERROR;
michael@0: uprv_strcpy(key, RB_SCRIPT_DISPLAY_NAME_PREFIX);
michael@0: args[j].getString(s);
michael@0: if (uprv_isInvariantUString(s.getBuffer(), s.length())) {
michael@0: s.extract(0, sizeof(key)-length-1, key+length, (int32_t)sizeof(key)-length-1, US_INV);
michael@0:
michael@0: resString = bundle.getStringEx(key, status);
michael@0:
michael@0: if (U_SUCCESS(status)) {
michael@0: args[j] = resString;
michael@0: }
michael@0: }
michael@0: }
michael@0:
michael@0: status = U_ZERO_ERROR;
michael@0: FieldPosition pos; // ignored by msg
michael@0: msg.format(args, nargs, result, pos, status);
michael@0: if (U_SUCCESS(status)) {
michael@0: result.append(variant);
michael@0: return result;
michael@0: }
michael@0: }
michael@0: #endif
michael@0: }
michael@0:
michael@0: // We should not reach this point unless there is something
michael@0: // wrong with the build or the RB_DISPLAY_NAME_PATTERN has
michael@0: // been deleted from the root RB_LOCALE_ELEMENTS resource.
michael@0: result = ID;
michael@0: return result;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns the filter used by this transliterator, or null
michael@0: * if this transliterator uses no filter. Caller musn't delete
michael@0: * the result!
michael@0: */
michael@0: const UnicodeFilter* Transliterator::getFilter(void) const {
michael@0: return filter;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns the filter used by this transliterator, or
michael@0: * NULL if this transliterator uses no filter. The
michael@0: * caller must eventually delete the result. After this call,
michael@0: * this transliterator's filter is set to NULL.
michael@0: */
michael@0: UnicodeFilter* Transliterator::orphanFilter(void) {
michael@0: UnicodeFilter *result = filter;
michael@0: filter = NULL;
michael@0: return result;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Changes the filter used by this transliterator. If the filter
michael@0: * is set to null then no filtering will occur.
michael@0: *
michael@0: *
Callers must take care if a transliterator is in use by
michael@0: * multiple threads. The filter should not be changed by one
michael@0: * thread while another thread may be transliterating.
michael@0: */
michael@0: void Transliterator::adoptFilter(UnicodeFilter* filterToAdopt) {
michael@0: delete filter;
michael@0: filter = filterToAdopt;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns this transliterator's inverse. See the class
michael@0: * documentation for details. This implementation simply inverts
michael@0: * the two entities in the ID and attempts to retrieve the
michael@0: * resulting transliterator. That is, if getID()
michael@0: * returns "A-B", then this method will return the result of
michael@0: * getInstance("B-A")
, or null
if that
michael@0: * call fails.
michael@0: *
michael@0: *
This method does not take filtering into account. The michael@0: * returned transliterator will have no filter. michael@0: * michael@0: *
Subclasses with knowledge of their inverse may wish to
michael@0: * override this method.
michael@0: *
michael@0: * @return a transliterator that is an inverse, not necessarily
michael@0: * exact, of this transliterator, or null
if no such
michael@0: * transliterator is registered.
michael@0: * @see #registerInstance
michael@0: */
michael@0: Transliterator* Transliterator::createInverse(UErrorCode& status) const {
michael@0: UParseError parseError;
michael@0: return Transliterator::createInstance(ID, UTRANS_REVERSE,parseError,status);
michael@0: }
michael@0:
michael@0: Transliterator* U_EXPORT2
michael@0: Transliterator::createInstance(const UnicodeString& ID,
michael@0: UTransDirection dir,
michael@0: UErrorCode& status)
michael@0: {
michael@0: UParseError parseError;
michael@0: return createInstance(ID, dir, parseError, status);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns a Transliterator
object given its ID.
michael@0: * The ID must be either a system transliterator ID or a ID registered
michael@0: * using registerInstance()
.
michael@0: *
michael@0: * @param ID a valid ID, as enumerated by getAvailableIDs()
michael@0: * @return A Transliterator
object with the given ID
michael@0: * @see #registerInstance
michael@0: * @see #getAvailableIDs
michael@0: * @see #getID
michael@0: */
michael@0: Transliterator* U_EXPORT2
michael@0: Transliterator::createInstance(const UnicodeString& ID,
michael@0: UTransDirection dir,
michael@0: UParseError& parseError,
michael@0: UErrorCode& status)
michael@0: {
michael@0: if (U_FAILURE(status)) {
michael@0: return 0;
michael@0: }
michael@0:
michael@0: UnicodeString canonID;
michael@0: UVector list(status);
michael@0: if (U_FAILURE(status)) {
michael@0: return NULL;
michael@0: }
michael@0:
michael@0: UnicodeSet* globalFilter;
michael@0: // TODO add code for parseError...currently unused, but
michael@0: // later may be used by parsing code...
michael@0: if (!TransliteratorIDParser::parseCompoundID(ID, dir, canonID, list, globalFilter)) {
michael@0: status = U_INVALID_ID;
michael@0: return NULL;
michael@0: }
michael@0:
michael@0: TransliteratorIDParser::instantiateList(list, status);
michael@0: if (U_FAILURE(status)) {
michael@0: return NULL;
michael@0: }
michael@0:
michael@0: U_ASSERT(list.size() > 0);
michael@0: Transliterator* t = NULL;
michael@0:
michael@0: if (list.size() > 1 || canonID.indexOf(ID_DELIM) >= 0) {
michael@0: // [NOTE: If it's a compoundID, we instantiate a CompoundTransliterator even if it only
michael@0: // has one child transliterator. This is so that toRules() will return the right thing
michael@0: // (without any inactive ID), but our main ID still comes out correct. That is, if we
michael@0: // instantiate "(Lower);Latin-Greek;", we want the rules to come out as "::Latin-Greek;"
michael@0: // even though the ID is "(Lower);Latin-Greek;".
michael@0: t = new CompoundTransliterator(list, parseError, status);
michael@0: }
michael@0: else {
michael@0: t = (Transliterator*)list.elementAt(0);
michael@0: }
michael@0: // Check null pointer
michael@0: if (t != NULL) {
michael@0: t->setID(canonID);
michael@0: if (globalFilter != NULL) {
michael@0: t->adoptFilter(globalFilter);
michael@0: }
michael@0: }
michael@0: else if (U_SUCCESS(status)) {
michael@0: status = U_MEMORY_ALLOCATION_ERROR;
michael@0: }
michael@0: return t;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Create a transliterator from a basic ID. This is an ID
michael@0: * containing only the forward direction source, target, and
michael@0: * variant.
michael@0: * @param id a basic ID of the form S-T or S-T/V.
michael@0: * @return a newly created Transliterator or null if the ID is
michael@0: * invalid.
michael@0: */
michael@0: Transliterator* Transliterator::createBasicInstance(const UnicodeString& id,
michael@0: const UnicodeString* canon) {
michael@0: UParseError pe;
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: TransliteratorAlias* alias = 0;
michael@0: Transliterator* t = 0;
michael@0:
michael@0: umtx_lock(®istryMutex);
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: t = registry->get(id, alias, ec);
michael@0: }
michael@0: umtx_unlock(®istryMutex);
michael@0:
michael@0: if (U_FAILURE(ec)) {
michael@0: delete t;
michael@0: delete alias;
michael@0: return 0;
michael@0: }
michael@0:
michael@0: // We may have not gotten a transliterator: Because we can't
michael@0: // instantiate a transliterator from inside TransliteratorRegistry::
michael@0: // get() (that would deadlock), we sometimes pass back an alias. This
michael@0: // contains the data we need to finish the instantiation outside the
michael@0: // registry mutex. The alias may, in turn, generate another alias, so
michael@0: // we handle aliases in a loop. The max times through the loop is two.
michael@0: // [alan]
michael@0: while (alias != 0) {
michael@0: U_ASSERT(t==0);
michael@0: // Rule-based aliases are handled with TransliteratorAlias::
michael@0: // parse(), followed by TransliteratorRegistry::reget().
michael@0: // Other aliases are handled with TransliteratorAlias::create().
michael@0: if (alias->isRuleBased()) {
michael@0: // Step 1. parse
michael@0: TransliteratorParser parser(ec);
michael@0: alias->parse(parser, pe, ec);
michael@0: delete alias;
michael@0: alias = 0;
michael@0:
michael@0: // Step 2. reget
michael@0: umtx_lock(®istryMutex);
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: t = registry->reget(id, parser, alias, ec);
michael@0: }
michael@0: umtx_unlock(®istryMutex);
michael@0:
michael@0: // Step 3. Loop back around!
michael@0: } else {
michael@0: t = alias->create(pe, ec);
michael@0: delete alias;
michael@0: alias = 0;
michael@0: break;
michael@0: }
michael@0: if (U_FAILURE(ec)) {
michael@0: delete t;
michael@0: delete alias;
michael@0: t = NULL;
michael@0: break;
michael@0: }
michael@0: }
michael@0:
michael@0: if (t != NULL && canon != NULL) {
michael@0: t->setID(*canon);
michael@0: }
michael@0:
michael@0: return t;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns a Transliterator
object constructed from
michael@0: * the given rule string. This will be a RuleBasedTransliterator,
michael@0: * if the rule string contains only rules, or a
michael@0: * CompoundTransliterator, if it contains ID blocks, or a
michael@0: * NullTransliterator, if it contains ID blocks which parse as
michael@0: * empty for the given direction.
michael@0: */
michael@0: Transliterator* U_EXPORT2
michael@0: Transliterator::createFromRules(const UnicodeString& ID,
michael@0: const UnicodeString& rules,
michael@0: UTransDirection dir,
michael@0: UParseError& parseError,
michael@0: UErrorCode& status)
michael@0: {
michael@0: Transliterator* t = NULL;
michael@0:
michael@0: TransliteratorParser parser(status);
michael@0: parser.parse(rules, dir, parseError, status);
michael@0:
michael@0: if (U_FAILURE(status)) {
michael@0: return 0;
michael@0: }
michael@0:
michael@0: // NOTE: The logic here matches that in TransliteratorRegistry.
michael@0: if (parser.idBlockVector.size() == 0 && parser.dataVector.size() == 0) {
michael@0: t = new NullTransliterator();
michael@0: }
michael@0: else if (parser.idBlockVector.size() == 0 && parser.dataVector.size() == 1) {
michael@0: t = new RuleBasedTransliterator(ID, (TransliterationRuleData*)parser.dataVector.orphanElementAt(0), TRUE);
michael@0: }
michael@0: else if (parser.idBlockVector.size() == 1 && parser.dataVector.size() == 0) {
michael@0: // idBlock, no data -- this is an alias. The ID has
michael@0: // been munged from reverse into forward mode, if
michael@0: // necessary, so instantiate the ID in the forward
michael@0: // direction.
michael@0: if (parser.compoundFilter != NULL) {
michael@0: UnicodeString filterPattern;
michael@0: parser.compoundFilter->toPattern(filterPattern, FALSE);
michael@0: t = createInstance(filterPattern + UnicodeString(ID_DELIM)
michael@0: + *((UnicodeString*)parser.idBlockVector.elementAt(0)), UTRANS_FORWARD, parseError, status);
michael@0: }
michael@0: else
michael@0: t = createInstance(*((UnicodeString*)parser.idBlockVector.elementAt(0)), UTRANS_FORWARD, parseError, status);
michael@0:
michael@0:
michael@0: if (t != NULL) {
michael@0: t->setID(ID);
michael@0: }
michael@0: }
michael@0: else {
michael@0: UVector transliterators(status);
michael@0: int32_t passNumber = 1;
michael@0:
michael@0: int32_t limit = parser.idBlockVector.size();
michael@0: if (parser.dataVector.size() > limit)
michael@0: limit = parser.dataVector.size();
michael@0:
michael@0: for (int32_t i = 0; i < limit; i++) {
michael@0: if (i < parser.idBlockVector.size()) {
michael@0: UnicodeString* idBlock = (UnicodeString*)parser.idBlockVector.elementAt(i);
michael@0: if (!idBlock->isEmpty()) {
michael@0: Transliterator* temp = createInstance(*idBlock, UTRANS_FORWARD, parseError, status);
michael@0: if (temp != NULL && typeid(*temp) != typeid(NullTransliterator))
michael@0: transliterators.addElement(temp, status);
michael@0: else
michael@0: delete temp;
michael@0: }
michael@0: }
michael@0: if (!parser.dataVector.isEmpty()) {
michael@0: TransliterationRuleData* data = (TransliterationRuleData*)parser.dataVector.orphanElementAt(0);
michael@0: // TODO: Should passNumber be turned into a decimal-string representation (1 -> "1")?
michael@0: RuleBasedTransliterator* temprbt = new RuleBasedTransliterator(UnicodeString(CompoundTransliterator::PASS_STRING) + UnicodeString(passNumber++),
michael@0: data, TRUE);
michael@0: // Check if NULL before adding it to transliterators to avoid future usage of NULL pointer.
michael@0: if (temprbt == NULL) {
michael@0: status = U_MEMORY_ALLOCATION_ERROR;
michael@0: return t;
michael@0: }
michael@0: transliterators.addElement(temprbt, status);
michael@0: }
michael@0: }
michael@0:
michael@0: t = new CompoundTransliterator(transliterators, passNumber - 1, parseError, status);
michael@0: // Null pointer check
michael@0: if (t != NULL) {
michael@0: t->setID(ID);
michael@0: t->adoptFilter(parser.orphanCompoundFilter());
michael@0: }
michael@0: }
michael@0: if (U_SUCCESS(status) && t == NULL) {
michael@0: status = U_MEMORY_ALLOCATION_ERROR;
michael@0: }
michael@0: return t;
michael@0: }
michael@0:
michael@0: UnicodeString& Transliterator::toRules(UnicodeString& rulesSource,
michael@0: UBool escapeUnprintable) const {
michael@0: // The base class implementation of toRules munges the ID into
michael@0: // the correct format. That is: foo => ::foo
michael@0: if (escapeUnprintable) {
michael@0: rulesSource.truncate(0);
michael@0: UnicodeString id = getID();
michael@0: for (int32_t i=0; iTransliterator
with the system. This object must
michael@0: * implement the clone() method. When
michael@0: * getInstance() is called with an ID string that is
michael@0: * equal to obj.getID(), then obj.clone() is
michael@0: * returned.
michael@0: *
michael@0: * @param obj an instance of subclass of
michael@0: * Transliterator
that defines clone()
michael@0: * @see #getInstance
michael@0: * @see #unregister
michael@0: */
michael@0: void U_EXPORT2 Transliterator::registerInstance(Transliterator* adoptedPrototype) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: _registerInstance(adoptedPrototype);
michael@0: }
michael@0: }
michael@0:
michael@0: void Transliterator::_registerInstance(Transliterator* adoptedPrototype) {
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: registry->put(adoptedPrototype, TRUE, ec);
michael@0: }
michael@0:
michael@0: void U_EXPORT2 Transliterator::registerAlias(const UnicodeString& aliasID,
michael@0: const UnicodeString& realID) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: _registerAlias(aliasID, realID);
michael@0: }
michael@0: }
michael@0:
michael@0: void Transliterator::_registerAlias(const UnicodeString& aliasID,
michael@0: const UnicodeString& realID) {
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: registry->put(aliasID, realID, FALSE, TRUE, ec);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Unregisters a transliterator or class. This may be either
michael@0: * a system transliterator or a user transliterator or class.
michael@0: *
michael@0: * @param ID the ID of the transliterator or class
michael@0: * @see #registerInstance
michael@0:
michael@0: */
michael@0: void U_EXPORT2 Transliterator::unregister(const UnicodeString& ID) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: registry->remove(ID);
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * == OBSOLETE - remove in ICU 3.4 ==
michael@0: * Return the number of IDs currently registered with the system.
michael@0: * To retrieve the actual IDs, call getAvailableID(i) with
michael@0: * i from 0 to countAvailableIDs() - 1.
michael@0: */
michael@0: int32_t U_EXPORT2 Transliterator::countAvailableIDs(void) {
michael@0: int32_t retVal = 0;
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: retVal = registry->countAvailableIDs();
michael@0: }
michael@0: return retVal;
michael@0: }
michael@0:
michael@0: /**
michael@0: * == OBSOLETE - remove in ICU 3.4 ==
michael@0: * Return the index-th available ID. index must be between 0
michael@0: * and countAvailableIDs() - 1, inclusive. If index is out of
michael@0: * range, the result of getAvailableID(0) is returned.
michael@0: */
michael@0: const UnicodeString& U_EXPORT2 Transliterator::getAvailableID(int32_t index) {
michael@0: const UnicodeString* result = NULL;
michael@0: umtx_lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: result = ®istry->getAvailableID(index);
michael@0: }
michael@0: umtx_unlock(®istryMutex);
michael@0: U_ASSERT(result != NULL); // fail if no registry
michael@0: return *result;
michael@0: }
michael@0:
michael@0: StringEnumeration* U_EXPORT2 Transliterator::getAvailableIDs(UErrorCode& ec) {
michael@0: if (U_FAILURE(ec)) return NULL;
michael@0: StringEnumeration* result = NULL;
michael@0: umtx_lock(®istryMutex);
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: result = registry->getAvailableIDs();
michael@0: }
michael@0: umtx_unlock(®istryMutex);
michael@0: if (result == NULL) {
michael@0: ec = U_INTERNAL_TRANSLITERATOR_ERROR;
michael@0: }
michael@0: return result;
michael@0: }
michael@0:
michael@0: int32_t U_EXPORT2 Transliterator::countAvailableSources(void) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: return HAVE_REGISTRY(ec) ? _countAvailableSources() : 0;
michael@0: }
michael@0:
michael@0: UnicodeString& U_EXPORT2 Transliterator::getAvailableSource(int32_t index,
michael@0: UnicodeString& result) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: _getAvailableSource(index, result);
michael@0: }
michael@0: return result;
michael@0: }
michael@0:
michael@0: int32_t U_EXPORT2 Transliterator::countAvailableTargets(const UnicodeString& source) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: return HAVE_REGISTRY(ec) ? _countAvailableTargets(source) : 0;
michael@0: }
michael@0:
michael@0: UnicodeString& U_EXPORT2 Transliterator::getAvailableTarget(int32_t index,
michael@0: const UnicodeString& source,
michael@0: UnicodeString& result) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: _getAvailableTarget(index, source, result);
michael@0: }
michael@0: return result;
michael@0: }
michael@0:
michael@0: int32_t U_EXPORT2 Transliterator::countAvailableVariants(const UnicodeString& source,
michael@0: const UnicodeString& target) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: return HAVE_REGISTRY(ec) ? _countAvailableVariants(source, target) : 0;
michael@0: }
michael@0:
michael@0: UnicodeString& U_EXPORT2 Transliterator::getAvailableVariant(int32_t index,
michael@0: const UnicodeString& source,
michael@0: const UnicodeString& target,
michael@0: UnicodeString& result) {
michael@0: Mutex lock(®istryMutex);
michael@0: UErrorCode ec = U_ZERO_ERROR;
michael@0: if (HAVE_REGISTRY(ec)) {
michael@0: _getAvailableVariant(index, source, target, result);
michael@0: }
michael@0: return result;
michael@0: }
michael@0:
michael@0: int32_t Transliterator::_countAvailableSources(void) {
michael@0: return registry->countAvailableSources();
michael@0: }
michael@0:
michael@0: UnicodeString& Transliterator::_getAvailableSource(int32_t index,
michael@0: UnicodeString& result) {
michael@0: return registry->getAvailableSource(index, result);
michael@0: }
michael@0:
michael@0: int32_t Transliterator::_countAvailableTargets(const UnicodeString& source) {
michael@0: return registry->countAvailableTargets(source);
michael@0: }
michael@0:
michael@0: UnicodeString& Transliterator::_getAvailableTarget(int32_t index,
michael@0: const UnicodeString& source,
michael@0: UnicodeString& result) {
michael@0: return registry->getAvailableTarget(index, source, result);
michael@0: }
michael@0:
michael@0: int32_t Transliterator::_countAvailableVariants(const UnicodeString& source,
michael@0: const UnicodeString& target) {
michael@0: return registry->countAvailableVariants(source, target);
michael@0: }
michael@0:
michael@0: UnicodeString& Transliterator::_getAvailableVariant(int32_t index,
michael@0: const UnicodeString& source,
michael@0: const UnicodeString& target,
michael@0: UnicodeString& result) {
michael@0: return registry->getAvailableVariant(index, source, target, result);
michael@0: }
michael@0:
michael@0: #ifdef U_USE_DEPRECATED_TRANSLITERATOR_API
michael@0:
michael@0: /**
michael@0: * Method for subclasses to use to obtain a character in the given
michael@0: * string, with filtering.
michael@0: * @deprecated the new architecture provides filtering at the top
michael@0: * level. This method will be removed Dec 31 2001.
michael@0: */
michael@0: UChar Transliterator::filteredCharAt(const Replaceable& text, int32_t i) const {
michael@0: UChar c;
michael@0: const UnicodeFilter* localFilter = getFilter();
michael@0: return (localFilter == 0) ? text.charAt(i) :
michael@0: (localFilter->contains(c = text.charAt(i)) ? c : (UChar)0xFFFE);
michael@0: }
michael@0:
michael@0: #endif
michael@0:
michael@0: /**
michael@0: * If the registry is initialized, return TRUE. If not, initialize it
michael@0: * and return TRUE. If the registry cannot be initialized, return
michael@0: * FALSE (rare).
michael@0: *
michael@0: * IMPORTANT: Upon entry, registryMutex must be LOCKED. The entire
michael@0: * initialization is done with the lock held. There is NO REASON to
michael@0: * unlock, since no other thread that is waiting on the registryMutex
michael@0: * cannot itself proceed until the registry is initialized.
michael@0: */
michael@0: UBool Transliterator::initializeRegistry(UErrorCode &status) {
michael@0: if (registry != 0) {
michael@0: return TRUE;
michael@0: }
michael@0:
michael@0: registry = new TransliteratorRegistry(status);
michael@0: if (registry == 0 || U_FAILURE(status)) {
michael@0: delete registry;
michael@0: registry = 0;
michael@0: return FALSE; // can't create registry, no recovery
michael@0: }
michael@0:
michael@0: /* The following code parses the index table located in
michael@0: * icu/data/translit/root.txt. The index is an n x 4 table
michael@0: * that follows this format:
michael@0: *