summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/tridpars.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/icu/source/i18n/tridpars.cpp')
-rw-r--r--intl/icu/source/i18n/tridpars.cpp932
1 files changed, 932 insertions, 0 deletions
diff --git a/intl/icu/source/i18n/tridpars.cpp b/intl/icu/source/i18n/tridpars.cpp
new file mode 100644
index 0000000000..6c23a0dc90
--- /dev/null
+++ b/intl/icu/source/i18n/tridpars.cpp
@@ -0,0 +1,932 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+**********************************************************************
+* Copyright (c) 2002-2014, International Business Machines Corporation
+* and others. All Rights Reserved.
+**********************************************************************
+* Date Name Description
+* 01/14/2002 aliu Creation.
+**********************************************************************
+*/
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_TRANSLITERATION
+
+#include "tridpars.h"
+#include "hash.h"
+#include "mutex.h"
+#include "transreg.h"
+#include "uassert.h"
+#include "ucln_in.h"
+#include "unicode/parsepos.h"
+#include "unicode/translit.h"
+#include "unicode/uchar.h"
+#include "unicode/uniset.h"
+#include "unicode/unistr.h"
+#include "unicode/utrans.h"
+#include "util.h"
+#include "uvector.h"
+
+U_NAMESPACE_BEGIN
+
+static const char16_t ID_DELIM = 0x003B; // ;
+static const char16_t TARGET_SEP = 0x002D; // -
+static const char16_t VARIANT_SEP = 0x002F; // /
+static const char16_t OPEN_REV = 0x0028; // (
+static const char16_t CLOSE_REV = 0x0029; // )
+
+//static const char16_t EMPTY[] = {0}; // ""
+static const char16_t ANY[] = {65,110,121,0}; // "Any"
+static const char16_t ANY_NULL[] = {65,110,121,45,78,117,108,108,0}; // "Any-Null"
+
+static const int32_t FORWARD = UTRANS_FORWARD;
+static const int32_t REVERSE = UTRANS_REVERSE;
+
+static Hashtable* SPECIAL_INVERSES = nullptr;
+static UInitOnce gSpecialInversesInitOnce {};
+
+/**
+ * The mutex controlling access to SPECIAL_INVERSES
+ */
+static UMutex LOCK;
+
+TransliteratorIDParser::Specs::Specs(const UnicodeString& s, const UnicodeString& t,
+ const UnicodeString& v, UBool sawS,
+ const UnicodeString& f) {
+ source = s;
+ target = t;
+ variant = v;
+ sawSource = sawS;
+ filter = f;
+}
+
+TransliteratorIDParser::SingleID::SingleID(const UnicodeString& c, const UnicodeString& b,
+ const UnicodeString& f) {
+ canonID = c;
+ basicID = b;
+ filter = f;
+}
+
+TransliteratorIDParser::SingleID::SingleID(const UnicodeString& c, const UnicodeString& b) {
+ canonID = c;
+ basicID = b;
+}
+
+Transliterator* TransliteratorIDParser::SingleID::createInstance() {
+ Transliterator* t;
+ if (basicID.length() == 0) {
+ t = createBasicInstance(UnicodeString(true, ANY_NULL, 8), &canonID);
+ } else {
+ t = createBasicInstance(basicID, &canonID);
+ }
+ if (t != nullptr) {
+ if (filter.length() != 0) {
+ UErrorCode ec = U_ZERO_ERROR;
+ UnicodeSet *set = new UnicodeSet(filter, ec);
+ if (U_FAILURE(ec)) {
+ delete set;
+ } else {
+ t->adoptFilter(set);
+ }
+ }
+ }
+ return t;
+}
+
+
+/**
+ * Parse a single ID, that is, an ID of the general form
+ * "[f1] s1-t1/v1 ([f2] s2-t3/v2)", with the parenthesized element
+ * optional, the filters optional, and the variants optional.
+ * @param id the id to be parsed
+ * @param pos INPUT-OUTPUT parameter. On input, the position of
+ * the first character to parse. On output, the position after
+ * the last character parsed.
+ * @param dir the direction. If the direction is REVERSE then the
+ * SingleID is constructed for the reverse direction.
+ * @return a SingleID object or nullptr
+ */
+TransliteratorIDParser::SingleID*
+TransliteratorIDParser::parseSingleID(const UnicodeString& id, int32_t& pos,
+ int32_t dir, UErrorCode& status) {
+
+ int32_t start = pos;
+
+ // The ID will be of the form A, A(), A(B), or (B), where
+ // A and B are filter IDs.
+ Specs* specsA = nullptr;
+ Specs* specsB = nullptr;
+ UBool sawParen = false;
+
+ // On the first pass, look for (B) or (). If this fails, then
+ // on the second pass, look for A, A(B), or A().
+ for (int32_t pass=1; pass<=2; ++pass) {
+ if (pass == 2) {
+ specsA = parseFilterID(id, pos, true);
+ if (specsA == nullptr) {
+ pos = start;
+ return nullptr;
+ }
+ }
+ if (ICU_Utility::parseChar(id, pos, OPEN_REV)) {
+ sawParen = true;
+ if (!ICU_Utility::parseChar(id, pos, CLOSE_REV)) {
+ specsB = parseFilterID(id, pos, true);
+ // Must close with a ')'
+ if (specsB == nullptr || !ICU_Utility::parseChar(id, pos, CLOSE_REV)) {
+ delete specsA;
+ pos = start;
+ return nullptr;
+ }
+ }
+ break;
+ }
+ }
+
+ // Assemble return results
+ SingleID* single;
+ if (sawParen) {
+ if (dir == FORWARD) {
+ SingleID* b = specsToID(specsB, FORWARD);
+ single = specsToID(specsA, FORWARD);
+ // Null pointers check
+ if (b == nullptr || single == nullptr) {
+ delete b;
+ delete single;
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return nullptr;
+ }
+ single->canonID.append(OPEN_REV)
+ .append(b->canonID).append(CLOSE_REV);
+ if (specsA != nullptr) {
+ single->filter = specsA->filter;
+ }
+ delete b;
+ } else {
+ SingleID* a = specsToID(specsA, FORWARD);
+ single = specsToID(specsB, FORWARD);
+ // Check for null pointer.
+ if (a == nullptr || single == nullptr) {
+ delete a;
+ delete single;
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return nullptr;
+ }
+ single->canonID.append(OPEN_REV)
+ .append(a->canonID).append(CLOSE_REV);
+ if (specsB != nullptr) {
+ single->filter = specsB->filter;
+ }
+ delete a;
+ }
+ } else {
+ // assert(specsA != nullptr);
+ if (dir == FORWARD) {
+ single = specsToID(specsA, FORWARD);
+ } else {
+ single = specsToSpecialInverse(*specsA, status);
+ if (single == nullptr) {
+ single = specsToID(specsA, REVERSE);
+ }
+ }
+ // Check for nullptr pointer
+ if (single == nullptr) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return nullptr;
+ }
+ single->filter = specsA->filter;
+ }
+
+ delete specsA;
+ delete specsB;
+
+ return single;
+}
+
+/**
+ * Parse a filter ID, that is, an ID of the general form
+ * "[f1] s1-t1/v1", with the filters optional, and the variants optional.
+ * @param id the id to be parsed
+ * @param pos INPUT-OUTPUT parameter. On input, the position of
+ * the first character to parse. On output, the position after
+ * the last character parsed.
+ * @return a SingleID object or null if the parse fails
+ */
+TransliteratorIDParser::SingleID*
+TransliteratorIDParser::parseFilterID(const UnicodeString& id, int32_t& pos) {
+
+ int32_t start = pos;
+
+ Specs* specs = parseFilterID(id, pos, true);
+ if (specs == nullptr) {
+ pos = start;
+ return nullptr;
+ }
+
+ // Assemble return results
+ SingleID* single = specsToID(specs, FORWARD);
+ if (single != nullptr) {
+ single->filter = specs->filter;
+ }
+ delete specs;
+ return single;
+}
+
+/**
+ * Parse a global filter of the form "[f]" or "([f])", depending
+ * on 'withParens'.
+ * @param id the pattern the parse
+ * @param pos INPUT-OUTPUT parameter. On input, the position of
+ * the first character to parse. On output, the position after
+ * the last character parsed.
+ * @param dir the direction.
+ * @param withParens INPUT-OUTPUT parameter. On entry, if
+ * withParens is 0, then parens are disallowed. If it is 1,
+ * then parens are requires. If it is -1, then parens are
+ * optional, and the return result will be set to 0 or 1.
+ * @param canonID OUTPUT parameter. The pattern for the filter
+ * added to the canonID, either at the end, if dir is FORWARD, or
+ * at the start, if dir is REVERSE. The pattern will be enclosed
+ * in parentheses if appropriate, and will be suffixed with an
+ * ID_DELIM character. May be nullptr.
+ * @return a UnicodeSet object or nullptr. A non-nullptr results
+ * indicates a successful parse, regardless of whether the filter
+ * applies to the given direction. The caller should discard it
+ * if withParens != (dir == REVERSE).
+ */
+UnicodeSet* TransliteratorIDParser::parseGlobalFilter(const UnicodeString& id, int32_t& pos,
+ int32_t dir,
+ int32_t& withParens,
+ UnicodeString* canonID) {
+ UnicodeSet* filter = nullptr;
+ int32_t start = pos;
+
+ if (withParens == -1) {
+ withParens = ICU_Utility::parseChar(id, pos, OPEN_REV) ? 1 : 0;
+ } else if (withParens == 1) {
+ if (!ICU_Utility::parseChar(id, pos, OPEN_REV)) {
+ pos = start;
+ return nullptr;
+ }
+ }
+
+ ICU_Utility::skipWhitespace(id, pos, true);
+
+ if (UnicodeSet::resemblesPattern(id, pos)) {
+ ParsePosition ppos(pos);
+ UErrorCode ec = U_ZERO_ERROR;
+ filter = new UnicodeSet(id, ppos, USET_IGNORE_SPACE, nullptr, ec);
+ /* test for nullptr */
+ if (filter == 0) {
+ pos = start;
+ return 0;
+ }
+ if (U_FAILURE(ec)) {
+ delete filter;
+ pos = start;
+ return nullptr;
+ }
+
+ UnicodeString pattern;
+ id.extractBetween(pos, ppos.getIndex(), pattern);
+ pos = ppos.getIndex();
+
+ if (withParens == 1 && !ICU_Utility::parseChar(id, pos, CLOSE_REV)) {
+ delete filter;
+ pos = start;
+ return nullptr;
+ }
+
+ // In the forward direction, append the pattern to the
+ // canonID. In the reverse, insert it at zero, and invert
+ // the presence of parens ("A" <-> "(A)").
+ if (canonID != nullptr) {
+ if (dir == FORWARD) {
+ if (withParens == 1) {
+ pattern.insert(0, OPEN_REV);
+ pattern.append(CLOSE_REV);
+ }
+ canonID->append(pattern).append(ID_DELIM);
+ } else {
+ if (withParens == 0) {
+ pattern.insert(0, OPEN_REV);
+ pattern.append(CLOSE_REV);
+ }
+ canonID->insert(0, pattern);
+ canonID->insert(pattern.length(), ID_DELIM);
+ }
+ }
+ }
+
+ return filter;
+}
+
+U_CDECL_BEGIN
+static void U_CALLCONV _deleteSingleID(void* obj) {
+ delete (TransliteratorIDParser::SingleID*) obj;
+}
+
+static void U_CALLCONV _deleteTransliteratorTrIDPars(void* obj) {
+ delete (Transliterator*) obj;
+}
+U_CDECL_END
+
+/**
+ * Parse a compound ID, consisting of an optional forward global
+ * filter, a separator, one or more single IDs delimited by
+ * separators, an an optional reverse global filter. The
+ * separator is a semicolon. The global filters are UnicodeSet
+ * patterns. The reverse global filter must be enclosed in
+ * parentheses.
+ * @param id the pattern the parse
+ * @param dir the direction.
+ * @param canonID OUTPUT parameter that receives the canonical ID,
+ * consisting of canonical IDs for all elements, as returned by
+ * parseSingleID(), separated by semicolons. Previous contents
+ * are discarded.
+ * @param list OUTPUT parameter that receives a list of SingleID
+ * objects representing the parsed IDs. Previous contents are
+ * discarded.
+ * @param globalFilter OUTPUT parameter that receives a pointer to
+ * a newly created global filter for this ID in this direction, or
+ * nullptr if there is none.
+ * @return true if the parse succeeds, that is, if the entire
+ * id is consumed without syntax error.
+ */
+UBool TransliteratorIDParser::parseCompoundID(const UnicodeString& id, int32_t dir,
+ UnicodeString& canonID,
+ UVector& list,
+ UnicodeSet*& globalFilter) {
+ UErrorCode ec = U_ZERO_ERROR;
+ int32_t i;
+ int32_t pos = 0;
+ int32_t withParens = 1;
+ list.removeAllElements();
+ UObjectDeleter *save = list.setDeleter(_deleteSingleID);
+
+ UnicodeSet* filter;
+ globalFilter = nullptr;
+ canonID.truncate(0);
+
+ // Parse leading global filter, if any
+ withParens = 0; // parens disallowed
+ filter = parseGlobalFilter(id, pos, dir, withParens, &canonID);
+ if (filter != nullptr) {
+ if (!ICU_Utility::parseChar(id, pos, ID_DELIM)) {
+ // Not a global filter; backup and resume
+ canonID.truncate(0);
+ pos = 0;
+ }
+ if (dir == FORWARD) {
+ globalFilter = filter;
+ } else {
+ delete filter;
+ }
+ filter = nullptr;
+ }
+
+ UBool sawDelimiter = true;
+ for (;;) {
+ SingleID* single = parseSingleID(id, pos, dir, ec);
+ if (single == nullptr) {
+ break;
+ }
+ if (dir == FORWARD) {
+ list.adoptElement(single, ec);
+ } else {
+ list.insertElementAt(single, 0, ec);
+ }
+ if (U_FAILURE(ec)) {
+ goto FAIL;
+ }
+ if (!ICU_Utility::parseChar(id, pos, ID_DELIM)) {
+ sawDelimiter = false;
+ break;
+ }
+ }
+
+ if (list.size() == 0) {
+ goto FAIL;
+ }
+
+ // Construct canonical ID
+ for (i=0; i<list.size(); ++i) {
+ SingleID* single = (SingleID*) list.elementAt(i);
+ canonID.append(single->canonID);
+ if (i != (list.size()-1)) {
+ canonID.append(ID_DELIM);
+ }
+ }
+
+ // Parse trailing global filter, if any, and only if we saw
+ // a trailing delimiter after the IDs.
+ if (sawDelimiter) {
+ withParens = 1; // parens required
+ filter = parseGlobalFilter(id, pos, dir, withParens, &canonID);
+ if (filter != nullptr) {
+ // Don't require trailing ';', but parse it if present
+ ICU_Utility::parseChar(id, pos, ID_DELIM);
+
+ if (dir == REVERSE) {
+ globalFilter = filter;
+ } else {
+ delete filter;
+ }
+ filter = nullptr;
+ }
+ }
+
+ // Trailing unparsed text is a syntax error
+ ICU_Utility::skipWhitespace(id, pos, true);
+ if (pos != id.length()) {
+ goto FAIL;
+ }
+
+ list.setDeleter(save);
+ return true;
+
+ FAIL:
+ list.removeAllElements();
+ list.setDeleter(save);
+ delete globalFilter;
+ globalFilter = nullptr;
+ return false;
+}
+
+/**
+ * Convert the elements of the 'list' vector, which are SingleID
+ * objects, into actual Transliterator objects. In the course of
+ * this, some (or all) entries may be removed. If all entries
+ * are removed, the nullptr transliterator will be added.
+ *
+ * Delete entries with empty basicIDs; these are generated by
+ * elements like "(A)" in the forward direction, or "A()" in
+ * the reverse. THIS MAY RESULT IN AN EMPTY VECTOR. Convert
+ * SingleID entries to actual transliterators.
+ *
+ * @param list vector of SingleID objects. On exit, vector
+ * of one or more Transliterators.
+ * @return new value of insertIndex. The index will shift if
+ * there are empty items, like "(Lower)", with indices less than
+ * insertIndex.
+ */
+void TransliteratorIDParser::instantiateList(UVector& list,
+ UErrorCode& ec) {
+ UVector tlist(ec);
+ if (U_FAILURE(ec)) {
+ goto RETURN;
+ }
+ tlist.setDeleter(_deleteTransliteratorTrIDPars);
+
+ Transliterator* t;
+ int32_t i;
+ for (i=0; i<=list.size(); ++i) { // [sic]: i<=list.size()
+ // We run the loop too long by one, so we can
+ // do an insert after the last element
+ if (i==list.size()) {
+ break;
+ }
+
+ SingleID* single = (SingleID*) list.elementAt(i);
+ if (single->basicID.length() != 0) {
+ t = single->createInstance();
+ if (t == nullptr) {
+ ec = U_INVALID_ID;
+ goto RETURN;
+ }
+ tlist.adoptElement(t, ec);
+ if (U_FAILURE(ec)) {
+ goto RETURN;
+ }
+ }
+ }
+
+ // An empty list is equivalent to a nullptr transliterator.
+ if (tlist.size() == 0) {
+ t = createBasicInstance(UnicodeString(true, ANY_NULL, 8), nullptr);
+ if (t == nullptr) {
+ // Should never happen
+ ec = U_INTERNAL_TRANSLITERATOR_ERROR;
+ }
+ tlist.adoptElement(t, ec);
+ }
+
+ RETURN:
+
+ UObjectDeleter *save = list.setDeleter(_deleteSingleID);
+ list.removeAllElements();
+
+ if (U_SUCCESS(ec)) {
+ list.setDeleter(_deleteTransliteratorTrIDPars);
+
+ while (tlist.size() > 0) {
+ t = (Transliterator*) tlist.orphanElementAt(0);
+ list.adoptElement(t, ec);
+ if (U_FAILURE(ec)) {
+ list.removeAllElements();
+ break;
+ }
+ }
+ }
+
+ list.setDeleter(save);
+}
+
+/**
+ * Parse an ID into pieces. Take IDs of the form T, T/V, S-T,
+ * S-T/V, or S/V-T. If the source is missing, return a source of
+ * ANY.
+ * @param id the id string, in any of several forms
+ * @return an array of 4 strings: source, target, variant, and
+ * isSourcePresent. If the source is not present, ANY will be
+ * given as the source, and isSourcePresent will be nullptr. Otherwise
+ * isSourcePresent will be non-nullptr. The target may be empty if the
+ * id is not well-formed. The variant may be empty.
+ */
+void TransliteratorIDParser::IDtoSTV(const UnicodeString& id,
+ UnicodeString& source,
+ UnicodeString& target,
+ UnicodeString& variant,
+ UBool& isSourcePresent) {
+ source.setTo(ANY, 3);
+ target.truncate(0);
+ variant.truncate(0);
+
+ int32_t sep = id.indexOf(TARGET_SEP);
+ int32_t var = id.indexOf(VARIANT_SEP);
+ if (var < 0) {
+ var = id.length();
+ }
+ isSourcePresent = false;
+
+ if (sep < 0) {
+ // Form: T/V or T (or /V)
+ id.extractBetween(0, var, target);
+ id.extractBetween(var, id.length(), variant);
+ } else if (sep < var) {
+ // Form: S-T/V or S-T (or -T/V or -T)
+ if (sep > 0) {
+ id.extractBetween(0, sep, source);
+ isSourcePresent = true;
+ }
+ id.extractBetween(++sep, var, target);
+ id.extractBetween(var, id.length(), variant);
+ } else {
+ // Form: (S/V-T or /V-T)
+ if (var > 0) {
+ id.extractBetween(0, var, source);
+ isSourcePresent = true;
+ }
+ id.extractBetween(var, sep++, variant);
+ id.extractBetween(sep, id.length(), target);
+ }
+
+ if (variant.length() > 0) {
+ variant.remove(0, 1);
+ }
+}
+
+/**
+ * Given source, target, and variant strings, concatenate them into a
+ * full ID. If the source is empty, then "Any" will be used for the
+ * source, so the ID will always be of the form s-t/v or s-t.
+ */
+void TransliteratorIDParser::STVtoID(const UnicodeString& source,
+ const UnicodeString& target,
+ const UnicodeString& variant,
+ UnicodeString& id) {
+ id = source;
+ if (id.length() == 0) {
+ id.setTo(ANY, 3);
+ }
+ id.append(TARGET_SEP).append(target);
+ if (variant.length() != 0) {
+ id.append(VARIANT_SEP).append(variant);
+ }
+ // NUL-terminate the ID string for getTerminatedBuffer.
+ // This prevents valgrind and Purify warnings.
+ id.append((char16_t)0);
+ id.truncate(id.length()-1);
+}
+
+/**
+ * Register two targets as being inverses of one another. For
+ * example, calling registerSpecialInverse("NFC", "NFD", true) causes
+ * Transliterator to form the following inverse relationships:
+ *
+ * <pre>NFC => NFD
+ * Any-NFC => Any-NFD
+ * NFD => NFC
+ * Any-NFD => Any-NFC</pre>
+ *
+ * (Without the special inverse registration, the inverse of NFC
+ * would be NFC-Any.) Note that NFD is shorthand for Any-NFD, but
+ * that the presence or absence of "Any-" is preserved.
+ *
+ * <p>The relationship is symmetrical; registering (a, b) is
+ * equivalent to registering (b, a).
+ *
+ * <p>The relevant IDs must still be registered separately as
+ * factories or classes.
+ *
+ * <p>Only the targets are specified. Special inverses always
+ * have the form Any-Target1 <=> Any-Target2. The target should
+ * have canonical casing (the casing desired to be produced when
+ * an inverse is formed) and should contain no whitespace or other
+ * extraneous characters.
+ *
+ * @param target the target against which to register the inverse
+ * @param inverseTarget the inverse of target, that is
+ * Any-target.getInverse() => Any-inverseTarget
+ * @param bidirectional if true, register the reverse relation
+ * as well, that is, Any-inverseTarget.getInverse() => Any-target
+ */
+void TransliteratorIDParser::registerSpecialInverse(const UnicodeString& target,
+ const UnicodeString& inverseTarget,
+ UBool bidirectional,
+ UErrorCode &status) {
+ umtx_initOnce(gSpecialInversesInitOnce, init, status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+
+ // If target == inverseTarget then force bidirectional => false
+ if (bidirectional && 0==target.caseCompare(inverseTarget, U_FOLD_CASE_DEFAULT)) {
+ bidirectional = false;
+ }
+
+ Mutex lock(&LOCK);
+
+ UnicodeString *tempus = new UnicodeString(inverseTarget); // Used for null pointer check before usage.
+ if (tempus == nullptr) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ SPECIAL_INVERSES->put(target, tempus, status);
+ if (bidirectional) {
+ tempus = new UnicodeString(target);
+ if (tempus == nullptr) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ SPECIAL_INVERSES->put(inverseTarget, tempus, status);
+ }
+}
+
+//----------------------------------------------------------------
+// Private implementation
+//----------------------------------------------------------------
+
+/**
+ * Parse an ID into component pieces. Take IDs of the form T,
+ * T/V, S-T, S-T/V, or S/V-T. If the source is missing, return a
+ * source of ANY.
+ * @param id the id string, in any of several forms
+ * @param pos INPUT-OUTPUT parameter. On input, pos is the
+ * offset of the first character to parse in id. On output,
+ * pos is the offset after the last parsed character. If the
+ * parse failed, pos will be unchanged.
+ * @param allowFilter2 if true, a UnicodeSet pattern is allowed
+ * at any location between specs or delimiters, and is returned
+ * as the fifth string in the array.
+ * @return a Specs object, or nullptr if the parse failed. If
+ * neither source nor target was seen in the parsed id, then the
+ * parse fails. If allowFilter is true, then the parsed filter
+ * pattern is returned in the Specs object, otherwise the returned
+ * filter reference is nullptr. If the parse fails for any reason
+ * nullptr is returned.
+ */
+TransliteratorIDParser::Specs*
+TransliteratorIDParser::parseFilterID(const UnicodeString& id, int32_t& pos,
+ UBool allowFilter) {
+ UnicodeString first;
+ UnicodeString source;
+ UnicodeString target;
+ UnicodeString variant;
+ UnicodeString filter;
+ char16_t delimiter = 0;
+ int32_t specCount = 0;
+ int32_t start = pos;
+
+ // This loop parses one of the following things with each
+ // pass: a filter, a delimiter character (either '-' or '/'),
+ // or a spec (source, target, or variant).
+ for (;;) {
+ ICU_Utility::skipWhitespace(id, pos, true);
+ if (pos == id.length()) {
+ break;
+ }
+
+ // Parse filters
+ if (allowFilter && filter.length() == 0 &&
+ UnicodeSet::resemblesPattern(id, pos)) {
+
+ ParsePosition ppos(pos);
+ UErrorCode ec = U_ZERO_ERROR;
+ UnicodeSet set(id, ppos, USET_IGNORE_SPACE, nullptr, ec);
+ if (U_FAILURE(ec)) {
+ pos = start;
+ return nullptr;
+ }
+ id.extractBetween(pos, ppos.getIndex(), filter);
+ pos = ppos.getIndex();
+ continue;
+ }
+
+ if (delimiter == 0) {
+ char16_t c = id.charAt(pos);
+ if ((c == TARGET_SEP && target.length() == 0) ||
+ (c == VARIANT_SEP && variant.length() == 0)) {
+ delimiter = c;
+ ++pos;
+ continue;
+ }
+ }
+
+ // We are about to try to parse a spec with no delimiter
+ // when we can no longer do so (we can only do so at the
+ // start); break.
+ if (delimiter == 0 && specCount > 0) {
+ break;
+ }
+
+ UnicodeString spec = ICU_Utility::parseUnicodeIdentifier(id, pos);
+ if (spec.length() == 0) {
+ // Note that if there was a trailing delimiter, we
+ // consume it. So Foo-, Foo/, Foo-Bar/, and Foo/Bar-
+ // are legal.
+ break;
+ }
+
+ switch (delimiter) {
+ case 0:
+ first = spec;
+ break;
+ case TARGET_SEP:
+ target = spec;
+ break;
+ case VARIANT_SEP:
+ variant = spec;
+ break;
+ }
+ ++specCount;
+ delimiter = 0;
+ }
+
+ // A spec with no prior character is either source or target,
+ // depending on whether an explicit "-target" was seen.
+ if (first.length() != 0) {
+ if (target.length() == 0) {
+ target = first;
+ } else {
+ source = first;
+ }
+ }
+
+ // Must have either source or target
+ if (source.length() == 0 && target.length() == 0) {
+ pos = start;
+ return nullptr;
+ }
+
+ // Empty source or target defaults to ANY
+ UBool sawSource = true;
+ if (source.length() == 0) {
+ source.setTo(ANY, 3);
+ sawSource = false;
+ }
+ if (target.length() == 0) {
+ target.setTo(ANY, 3);
+ }
+
+ return new Specs(source, target, variant, sawSource, filter);
+}
+
+/**
+ * Givens a Spec object, convert it to a SingleID object. The
+ * Spec object is a more unprocessed parse result. The SingleID
+ * object contains information about canonical and basic IDs.
+ * @return a SingleID; never returns nullptr. Returned object always
+ * has 'filter' field of nullptr.
+ */
+TransliteratorIDParser::SingleID*
+TransliteratorIDParser::specsToID(const Specs* specs, int32_t dir) {
+ UnicodeString canonID;
+ UnicodeString basicID;
+ UnicodeString basicPrefix;
+ if (specs != nullptr) {
+ UnicodeString buf;
+ if (dir == FORWARD) {
+ if (specs->sawSource) {
+ buf.append(specs->source).append(TARGET_SEP);
+ } else {
+ basicPrefix = specs->source;
+ basicPrefix.append(TARGET_SEP);
+ }
+ buf.append(specs->target);
+ } else {
+ buf.append(specs->target).append(TARGET_SEP).append(specs->source);
+ }
+ if (specs->variant.length() != 0) {
+ buf.append(VARIANT_SEP).append(specs->variant);
+ }
+ basicID = basicPrefix;
+ basicID.append(buf);
+ if (specs->filter.length() != 0) {
+ buf.insert(0, specs->filter);
+ }
+ canonID = buf;
+ }
+ return new SingleID(canonID, basicID);
+}
+
+/**
+ * Given a Specs object, return a SingleID representing the
+ * special inverse of that ID. If there is no special inverse
+ * then return nullptr.
+ * @return a SingleID or nullptr. Returned object always has
+ * 'filter' field of nullptr.
+ */
+TransliteratorIDParser::SingleID*
+TransliteratorIDParser::specsToSpecialInverse(const Specs& specs, UErrorCode &status) {
+ if (0!=specs.source.caseCompare(ANY, 3, U_FOLD_CASE_DEFAULT)) {
+ return nullptr;
+ }
+ umtx_initOnce(gSpecialInversesInitOnce, init, status);
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
+
+ UnicodeString* inverseTarget;
+
+ umtx_lock(&LOCK);
+ inverseTarget = (UnicodeString*) SPECIAL_INVERSES->get(specs.target);
+ umtx_unlock(&LOCK);
+
+ if (inverseTarget != nullptr) {
+ // If the original ID contained "Any-" then make the
+ // special inverse "Any-Foo"; otherwise make it "Foo".
+ // So "Any-NFC" => "Any-NFD" but "NFC" => "NFD".
+ UnicodeString buf;
+ if (specs.filter.length() != 0) {
+ buf.append(specs.filter);
+ }
+ if (specs.sawSource) {
+ buf.append(ANY, 3).append(TARGET_SEP);
+ }
+ buf.append(*inverseTarget);
+
+ UnicodeString basicID(true, ANY, 3);
+ basicID.append(TARGET_SEP).append(*inverseTarget);
+
+ if (specs.variant.length() != 0) {
+ buf.append(VARIANT_SEP).append(specs.variant);
+ basicID.append(VARIANT_SEP).append(specs.variant);
+ }
+ return new SingleID(buf, basicID);
+ }
+ return nullptr;
+}
+
+/**
+ * Glue method to get around access problems in C++. This would
+ * ideally be inline but we want to avoid a circular header
+ * dependency.
+ */
+Transliterator* TransliteratorIDParser::createBasicInstance(const UnicodeString& id, const UnicodeString* canonID) {
+ return Transliterator::createBasicInstance(id, canonID);
+}
+
+/**
+ * Initialize static memory. Called through umtx_initOnce only.
+ */
+void U_CALLCONV TransliteratorIDParser::init(UErrorCode &status) {
+ U_ASSERT(SPECIAL_INVERSES == nullptr);
+ ucln_i18n_registerCleanup(UCLN_I18N_TRANSLITERATOR, utrans_transliterator_cleanup);
+
+ SPECIAL_INVERSES = new Hashtable(true, status);
+ if (SPECIAL_INVERSES == nullptr) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ SPECIAL_INVERSES->setValueDeleter(uprv_deleteUObject);
+}
+
+/**
+ * Free static memory.
+ */
+void TransliteratorIDParser::cleanup() {
+ if (SPECIAL_INVERSES) {
+ delete SPECIAL_INVERSES;
+ SPECIAL_INVERSES = nullptr;
+ }
+ gSpecialInversesInitOnce.reset();
+}
+
+U_NAMESPACE_END
+
+#endif /* #if !UCONFIG_NO_TRANSLITERATION */
+
+//eof