michael@0: /* michael@0: ********************************************************************** michael@0: * Copyright (c) 2002-2012, International Business Machines Corporation michael@0: * and others. All Rights Reserved. michael@0: ********************************************************************** michael@0: * Date Name Description michael@0: * 01/14/2002 aliu Creation. michael@0: ********************************************************************** michael@0: */ michael@0: michael@0: #include "unicode/utypes.h" michael@0: michael@0: #if !UCONFIG_NO_TRANSLITERATION michael@0: michael@0: #include "tridpars.h" michael@0: #include "hash.h" michael@0: #include "mutex.h" michael@0: #include "ucln_in.h" michael@0: #include "unicode/parsepos.h" michael@0: #include "unicode/translit.h" michael@0: #include "unicode/uchar.h" michael@0: #include "unicode/uniset.h" michael@0: #include "unicode/unistr.h" michael@0: #include "unicode/utrans.h" michael@0: #include "util.h" michael@0: #include "uvector.h" michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: static const UChar ID_DELIM = 0x003B; // ; michael@0: static const UChar TARGET_SEP = 0x002D; // - michael@0: static const UChar VARIANT_SEP = 0x002F; // / michael@0: static const UChar OPEN_REV = 0x0028; // ( michael@0: static const UChar CLOSE_REV = 0x0029; // ) michael@0: michael@0: //static const UChar EMPTY[] = {0}; // "" michael@0: static const UChar ANY[] = {65,110,121,0}; // "Any" michael@0: static const UChar ANY_NULL[] = {65,110,121,45,78,117,108,108,0}; // "Any-Null" michael@0: michael@0: static const int32_t FORWARD = UTRANS_FORWARD; michael@0: static const int32_t REVERSE = UTRANS_REVERSE; michael@0: michael@0: static Hashtable* SPECIAL_INVERSES = NULL; michael@0: michael@0: /** michael@0: * The mutex controlling access to SPECIAL_INVERSES michael@0: */ michael@0: static UMutex LOCK = U_MUTEX_INITIALIZER; michael@0: michael@0: TransliteratorIDParser::Specs::Specs(const UnicodeString& s, const UnicodeString& t, michael@0: const UnicodeString& v, UBool sawS, michael@0: const UnicodeString& f) { michael@0: source = s; michael@0: target = t; michael@0: variant = v; michael@0: sawSource = sawS; michael@0: filter = f; michael@0: } michael@0: michael@0: TransliteratorIDParser::SingleID::SingleID(const UnicodeString& c, const UnicodeString& b, michael@0: const UnicodeString& f) { michael@0: canonID = c; michael@0: basicID = b; michael@0: filter = f; michael@0: } michael@0: michael@0: TransliteratorIDParser::SingleID::SingleID(const UnicodeString& c, const UnicodeString& b) { michael@0: canonID = c; michael@0: basicID = b; michael@0: } michael@0: michael@0: Transliterator* TransliteratorIDParser::SingleID::createInstance() { michael@0: Transliterator* t; michael@0: if (basicID.length() == 0) { michael@0: t = createBasicInstance(UnicodeString(TRUE, ANY_NULL, 8), &canonID); michael@0: } else { michael@0: t = createBasicInstance(basicID, &canonID); michael@0: } michael@0: if (t != NULL) { michael@0: if (filter.length() != 0) { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: UnicodeSet *set = new UnicodeSet(filter, ec); michael@0: if (U_FAILURE(ec)) { michael@0: delete set; michael@0: } else { michael@0: t->adoptFilter(set); michael@0: } michael@0: } michael@0: } michael@0: return t; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Parse a single ID, that is, an ID of the general form michael@0: * "[f1] s1-t1/v1 ([f2] s2-t3/v2)", with the parenthesized element michael@0: * optional, the filters optional, and the variants optional. michael@0: * @param id the id to be parsed michael@0: * @param pos INPUT-OUTPUT parameter. On input, the position of michael@0: * the first character to parse. On output, the position after michael@0: * the last character parsed. michael@0: * @param dir the direction. If the direction is REVERSE then the michael@0: * SingleID is constructed for the reverse direction. michael@0: * @return a SingleID object or NULL michael@0: */ michael@0: TransliteratorIDParser::SingleID* michael@0: TransliteratorIDParser::parseSingleID(const UnicodeString& id, int32_t& pos, michael@0: int32_t dir, UErrorCode& status) { michael@0: michael@0: int32_t start = pos; michael@0: michael@0: // The ID will be of the form A, A(), A(B), or (B), where michael@0: // A and B are filter IDs. michael@0: Specs* specsA = NULL; michael@0: Specs* specsB = NULL; michael@0: UBool sawParen = FALSE; michael@0: michael@0: // On the first pass, look for (B) or (). If this fails, then michael@0: // on the second pass, look for A, A(B), or A(). michael@0: for (int32_t pass=1; pass<=2; ++pass) { michael@0: if (pass == 2) { michael@0: specsA = parseFilterID(id, pos, TRUE); michael@0: if (specsA == NULL) { michael@0: pos = start; michael@0: return NULL; michael@0: } michael@0: } michael@0: if (ICU_Utility::parseChar(id, pos, OPEN_REV)) { michael@0: sawParen = TRUE; michael@0: if (!ICU_Utility::parseChar(id, pos, CLOSE_REV)) { michael@0: specsB = parseFilterID(id, pos, TRUE); michael@0: // Must close with a ')' michael@0: if (specsB == NULL || !ICU_Utility::parseChar(id, pos, CLOSE_REV)) { michael@0: delete specsA; michael@0: pos = start; michael@0: return NULL; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Assemble return results michael@0: SingleID* single; michael@0: if (sawParen) { michael@0: if (dir == FORWARD) { michael@0: SingleID* b = specsToID(specsB, FORWARD); michael@0: single = specsToID(specsA, FORWARD); michael@0: // Null pointers check michael@0: if (b == NULL || single == NULL) { michael@0: delete b; michael@0: delete single; michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: single->canonID.append(OPEN_REV) michael@0: .append(b->canonID).append(CLOSE_REV); michael@0: if (specsA != NULL) { michael@0: single->filter = specsA->filter; michael@0: } michael@0: delete b; michael@0: } else { michael@0: SingleID* a = specsToID(specsA, FORWARD); michael@0: single = specsToID(specsB, FORWARD); michael@0: // Check for null pointer. michael@0: if (a == NULL || single == NULL) { michael@0: delete a; michael@0: delete single; michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: single->canonID.append(OPEN_REV) michael@0: .append(a->canonID).append(CLOSE_REV); michael@0: if (specsB != NULL) { michael@0: single->filter = specsB->filter; michael@0: } michael@0: delete a; michael@0: } michael@0: } else { michael@0: // assert(specsA != NULL); michael@0: if (dir == FORWARD) { michael@0: single = specsToID(specsA, FORWARD); michael@0: } else { michael@0: single = specsToSpecialInverse(*specsA, status); michael@0: if (single == NULL) { michael@0: single = specsToID(specsA, REVERSE); michael@0: } michael@0: } michael@0: // Check for NULL pointer michael@0: if (single == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: single->filter = specsA->filter; michael@0: } michael@0: michael@0: delete specsA; michael@0: delete specsB; michael@0: michael@0: return single; michael@0: } michael@0: michael@0: /** michael@0: * Parse a filter ID, that is, an ID of the general form michael@0: * "[f1] s1-t1/v1", with the filters optional, and the variants optional. michael@0: * @param id the id to be parsed michael@0: * @param pos INPUT-OUTPUT parameter. On input, the position of michael@0: * the first character to parse. On output, the position after michael@0: * the last character parsed. michael@0: * @return a SingleID object or null if the parse fails michael@0: */ michael@0: TransliteratorIDParser::SingleID* michael@0: TransliteratorIDParser::parseFilterID(const UnicodeString& id, int32_t& pos) { michael@0: michael@0: int32_t start = pos; michael@0: michael@0: Specs* specs = parseFilterID(id, pos, TRUE); michael@0: if (specs == NULL) { michael@0: pos = start; michael@0: return NULL; michael@0: } michael@0: michael@0: // Assemble return results michael@0: SingleID* single = specsToID(specs, FORWARD); michael@0: if (single != NULL) { michael@0: single->filter = specs->filter; michael@0: } michael@0: delete specs; michael@0: return single; michael@0: } michael@0: michael@0: /** michael@0: * Parse a global filter of the form "[f]" or "([f])", depending michael@0: * on 'withParens'. michael@0: * @param id the pattern the parse michael@0: * @param pos INPUT-OUTPUT parameter. On input, the position of michael@0: * the first character to parse. On output, the position after michael@0: * the last character parsed. michael@0: * @param dir the direction. michael@0: * @param withParens INPUT-OUTPUT parameter. On entry, if michael@0: * withParens is 0, then parens are disallowed. If it is 1, michael@0: * then parens are requires. If it is -1, then parens are michael@0: * optional, and the return result will be set to 0 or 1. michael@0: * @param canonID OUTPUT parameter. The pattern for the filter michael@0: * added to the canonID, either at the end, if dir is FORWARD, or michael@0: * at the start, if dir is REVERSE. The pattern will be enclosed michael@0: * in parentheses if appropriate, and will be suffixed with an michael@0: * ID_DELIM character. May be NULL. michael@0: * @return a UnicodeSet object or NULL. A non-NULL results michael@0: * indicates a successful parse, regardless of whether the filter michael@0: * applies to the given direction. The caller should discard it michael@0: * if withParens != (dir == REVERSE). michael@0: */ michael@0: UnicodeSet* TransliteratorIDParser::parseGlobalFilter(const UnicodeString& id, int32_t& pos, michael@0: int32_t dir, michael@0: int32_t& withParens, michael@0: UnicodeString* canonID) { michael@0: UnicodeSet* filter = NULL; michael@0: int32_t start = pos; michael@0: michael@0: if (withParens == -1) { michael@0: withParens = ICU_Utility::parseChar(id, pos, OPEN_REV) ? 1 : 0; michael@0: } else if (withParens == 1) { michael@0: if (!ICU_Utility::parseChar(id, pos, OPEN_REV)) { michael@0: pos = start; michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: ICU_Utility::skipWhitespace(id, pos, TRUE); michael@0: michael@0: if (UnicodeSet::resemblesPattern(id, pos)) { michael@0: ParsePosition ppos(pos); michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: filter = new UnicodeSet(id, ppos, USET_IGNORE_SPACE, NULL, ec); michael@0: /* test for NULL */ michael@0: if (filter == 0) { michael@0: pos = start; michael@0: return 0; michael@0: } michael@0: if (U_FAILURE(ec)) { michael@0: delete filter; michael@0: pos = start; michael@0: return NULL; michael@0: } michael@0: michael@0: UnicodeString pattern; michael@0: id.extractBetween(pos, ppos.getIndex(), pattern); michael@0: pos = ppos.getIndex(); michael@0: michael@0: if (withParens == 1 && !ICU_Utility::parseChar(id, pos, CLOSE_REV)) { michael@0: pos = start; michael@0: return NULL; michael@0: } michael@0: michael@0: // In the forward direction, append the pattern to the michael@0: // canonID. In the reverse, insert it at zero, and invert michael@0: // the presence of parens ("A" <-> "(A)"). michael@0: if (canonID != NULL) { michael@0: if (dir == FORWARD) { michael@0: if (withParens == 1) { michael@0: pattern.insert(0, OPEN_REV); michael@0: pattern.append(CLOSE_REV); michael@0: } michael@0: canonID->append(pattern).append(ID_DELIM); michael@0: } else { michael@0: if (withParens == 0) { michael@0: pattern.insert(0, OPEN_REV); michael@0: pattern.append(CLOSE_REV); michael@0: } michael@0: canonID->insert(0, pattern); michael@0: canonID->insert(pattern.length(), ID_DELIM); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return filter; michael@0: } michael@0: michael@0: U_CDECL_BEGIN michael@0: static void U_CALLCONV _deleteSingleID(void* obj) { michael@0: delete (TransliteratorIDParser::SingleID*) obj; michael@0: } michael@0: michael@0: static void U_CALLCONV _deleteTransliteratorTrIDPars(void* obj) { michael@0: delete (Transliterator*) obj; michael@0: } michael@0: U_CDECL_END michael@0: michael@0: /** michael@0: * Parse a compound ID, consisting of an optional forward global michael@0: * filter, a separator, one or more single IDs delimited by michael@0: * separators, an an optional reverse global filter. The michael@0: * separator is a semicolon. The global filters are UnicodeSet michael@0: * patterns. The reverse global filter must be enclosed in michael@0: * parentheses. michael@0: * @param id the pattern the parse michael@0: * @param dir the direction. michael@0: * @param canonID OUTPUT parameter that receives the canonical ID, michael@0: * consisting of canonical IDs for all elements, as returned by michael@0: * parseSingleID(), separated by semicolons. Previous contents michael@0: * are discarded. michael@0: * @param list OUTPUT parameter that receives a list of SingleID michael@0: * objects representing the parsed IDs. Previous contents are michael@0: * discarded. michael@0: * @param globalFilter OUTPUT parameter that receives a pointer to michael@0: * a newly created global filter for this ID in this direction, or michael@0: * NULL if there is none. michael@0: * @return TRUE if the parse succeeds, that is, if the entire michael@0: * id is consumed without syntax error. michael@0: */ michael@0: UBool TransliteratorIDParser::parseCompoundID(const UnicodeString& id, int32_t dir, michael@0: UnicodeString& canonID, michael@0: UVector& list, michael@0: UnicodeSet*& globalFilter) { michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: int32_t i; michael@0: int32_t pos = 0; michael@0: int32_t withParens = 1; michael@0: list.removeAllElements(); michael@0: UnicodeSet* filter; michael@0: globalFilter = NULL; michael@0: canonID.truncate(0); michael@0: michael@0: // Parse leading global filter, if any michael@0: withParens = 0; // parens disallowed michael@0: filter = parseGlobalFilter(id, pos, dir, withParens, &canonID); michael@0: if (filter != NULL) { michael@0: if (!ICU_Utility::parseChar(id, pos, ID_DELIM)) { michael@0: // Not a global filter; backup and resume michael@0: canonID.truncate(0); michael@0: pos = 0; michael@0: } michael@0: if (dir == FORWARD) { michael@0: globalFilter = filter; michael@0: } else { michael@0: delete filter; michael@0: } michael@0: filter = NULL; michael@0: } michael@0: michael@0: UBool sawDelimiter = TRUE; michael@0: for (;;) { michael@0: SingleID* single = parseSingleID(id, pos, dir, ec); michael@0: if (single == NULL) { michael@0: break; michael@0: } michael@0: if (dir == FORWARD) { michael@0: list.addElement(single, ec); michael@0: } else { michael@0: list.insertElementAt(single, 0, ec); michael@0: } michael@0: if (U_FAILURE(ec)) { michael@0: goto FAIL; michael@0: } michael@0: if (!ICU_Utility::parseChar(id, pos, ID_DELIM)) { michael@0: sawDelimiter = FALSE; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (list.size() == 0) { michael@0: goto FAIL; michael@0: } michael@0: michael@0: // Construct canonical ID michael@0: for (i=0; icanonID); michael@0: if (i != (list.size()-1)) { michael@0: canonID.append(ID_DELIM); michael@0: } michael@0: } michael@0: michael@0: // Parse trailing global filter, if any, and only if we saw michael@0: // a trailing delimiter after the IDs. michael@0: if (sawDelimiter) { michael@0: withParens = 1; // parens required michael@0: filter = parseGlobalFilter(id, pos, dir, withParens, &canonID); michael@0: if (filter != NULL) { michael@0: // Don't require trailing ';', but parse it if present michael@0: ICU_Utility::parseChar(id, pos, ID_DELIM); michael@0: michael@0: if (dir == REVERSE) { michael@0: globalFilter = filter; michael@0: } else { michael@0: delete filter; michael@0: } michael@0: filter = NULL; michael@0: } michael@0: } michael@0: michael@0: // Trailing unparsed text is a syntax error michael@0: ICU_Utility::skipWhitespace(id, pos, TRUE); michael@0: if (pos != id.length()) { michael@0: goto FAIL; michael@0: } michael@0: michael@0: return TRUE; michael@0: michael@0: FAIL: michael@0: UObjectDeleter *save = list.setDeleter(_deleteSingleID); michael@0: list.removeAllElements(); michael@0: list.setDeleter(save); michael@0: delete globalFilter; michael@0: globalFilter = NULL; michael@0: return FALSE; michael@0: } michael@0: michael@0: /** michael@0: * Convert the elements of the 'list' vector, which are SingleID michael@0: * objects, into actual Transliterator objects. In the course of michael@0: * this, some (or all) entries may be removed. If all entries michael@0: * are removed, the NULL transliterator will be added. michael@0: * michael@0: * Delete entries with empty basicIDs; these are generated by michael@0: * elements like "(A)" in the forward direction, or "A()" in michael@0: * the reverse. THIS MAY RESULT IN AN EMPTY VECTOR. Convert michael@0: * SingleID entries to actual transliterators. michael@0: * michael@0: * @param list vector of SingleID objects. On exit, vector michael@0: * of one or more Transliterators. michael@0: * @return new value of insertIndex. The index will shift if michael@0: * there are empty items, like "(Lower)", with indices less than michael@0: * insertIndex. michael@0: */ michael@0: void TransliteratorIDParser::instantiateList(UVector& list, michael@0: UErrorCode& ec) { michael@0: UVector tlist(ec); michael@0: if (U_FAILURE(ec)) { michael@0: goto RETURN; michael@0: } michael@0: tlist.setDeleter(_deleteTransliteratorTrIDPars); michael@0: michael@0: Transliterator* t; michael@0: int32_t i; michael@0: for (i=0; i<=list.size(); ++i) { // [sic]: i<=list.size() michael@0: // We run the loop too long by one, so we can michael@0: // do an insert after the last element michael@0: if (i==list.size()) { michael@0: break; michael@0: } michael@0: michael@0: SingleID* single = (SingleID*) list.elementAt(i); michael@0: if (single->basicID.length() != 0) { michael@0: t = single->createInstance(); michael@0: if (t == NULL) { michael@0: ec = U_INVALID_ID; michael@0: goto RETURN; michael@0: } michael@0: tlist.addElement(t, ec); michael@0: if (U_FAILURE(ec)) { michael@0: delete t; michael@0: goto RETURN; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // An empty list is equivalent to a NULL transliterator. michael@0: if (tlist.size() == 0) { michael@0: t = createBasicInstance(UnicodeString(TRUE, ANY_NULL, 8), NULL); michael@0: if (t == NULL) { michael@0: // Should never happen michael@0: ec = U_INTERNAL_TRANSLITERATOR_ERROR; michael@0: } michael@0: tlist.addElement(t, ec); michael@0: if (U_FAILURE(ec)) { michael@0: delete t; michael@0: } michael@0: } michael@0: michael@0: RETURN: michael@0: michael@0: UObjectDeleter *save = list.setDeleter(_deleteSingleID); michael@0: list.removeAllElements(); michael@0: michael@0: if (U_SUCCESS(ec)) { michael@0: list.setDeleter(_deleteTransliteratorTrIDPars); michael@0: michael@0: while (tlist.size() > 0) { michael@0: t = (Transliterator*) tlist.orphanElementAt(0); michael@0: list.addElement(t, ec); michael@0: if (U_FAILURE(ec)) { michael@0: delete t; michael@0: list.removeAllElements(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: list.setDeleter(save); michael@0: } michael@0: michael@0: /** michael@0: * Parse an ID into pieces. Take IDs of the form T, T/V, S-T, michael@0: * S-T/V, or S/V-T. If the source is missing, return a source of michael@0: * ANY. michael@0: * @param id the id string, in any of several forms michael@0: * @return an array of 4 strings: source, target, variant, and michael@0: * isSourcePresent. If the source is not present, ANY will be michael@0: * given as the source, and isSourcePresent will be NULL. Otherwise michael@0: * isSourcePresent will be non-NULL. The target may be empty if the michael@0: * id is not well-formed. The variant may be empty. michael@0: */ michael@0: void TransliteratorIDParser::IDtoSTV(const UnicodeString& id, michael@0: UnicodeString& source, michael@0: UnicodeString& target, michael@0: UnicodeString& variant, michael@0: UBool& isSourcePresent) { michael@0: source.setTo(ANY, 3); michael@0: target.truncate(0); michael@0: variant.truncate(0); michael@0: michael@0: int32_t sep = id.indexOf(TARGET_SEP); michael@0: int32_t var = id.indexOf(VARIANT_SEP); michael@0: if (var < 0) { michael@0: var = id.length(); michael@0: } michael@0: isSourcePresent = FALSE; michael@0: michael@0: if (sep < 0) { michael@0: // Form: T/V or T (or /V) michael@0: id.extractBetween(0, var, target); michael@0: id.extractBetween(var, id.length(), variant); michael@0: } else if (sep < var) { michael@0: // Form: S-T/V or S-T (or -T/V or -T) michael@0: if (sep > 0) { michael@0: id.extractBetween(0, sep, source); michael@0: isSourcePresent = TRUE; michael@0: } michael@0: id.extractBetween(++sep, var, target); michael@0: id.extractBetween(var, id.length(), variant); michael@0: } else { michael@0: // Form: (S/V-T or /V-T) michael@0: if (var > 0) { michael@0: id.extractBetween(0, var, source); michael@0: isSourcePresent = TRUE; michael@0: } michael@0: id.extractBetween(var, sep++, variant); michael@0: id.extractBetween(sep, id.length(), target); michael@0: } michael@0: michael@0: if (variant.length() > 0) { michael@0: variant.remove(0, 1); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Given source, target, and variant strings, concatenate them into a michael@0: * full ID. If the source is empty, then "Any" will be used for the michael@0: * source, so the ID will always be of the form s-t/v or s-t. michael@0: */ michael@0: void TransliteratorIDParser::STVtoID(const UnicodeString& source, michael@0: const UnicodeString& target, michael@0: const UnicodeString& variant, michael@0: UnicodeString& id) { michael@0: id = source; michael@0: if (id.length() == 0) { michael@0: id.setTo(ANY, 3); michael@0: } michael@0: id.append(TARGET_SEP).append(target); michael@0: if (variant.length() != 0) { michael@0: id.append(VARIANT_SEP).append(variant); michael@0: } michael@0: // NUL-terminate the ID string for getTerminatedBuffer. michael@0: // This prevents valgrind and Purify warnings. michael@0: id.append((UChar)0); michael@0: id.truncate(id.length()-1); michael@0: } michael@0: michael@0: /** michael@0: * Register two targets as being inverses of one another. For michael@0: * example, calling registerSpecialInverse("NFC", "NFD", TRUE) causes michael@0: * Transliterator to form the following inverse relationships: michael@0: * michael@0: *
NFC => NFD
michael@0:  * Any-NFC => Any-NFD
michael@0:  * NFD => NFC
michael@0:  * Any-NFD => Any-NFC
michael@0: * michael@0: * (Without the special inverse registration, the inverse of NFC michael@0: * would be NFC-Any.) Note that NFD is shorthand for Any-NFD, but michael@0: * that the presence or absence of "Any-" is preserved. michael@0: * michael@0: *

The relationship is symmetrical; registering (a, b) is michael@0: * equivalent to registering (b, a). michael@0: * michael@0: *

The relevant IDs must still be registered separately as michael@0: * factories or classes. michael@0: * michael@0: *

Only the targets are specified. Special inverses always michael@0: * have the form Any-Target1 <=> Any-Target2. The target should michael@0: * have canonical casing (the casing desired to be produced when michael@0: * an inverse is formed) and should contain no whitespace or other michael@0: * extraneous characters. michael@0: * michael@0: * @param target the target against which to register the inverse michael@0: * @param inverseTarget the inverse of target, that is michael@0: * Any-target.getInverse() => Any-inverseTarget michael@0: * @param bidirectional if TRUE, register the reverse relation michael@0: * as well, that is, Any-inverseTarget.getInverse() => Any-target michael@0: */ michael@0: void TransliteratorIDParser::registerSpecialInverse(const UnicodeString& target, michael@0: const UnicodeString& inverseTarget, michael@0: UBool bidirectional, michael@0: UErrorCode &status) { michael@0: init(status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: // If target == inverseTarget then force bidirectional => FALSE michael@0: if (bidirectional && 0==target.caseCompare(inverseTarget, U_FOLD_CASE_DEFAULT)) { michael@0: bidirectional = FALSE; michael@0: } michael@0: michael@0: Mutex lock(&LOCK); michael@0: michael@0: UnicodeString *tempus = new UnicodeString(inverseTarget); // Used for null pointer check before usage. michael@0: if (tempus == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: SPECIAL_INVERSES->put(target, tempus, status); michael@0: if (bidirectional) { michael@0: tempus = new UnicodeString(target); michael@0: if (tempus == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: SPECIAL_INVERSES->put(inverseTarget, tempus, status); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------- michael@0: // Private implementation michael@0: //---------------------------------------------------------------- michael@0: michael@0: /** michael@0: * Parse an ID into component pieces. Take IDs of the form T, michael@0: * T/V, S-T, S-T/V, or S/V-T. If the source is missing, return a michael@0: * source of ANY. michael@0: * @param id the id string, in any of several forms michael@0: * @param pos INPUT-OUTPUT parameter. On input, pos is the michael@0: * offset of the first character to parse in id. On output, michael@0: * pos is the offset after the last parsed character. If the michael@0: * parse failed, pos will be unchanged. michael@0: * @param allowFilter2 if TRUE, a UnicodeSet pattern is allowed michael@0: * at any location between specs or delimiters, and is returned michael@0: * as the fifth string in the array. michael@0: * @return a Specs object, or NULL if the parse failed. If michael@0: * neither source nor target was seen in the parsed id, then the michael@0: * parse fails. If allowFilter is TRUE, then the parsed filter michael@0: * pattern is returned in the Specs object, otherwise the returned michael@0: * filter reference is NULL. If the parse fails for any reason michael@0: * NULL is returned. michael@0: */ michael@0: TransliteratorIDParser::Specs* michael@0: TransliteratorIDParser::parseFilterID(const UnicodeString& id, int32_t& pos, michael@0: UBool allowFilter) { michael@0: UnicodeString first; michael@0: UnicodeString source; michael@0: UnicodeString target; michael@0: UnicodeString variant; michael@0: UnicodeString filter; michael@0: UChar delimiter = 0; michael@0: int32_t specCount = 0; michael@0: int32_t start = pos; michael@0: michael@0: // This loop parses one of the following things with each michael@0: // pass: a filter, a delimiter character (either '-' or '/'), michael@0: // or a spec (source, target, or variant). michael@0: for (;;) { michael@0: ICU_Utility::skipWhitespace(id, pos, TRUE); michael@0: if (pos == id.length()) { michael@0: break; michael@0: } michael@0: michael@0: // Parse filters michael@0: if (allowFilter && filter.length() == 0 && michael@0: UnicodeSet::resemblesPattern(id, pos)) { michael@0: michael@0: ParsePosition ppos(pos); michael@0: UErrorCode ec = U_ZERO_ERROR; michael@0: UnicodeSet set(id, ppos, USET_IGNORE_SPACE, NULL, ec); michael@0: if (U_FAILURE(ec)) { michael@0: pos = start; michael@0: return NULL; michael@0: } michael@0: id.extractBetween(pos, ppos.getIndex(), filter); michael@0: pos = ppos.getIndex(); michael@0: continue; michael@0: } michael@0: michael@0: if (delimiter == 0) { michael@0: UChar c = id.charAt(pos); michael@0: if ((c == TARGET_SEP && target.length() == 0) || michael@0: (c == VARIANT_SEP && variant.length() == 0)) { michael@0: delimiter = c; michael@0: ++pos; michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // We are about to try to parse a spec with no delimiter michael@0: // when we can no longer do so (we can only do so at the michael@0: // start); break. michael@0: if (delimiter == 0 && specCount > 0) { michael@0: break; michael@0: } michael@0: michael@0: UnicodeString spec = ICU_Utility::parseUnicodeIdentifier(id, pos); michael@0: if (spec.length() == 0) { michael@0: // Note that if there was a trailing delimiter, we michael@0: // consume it. So Foo-, Foo/, Foo-Bar/, and Foo/Bar- michael@0: // are legal. michael@0: break; michael@0: } michael@0: michael@0: switch (delimiter) { michael@0: case 0: michael@0: first = spec; michael@0: break; michael@0: case TARGET_SEP: michael@0: target = spec; michael@0: break; michael@0: case VARIANT_SEP: michael@0: variant = spec; michael@0: break; michael@0: } michael@0: ++specCount; michael@0: delimiter = 0; michael@0: } michael@0: michael@0: // A spec with no prior character is either source or target, michael@0: // depending on whether an explicit "-target" was seen. michael@0: if (first.length() != 0) { michael@0: if (target.length() == 0) { michael@0: target = first; michael@0: } else { michael@0: source = first; michael@0: } michael@0: } michael@0: michael@0: // Must have either source or target michael@0: if (source.length() == 0 && target.length() == 0) { michael@0: pos = start; michael@0: return NULL; michael@0: } michael@0: michael@0: // Empty source or target defaults to ANY michael@0: UBool sawSource = TRUE; michael@0: if (source.length() == 0) { michael@0: source.setTo(ANY, 3); michael@0: sawSource = FALSE; michael@0: } michael@0: if (target.length() == 0) { michael@0: target.setTo(ANY, 3); michael@0: } michael@0: michael@0: return new Specs(source, target, variant, sawSource, filter); michael@0: } michael@0: michael@0: /** michael@0: * Givens a Spec object, convert it to a SingleID object. The michael@0: * Spec object is a more unprocessed parse result. The SingleID michael@0: * object contains information about canonical and basic IDs. michael@0: * @return a SingleID; never returns NULL. Returned object always michael@0: * has 'filter' field of NULL. michael@0: */ michael@0: TransliteratorIDParser::SingleID* michael@0: TransliteratorIDParser::specsToID(const Specs* specs, int32_t dir) { michael@0: UnicodeString canonID; michael@0: UnicodeString basicID; michael@0: UnicodeString basicPrefix; michael@0: if (specs != NULL) { michael@0: UnicodeString buf; michael@0: if (dir == FORWARD) { michael@0: if (specs->sawSource) { michael@0: buf.append(specs->source).append(TARGET_SEP); michael@0: } else { michael@0: basicPrefix = specs->source; michael@0: basicPrefix.append(TARGET_SEP); michael@0: } michael@0: buf.append(specs->target); michael@0: } else { michael@0: buf.append(specs->target).append(TARGET_SEP).append(specs->source); michael@0: } michael@0: if (specs->variant.length() != 0) { michael@0: buf.append(VARIANT_SEP).append(specs->variant); michael@0: } michael@0: basicID = basicPrefix; michael@0: basicID.append(buf); michael@0: if (specs->filter.length() != 0) { michael@0: buf.insert(0, specs->filter); michael@0: } michael@0: canonID = buf; michael@0: } michael@0: return new SingleID(canonID, basicID); michael@0: } michael@0: michael@0: /** michael@0: * Given a Specs object, return a SingleID representing the michael@0: * special inverse of that ID. If there is no special inverse michael@0: * then return NULL. michael@0: * @return a SingleID or NULL. Returned object always has michael@0: * 'filter' field of NULL. michael@0: */ michael@0: TransliteratorIDParser::SingleID* michael@0: TransliteratorIDParser::specsToSpecialInverse(const Specs& specs, UErrorCode &status) { michael@0: if (0!=specs.source.caseCompare(ANY, 3, U_FOLD_CASE_DEFAULT)) { michael@0: return NULL; michael@0: } michael@0: init(status); michael@0: michael@0: UnicodeString* inverseTarget; michael@0: michael@0: umtx_lock(&LOCK); michael@0: inverseTarget = (UnicodeString*) SPECIAL_INVERSES->get(specs.target); michael@0: umtx_unlock(&LOCK); michael@0: michael@0: if (inverseTarget != NULL) { michael@0: // If the original ID contained "Any-" then make the michael@0: // special inverse "Any-Foo"; otherwise make it "Foo". michael@0: // So "Any-NFC" => "Any-NFD" but "NFC" => "NFD". michael@0: UnicodeString buf; michael@0: if (specs.filter.length() != 0) { michael@0: buf.append(specs.filter); michael@0: } michael@0: if (specs.sawSource) { michael@0: buf.append(ANY, 3).append(TARGET_SEP); michael@0: } michael@0: buf.append(*inverseTarget); michael@0: michael@0: UnicodeString basicID(TRUE, ANY, 3); michael@0: basicID.append(TARGET_SEP).append(*inverseTarget); michael@0: michael@0: if (specs.variant.length() != 0) { michael@0: buf.append(VARIANT_SEP).append(specs.variant); michael@0: basicID.append(VARIANT_SEP).append(specs.variant); michael@0: } michael@0: return new SingleID(buf, basicID); michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: /** michael@0: * Glue method to get around access problems in C++. This would michael@0: * ideally be inline but we want to avoid a circular header michael@0: * dependency. michael@0: */ michael@0: Transliterator* TransliteratorIDParser::createBasicInstance(const UnicodeString& id, const UnicodeString* canonID) { michael@0: return Transliterator::createBasicInstance(id, canonID); michael@0: } michael@0: michael@0: /** michael@0: * Initialize static memory. michael@0: */ michael@0: void TransliteratorIDParser::init(UErrorCode &status) { michael@0: if (SPECIAL_INVERSES != NULL) { michael@0: return; michael@0: } michael@0: michael@0: Hashtable* special_inverses = new Hashtable(TRUE, status); michael@0: // Null pointer check michael@0: if (special_inverses == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: special_inverses->setValueDeleter(uprv_deleteUObject); michael@0: michael@0: umtx_lock(&LOCK); michael@0: if (SPECIAL_INVERSES == NULL) { michael@0: SPECIAL_INVERSES = special_inverses; michael@0: special_inverses = NULL; michael@0: } michael@0: umtx_unlock(&LOCK); michael@0: delete special_inverses; /*null instance*/ michael@0: michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TRANSLITERATOR, utrans_transliterator_cleanup); michael@0: } michael@0: michael@0: /** michael@0: * Free static memory. michael@0: */ michael@0: void TransliteratorIDParser::cleanup() { michael@0: if (SPECIAL_INVERSES) { michael@0: delete SPECIAL_INVERSES; michael@0: SPECIAL_INVERSES = NULL; michael@0: } michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: #endif /* #if !UCONFIG_NO_TRANSLITERATION */ michael@0: michael@0: //eof