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: *

michael@0: * michael@0: * @param insertion text to be inserted and possibly michael@0: * transliterated into the translation buffer at michael@0: * 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: * .handleTransliterate(). Subclasses that can handle the michael@0: * filter logic more efficiently themselves may override this method. michael@0: * michael@0: * All transliteration calls in this class go through this method. michael@0: */ michael@0: void Transliterator::filteredTransliterate(Replaceable& text, michael@0: UTransPosition& index, michael@0: UBool incremental, michael@0: UBool rollback) const { michael@0: // Short circuit path for transliterators with no filter in michael@0: // non-incremental mode. michael@0: if (filter == 0 && !rollback) { michael@0: handleTransliterate(text, index, incremental); michael@0: return; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // This method processes text in two groupings: michael@0: // michael@0: // RUNS -- A run is a contiguous group of characters which are contained michael@0: // in the filter for this transliterator (filter.contains(ch) == TRUE). michael@0: // Text outside of runs may appear as context but it is not modified. michael@0: // The start and limit Position values are narrowed to each run. michael@0: // michael@0: // PASSES (incremental only) -- To make incremental mode work correctly, michael@0: // each run is broken up into n passes, where n is the length (in code michael@0: // points) of the run. Each pass contains the first n characters. If a michael@0: // pass is completely transliterated, it is committed, and further passes michael@0: // include characters after the committed text. If a pass is blocked, michael@0: // and does not transliterate completely, then this method rolls back michael@0: // the changes made during the pass, extends the pass by one code point, michael@0: // and tries again. michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // globalLimit is the limit value for the entire operation. We michael@0: // set index.limit to the end of each unfiltered run before michael@0: // calling handleTransliterate(), so we need to maintain the real michael@0: // value of index.limit here. After each transliteration, we michael@0: // update globalLimit for insertions or deletions that have michael@0: // happened. michael@0: int32_t globalLimit = index.limit; michael@0: michael@0: // If there is a non-null filter, then break the input text up. Say the michael@0: // input text has the form: michael@0: // xxxabcxxdefxx michael@0: // where 'x' represents a filtered character (filter.contains('x') == michael@0: // false). Then we break this up into: michael@0: // xxxabc xxdef xx michael@0: // Each pass through the loop consumes a run of filtered michael@0: // characters (which are ignored) and a subsequent run of michael@0: // unfiltered characters (which are transliterated). michael@0: michael@0: for (;;) { michael@0: michael@0: if (filter != NULL) { michael@0: // Narrow the range to be transliterated to the first segment michael@0: // of unfiltered characters at or after index.start. michael@0: michael@0: // Advance past filtered chars michael@0: UChar32 c; michael@0: while (index.start < globalLimit && michael@0: !filter->contains(c=text.char32At(index.start))) { michael@0: index.start += U16_LENGTH(c); michael@0: } michael@0: michael@0: // Find the end of this run of unfiltered chars michael@0: index.limit = index.start; michael@0: while (index.limit < globalLimit && michael@0: filter->contains(c=text.char32At(index.limit))) { michael@0: index.limit += U16_LENGTH(c); michael@0: } michael@0: } michael@0: michael@0: // Check to see if the unfiltered run is empty. This only michael@0: // happens at the end of the string when all the remaining michael@0: // characters are filtered. michael@0: if (index.limit == index.start) { michael@0: // assert(index.start == globalLimit); michael@0: break; michael@0: } michael@0: michael@0: // Is this run incremental? If there is additional michael@0: // filtered text (if limit < globalLimit) then we pass in michael@0: // an incremental value of FALSE to force the subclass to michael@0: // complete the transliteration for this run. michael@0: UBool isIncrementalRun = michael@0: (index.limit < globalLimit ? FALSE : incremental); michael@0: michael@0: int32_t delta; michael@0: michael@0: // Implement rollback. To understand the need for rollback, michael@0: // consider the following transliterator: michael@0: // michael@0: // "t" is "a > A;" michael@0: // "u" is "A > b;" michael@0: // "v" is a compound of "t; NFD; u" with a filter [:Ll:] michael@0: // michael@0: // Now apply "c" to the input text "a". The result is "b". But if michael@0: // the transliteration is done incrementally, then the NFD holds michael@0: // things up after "t" has already transformed "a" to "A". When michael@0: // finishTransliterate() is called, "A" is _not_ processed because michael@0: // it gets excluded by the [:Ll:] filter, and the end result is "A" michael@0: // -- incorrect. The problem is that the filter is applied to a michael@0: // partially-transliterated result, when we only want it to apply to michael@0: // input text. Although this example hinges on a compound michael@0: // transliterator containing NFD and a specific filter, it can michael@0: // actually happen with any transliterator which may do a partial michael@0: // transformation in incremental mode into characters outside its michael@0: // filter. michael@0: // michael@0: // To handle this, when in incremental mode we supply characters to michael@0: // handleTransliterate() in several passes. Each pass adds one more michael@0: // input character to the input text. That is, for input "ABCD", we michael@0: // first try "A", then "AB", then "ABC", and finally "ABCD". If at michael@0: // any point we block (upon return, start < limit) then we roll michael@0: // back. If at any point we complete the run (upon return start == michael@0: // limit) then we commit that run. michael@0: michael@0: if (rollback && isIncrementalRun) { michael@0: michael@0: int32_t runStart = index.start; michael@0: int32_t runLimit = index.limit; michael@0: int32_t runLength = runLimit - runStart; michael@0: michael@0: // Make a rollback copy at the end of the string michael@0: int32_t rollbackOrigin = text.length(); michael@0: text.copy(runStart, runLimit, rollbackOrigin); michael@0: michael@0: // Variables reflecting the commitment of completely michael@0: // transliterated text. passStart is the runStart, advanced michael@0: // past committed text. rollbackStart is the rollbackOrigin, michael@0: // advanced past rollback text that corresponds to committed michael@0: // text. michael@0: int32_t passStart = runStart; michael@0: int32_t rollbackStart = rollbackOrigin; michael@0: michael@0: // The limit for each pass; we advance by one code point with michael@0: // each iteration. michael@0: int32_t passLimit = index.start; michael@0: michael@0: // Total length, in 16-bit code units, of uncommitted text. michael@0: // This is the length to be rolled back. michael@0: int32_t uncommittedLength = 0; michael@0: michael@0: // Total delta (change in length) for all passes michael@0: int32_t totalDelta = 0; michael@0: michael@0: // PASS MAIN LOOP -- Start with a single character, and extend michael@0: // the text by one character at a time. Roll back partial michael@0: // transliterations and commit complete transliterations. michael@0: for (;;) { michael@0: // Length of additional code point, either one or two michael@0: int32_t charLength = U16_LENGTH(text.char32At(passLimit)); michael@0: passLimit += charLength; michael@0: if (passLimit > runLimit) { michael@0: break; michael@0: } michael@0: uncommittedLength += charLength; michael@0: michael@0: index.limit = passLimit; michael@0: michael@0: // Delegate to subclass for actual transliteration. Upon michael@0: // return, start will be updated to point after the michael@0: // transliterated text, and limit and contextLimit will be michael@0: // adjusted for length changes. michael@0: handleTransliterate(text, index, TRUE); michael@0: michael@0: delta = index.limit - passLimit; // change in length michael@0: michael@0: // We failed to completely transliterate this pass. michael@0: // Roll back the text. Indices remain unchanged; reset michael@0: // them where necessary. michael@0: if (index.start != index.limit) { michael@0: // Find the rollbackStart, adjusted for length changes michael@0: // and the deletion of partially transliterated text. michael@0: int32_t rs = rollbackStart + delta - (index.limit - passStart); michael@0: michael@0: // Delete the partially transliterated text michael@0: text.handleReplaceBetween(passStart, index.limit, UnicodeString()); michael@0: michael@0: // Copy the rollback text back michael@0: text.copy(rs, rs + uncommittedLength, passStart); michael@0: michael@0: // Restore indices to their original values michael@0: index.start = passStart; michael@0: index.limit = passLimit; michael@0: index.contextLimit -= delta; michael@0: } michael@0: michael@0: // We did completely transliterate this pass. Update the michael@0: // commit indices to record how far we got. Adjust indices michael@0: // for length change. michael@0: else { michael@0: // Move the pass indices past the committed text. michael@0: passStart = passLimit = index.start; michael@0: michael@0: // Adjust the rollbackStart for length changes and move michael@0: // it past the committed text. All characters we've michael@0: // processed to this point are committed now, so zero michael@0: // out the uncommittedLength. michael@0: rollbackStart += delta + uncommittedLength; michael@0: uncommittedLength = 0; michael@0: michael@0: // Adjust indices for length changes. michael@0: runLimit += delta; michael@0: totalDelta += delta; michael@0: } michael@0: } michael@0: michael@0: // Adjust overall limit and rollbackOrigin for insertions and michael@0: // deletions. Don't need to worry about contextLimit because michael@0: // handleTransliterate() maintains that. michael@0: rollbackOrigin += totalDelta; michael@0: globalLimit += totalDelta; michael@0: michael@0: // Delete the rollback copy michael@0: text.handleReplaceBetween(rollbackOrigin, rollbackOrigin + runLength, UnicodeString()); michael@0: michael@0: // Move start past committed text michael@0: index.start = passStart; michael@0: } michael@0: michael@0: else { michael@0: // Delegate to subclass for actual transliteration. michael@0: int32_t limit = index.limit; michael@0: handleTransliterate(text, index, isIncrementalRun); michael@0: delta = index.limit - limit; // change in length michael@0: michael@0: // In a properly written transliterator, start == limit after michael@0: // handleTransliterate() returns when incremental is false. michael@0: // Catch cases where the subclass doesn't do this, and throw michael@0: // an exception. (Just pinning start to limit is a bad idea, michael@0: // because what's probably happening is that the subclass michael@0: // isn't transliterating all the way to the end, and it should michael@0: // in non-incremental mode.) michael@0: if (!incremental && index.start != index.limit) { michael@0: // We can't throw an exception, so just fudge things michael@0: index.start = index.limit; michael@0: } michael@0: michael@0: // Adjust overall limit for insertions/deletions. Don't need michael@0: // to worry about contextLimit because handleTransliterate() michael@0: // maintains that. michael@0: globalLimit += delta; michael@0: } michael@0: michael@0: if (filter == NULL || isIncrementalRun) { michael@0: break; michael@0: } michael@0: michael@0: // If we did completely transliterate this michael@0: // run, then repeat with the next unfiltered run. michael@0: } michael@0: michael@0: // Start is valid where it is. Limit needs to be put back where michael@0: // it was, modulo adjustments for deletions/insertions. michael@0: index.limit = globalLimit; michael@0: } michael@0: michael@0: void Transliterator::filteredTransliterate(Replaceable& text, michael@0: UTransPosition& index, michael@0: UBool incremental) const { michael@0: filteredTransliterate(text, index, incremental, FALSE); michael@0: } michael@0: michael@0: /** michael@0: * Method for subclasses to use to set the maximum context length. michael@0: * @see #getMaximumContextLength michael@0: */ michael@0: void Transliterator::setMaximumContextLength(int32_t maxContextLength) { michael@0: maximumContextLength = maxContextLength; michael@0: } michael@0: michael@0: /** michael@0: * Returns a programmatic identifier for this transliterator. michael@0: * If this identifier is passed to 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; i(this); michael@0: return ct != NULL ? ct->getCount() : 0; michael@0: } michael@0: michael@0: const Transliterator& Transliterator::getElement(int32_t index, UErrorCode& ec) const { michael@0: if (U_FAILURE(ec)) { michael@0: return *this; michael@0: } michael@0: const CompoundTransliterator* cpd = dynamic_cast(this); michael@0: int32_t n = (cpd == NULL) ? 1 : cpd->getCount(); michael@0: if (index < 0 || index >= n) { michael@0: ec = U_INDEX_OUTOFBOUNDS_ERROR; michael@0: return *this; michael@0: } else { michael@0: return (n == 1) ? *this : cpd->getTransliterator(index); michael@0: } michael@0: } michael@0: michael@0: UnicodeSet& Transliterator::getSourceSet(UnicodeSet& result) const { michael@0: handleGetSourceSet(result); michael@0: if (filter != NULL) { michael@0: UnicodeSet* filterSet = dynamic_cast(filter); michael@0: UBool deleteFilterSet = FALSE; michael@0: // Most, but not all filters will be UnicodeSets. Optimize for michael@0: // the high-runner case. michael@0: if (filterSet == NULL) { michael@0: filterSet = new UnicodeSet(); michael@0: // Check null pointer michael@0: if (filterSet == NULL) { michael@0: return result; michael@0: } michael@0: deleteFilterSet = TRUE; michael@0: filter->addMatchSetTo(*filterSet); michael@0: } michael@0: result.retainAll(*filterSet); michael@0: if (deleteFilterSet) { michael@0: delete filterSet; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: void Transliterator::handleGetSourceSet(UnicodeSet& result) const { michael@0: result.clear(); michael@0: } michael@0: michael@0: UnicodeSet& Transliterator::getTargetSet(UnicodeSet& result) const { michael@0: return result.clear(); michael@0: } michael@0: michael@0: // For public consumption michael@0: void U_EXPORT2 Transliterator::registerFactory(const UnicodeString& id, michael@0: Transliterator::Factory factory, michael@0: Transliterator::Token context) { michael@0: Mutex lock(®istryMutex); michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: if (HAVE_REGISTRY(ec)) { michael@0: _registerFactory(id, factory, context); michael@0: } michael@0: } michael@0: michael@0: // To be called only by Transliterator subclasses that are called michael@0: // to register themselves by initializeRegistry(). michael@0: void Transliterator::_registerFactory(const UnicodeString& id, michael@0: Transliterator::Factory factory, michael@0: Transliterator::Token context) { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: registry->put(id, factory, context, TRUE, ec); michael@0: } michael@0: michael@0: // To be called only by Transliterator subclasses that are called michael@0: // to register themselves by initializeRegistry(). michael@0: void Transliterator::_registerSpecialInverse(const UnicodeString& target, michael@0: const UnicodeString& inverseTarget, michael@0: UBool bidirectional) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: TransliteratorIDParser::registerSpecialInverse(target, inverseTarget, bidirectional, status); michael@0: } michael@0: michael@0: /** michael@0: * Registers a instance obj of a subclass of michael@0: * Transliterator 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: * { michael@0: * file{ michael@0: * resource{""} michael@0: * direction{""} michael@0: * } michael@0: * } michael@0: * { michael@0: * internal{ michael@0: * resource{""} michael@0: * direction{"{ michael@0: * alias{" is the ID of the system transliterator being defined. These michael@0: * are public IDs enumerated by Transliterator.getAvailableIDs(), michael@0: * unless the second field is "internal". michael@0: * michael@0: * is a ResourceReader resource name. Currently these refer michael@0: * to file names under com/ibm/text/resources. This string is passed michael@0: * directly to ResourceReader, together with . michael@0: * michael@0: * is either "FORWARD" or "REVERSE". michael@0: * michael@0: * is a string to be passed directly to michael@0: * Transliterator.getInstance(). The returned Transliterator object michael@0: * then has its ID changed to and is returned. michael@0: * michael@0: * The extra blank field on "alias" lines is to make the array square. michael@0: */ michael@0: //static const char translit_index[] = "translit_index"; michael@0: michael@0: UResourceBundle *bundle, *transIDs, *colBund; michael@0: bundle = ures_open(U_ICUDATA_TRANSLIT, NULL/*open default locale*/, &status); michael@0: transIDs = ures_getByKey(bundle, RB_RULE_BASED_IDS, 0, &status); michael@0: michael@0: int32_t row, maxRows; michael@0: if (U_SUCCESS(status)) { michael@0: maxRows = ures_getSize(transIDs); michael@0: for (row = 0; row < maxRows; row++) { michael@0: colBund = ures_getByIndex(transIDs, row, 0, &status); michael@0: if (U_SUCCESS(status)) { michael@0: UnicodeString id(ures_getKey(colBund), -1, US_INV); michael@0: UResourceBundle* res = ures_getNextResource(colBund, NULL, &status); michael@0: const char* typeStr = ures_getKey(res); michael@0: UChar type; michael@0: u_charsToUChars(typeStr, &type, 1); michael@0: michael@0: if (U_SUCCESS(status)) { michael@0: int32_t len = 0; michael@0: const UChar *resString; michael@0: switch (type) { michael@0: case 0x66: // 'f' michael@0: case 0x69: // 'i' michael@0: // 'file' or 'internal'; michael@0: // row[2]=resource, row[3]=direction michael@0: { michael@0: michael@0: resString = ures_getStringByKey(res, "resource", &len, &status); michael@0: UBool visible = (type == 0x0066 /*f*/); michael@0: UTransDirection dir = michael@0: (ures_getUnicodeStringByKey(res, "direction", &status).charAt(0) == michael@0: 0x0046 /*F*/) ? michael@0: UTRANS_FORWARD : UTRANS_REVERSE; michael@0: registry->put(id, UnicodeString(TRUE, resString, len), dir, TRUE, visible, status); michael@0: } michael@0: break; michael@0: case 0x61: // 'a' michael@0: // 'alias'; row[2]=createInstance argument michael@0: resString = ures_getString(res, &len, &status); michael@0: registry->put(id, UnicodeString(TRUE, resString, len), TRUE, TRUE, status); michael@0: break; michael@0: } michael@0: } michael@0: ures_close(res); michael@0: } michael@0: ures_close(colBund); michael@0: } michael@0: } michael@0: michael@0: ures_close(transIDs); michael@0: ures_close(bundle); michael@0: michael@0: // Manually add prototypes that the system knows about to the michael@0: // cache. This is how new non-rule-based transliterators are michael@0: // added to the system. michael@0: michael@0: // This is to allow for null pointer check michael@0: NullTransliterator* tempNullTranslit = new NullTransliterator(); michael@0: LowercaseTransliterator* tempLowercaseTranslit = new LowercaseTransliterator(); michael@0: UppercaseTransliterator* tempUppercaseTranslit = new UppercaseTransliterator(); michael@0: TitlecaseTransliterator* tempTitlecaseTranslit = new TitlecaseTransliterator(); michael@0: UnicodeNameTransliterator* tempUnicodeTranslit = new UnicodeNameTransliterator(); michael@0: NameUnicodeTransliterator* tempNameUnicodeTranslit = new NameUnicodeTransliterator(); michael@0: #if !UCONFIG_NO_BREAK_ITERATION michael@0: // TODO: could or should these transliterators be referenced polymorphically once constructed? michael@0: BreakTransliterator* tempBreakTranslit = new BreakTransliterator(); michael@0: #endif michael@0: // Check for null pointers michael@0: if (tempNullTranslit == NULL || tempLowercaseTranslit == NULL || tempUppercaseTranslit == NULL || michael@0: tempTitlecaseTranslit == NULL || tempUnicodeTranslit == NULL || michael@0: #if !UCONFIG_NO_BREAK_ITERATION michael@0: tempBreakTranslit == NULL || michael@0: #endif michael@0: tempNameUnicodeTranslit == NULL ) michael@0: { michael@0: delete tempNullTranslit; michael@0: delete tempLowercaseTranslit; michael@0: delete tempUppercaseTranslit; michael@0: delete tempTitlecaseTranslit; michael@0: delete tempUnicodeTranslit; michael@0: delete tempNameUnicodeTranslit; michael@0: #if !UCONFIG_NO_BREAK_ITERATION michael@0: delete tempBreakTranslit; michael@0: #endif michael@0: // Since there was an error, remove registry michael@0: delete registry; michael@0: registry = NULL; michael@0: michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return 0; michael@0: } michael@0: michael@0: registry->put(tempNullTranslit, TRUE, status); michael@0: registry->put(tempLowercaseTranslit, TRUE, status); michael@0: registry->put(tempUppercaseTranslit, TRUE, status); michael@0: registry->put(tempTitlecaseTranslit, TRUE, status); michael@0: registry->put(tempUnicodeTranslit, TRUE, status); michael@0: registry->put(tempNameUnicodeTranslit, TRUE, status); michael@0: #if !UCONFIG_NO_BREAK_ITERATION michael@0: registry->put(tempBreakTranslit, FALSE, status); // FALSE means invisible. michael@0: #endif michael@0: michael@0: RemoveTransliterator::registerIDs(); // Must be within mutex michael@0: EscapeTransliterator::registerIDs(); michael@0: UnescapeTransliterator::registerIDs(); michael@0: NormalizationTransliterator::registerIDs(); michael@0: AnyTransliterator::registerIDs(); michael@0: michael@0: _registerSpecialInverse(UNICODE_STRING_SIMPLE("Null"), michael@0: UNICODE_STRING_SIMPLE("Null"), FALSE); michael@0: _registerSpecialInverse(UNICODE_STRING_SIMPLE("Upper"), michael@0: UNICODE_STRING_SIMPLE("Lower"), TRUE); michael@0: _registerSpecialInverse(UNICODE_STRING_SIMPLE("Title"), michael@0: UNICODE_STRING_SIMPLE("Lower"), FALSE); michael@0: michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TRANSLITERATOR, utrans_transliterator_cleanup); michael@0: michael@0: return TRUE; michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: // Defined in ucln_in.h: michael@0: michael@0: /** michael@0: * Release all static memory held by transliterator. This will michael@0: * necessarily invalidate any rule-based transliterators held by the michael@0: * user, because RBTs hold pointers to common data objects. michael@0: */ michael@0: U_CFUNC UBool utrans_transliterator_cleanup(void) { michael@0: U_NAMESPACE_USE michael@0: TransliteratorIDParser::cleanup(); michael@0: if (registry) { michael@0: delete registry; michael@0: registry = NULL; michael@0: } michael@0: return TRUE; michael@0: } michael@0: michael@0: #endif /* #if !UCONFIG_NO_TRANSLITERATION */ michael@0: michael@0: //eof