tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

localeprioritylist.cpp (7849B)


      1 // © 2019 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 
      4 // localeprioritylist.cpp
      5 // created: 2019jul11 Markus W. Scherer
      6 
      7 #include "unicode/utypes.h"
      8 #include "unicode/localpointer.h"
      9 #include "unicode/locid.h"
     10 #include "unicode/stringpiece.h"
     11 #include "unicode/uobject.h"
     12 #include "charstr.h"
     13 #include "cmemory.h"
     14 #include "localeprioritylist.h"
     15 #include "uarrsort.h"
     16 #include "uassert.h"
     17 #include "uhash.h"
     18 
     19 U_NAMESPACE_BEGIN
     20 
     21 namespace {
     22 
     23 int32_t hashLocale(const UHashTok token) {
     24    const auto* locale = static_cast<const Locale*>(token.pointer);
     25    return locale->hashCode();
     26 }
     27 
     28 UBool compareLocales(const UHashTok t1, const UHashTok t2) {
     29    const auto* l1 = static_cast<const Locale*>(t1.pointer);
     30    const auto* l2 = static_cast<const Locale*>(t2.pointer);
     31    return *l1 == *l2;
     32 }
     33 
     34 constexpr int32_t WEIGHT_ONE = 1000;
     35 
     36 struct LocaleAndWeight {
     37    Locale *locale;
     38    int32_t weight;  // 0..1000 = 0.0..1.0
     39    int32_t index;  // force stable sort
     40 
     41    int32_t compare(const LocaleAndWeight &other) const {
     42        int32_t diff = other.weight - weight;  // descending: other-this
     43        if (diff != 0) { return diff; }
     44        return index - other.index;
     45    }
     46 };
     47 
     48 int32_t U_CALLCONV
     49 compareLocaleAndWeight(const void * /*context*/, const void *left, const void *right) {
     50    return static_cast<const LocaleAndWeight *>(left)->
     51        compare(*static_cast<const LocaleAndWeight *>(right));
     52 }
     53 
     54 const char *skipSpaces(const char *p, const char *limit) {
     55    while (p < limit && *p == ' ') { ++p; }
     56    return p;
     57 }
     58 
     59 int32_t findTagLength(const char *p, const char *limit) {
     60    // Look for accept-language delimiters.
     61    // Leave other validation up to the Locale constructor.
     62    const char *q;
     63    for (q = p; q < limit; ++q) {
     64        char c = *q;
     65        if (c == ' ' || c == ',' || c == ';') { break; }
     66    }
     67    return static_cast<int32_t>(q - p);
     68 }
     69 
     70 /**
     71 * Parses and returns a qvalue weight in millis.
     72 * Advances p to after the parsed substring.
     73 * Returns a negative value if parsing fails.
     74 */
     75 int32_t parseWeight(const char *&p, const char *limit) {
     76    p = skipSpaces(p, limit);
     77    char c;
     78    if (p == limit || ((c = *p) != '0' && c != '1')) { return -1; }
     79    int32_t weight = (c - '0') * 1000;
     80    if (++p == limit || *p != '.') { return weight; }
     81    int32_t multiplier = 100;
     82    while (++p != limit && '0' <= (c = *p) && c <= '9') {
     83        c -= '0';
     84        if (multiplier > 0) {
     85            weight += c * multiplier;
     86            multiplier /= 10;
     87        } else if (multiplier == 0) {
     88            // round up
     89            if (c >= 5) { ++weight; }
     90            multiplier = -1;
     91        }  // else ignore further fraction digits
     92    }
     93    return weight <= WEIGHT_ONE ? weight : -1;  // bad if > 1.0
     94 }
     95 
     96 }  // namespace
     97 
     98 /**
     99 * Nothing but a wrapper over a MaybeStackArray of LocaleAndWeight.
    100 *
    101 * This wrapper exists (and is not in an anonymous namespace)
    102 * so that we can forward-declare it in the header file and
    103 * don't have to expose the MaybeStackArray specialization and
    104 * the LocaleAndWeight to code (like the test) that #includes localeprioritylist.h.
    105 * Also, otherwise we would have to do a platform-specific
    106 * template export declaration of some kind for the MaybeStackArray specialization
    107 * to be properly exported from the common DLL.
    108 */
    109 struct LocaleAndWeightArray : public UMemory {
    110    MaybeStackArray<LocaleAndWeight, 20> array;
    111 };
    112 
    113 LocalePriorityList::LocalePriorityList(StringPiece s, UErrorCode &errorCode) {
    114    if (U_FAILURE(errorCode)) { return; }
    115    list = new LocaleAndWeightArray();
    116    if (list == nullptr) {
    117        errorCode = U_MEMORY_ALLOCATION_ERROR;
    118        return;
    119    }
    120    const char *p = s.data();
    121    const char *limit = p + s.length();
    122    while ((p = skipSpaces(p, limit)) != limit) {
    123        if (*p == ',') {  // empty range field
    124            ++p;
    125            continue;
    126        }
    127        int32_t tagLength = findTagLength(p, limit);
    128        if (tagLength == 0) {
    129            errorCode = U_ILLEGAL_ARGUMENT_ERROR;
    130            return;
    131        }
    132        CharString tag(p, tagLength, errorCode);
    133        if (U_FAILURE(errorCode)) { return; }
    134        Locale locale = Locale(tag.data());
    135        if (locale.isBogus()) {
    136            errorCode = U_ILLEGAL_ARGUMENT_ERROR;
    137            return;
    138        }
    139        int32_t weight = WEIGHT_ONE;
    140        if ((p = skipSpaces(p + tagLength, limit)) != limit && *p == ';') {
    141            if ((p = skipSpaces(p + 1, limit)) == limit || *p != 'q' ||
    142                    (p = skipSpaces(p + 1, limit)) == limit || *p != '=' ||
    143                    (++p, (weight = parseWeight(p, limit)) < 0)) {
    144                errorCode = U_ILLEGAL_ARGUMENT_ERROR;
    145                return;
    146            }
    147            p = skipSpaces(p, limit);
    148        }
    149        if (p != limit && *p != ',') {  // trailing junk
    150            errorCode = U_ILLEGAL_ARGUMENT_ERROR;
    151            return;
    152        }
    153        add(locale, weight, errorCode);
    154        if (p == limit) { break; }
    155        ++p;
    156    }
    157    sort(errorCode);
    158 }
    159 
    160 LocalePriorityList::~LocalePriorityList() {
    161    if (list != nullptr) {
    162        for (int32_t i = 0; i < listLength; ++i) {
    163            delete list->array[i].locale;
    164        }
    165        delete list;
    166    }
    167    uhash_close(map);
    168 }
    169 
    170 const Locale *LocalePriorityList::localeAt(int32_t i) const {
    171    return list->array[i].locale;
    172 }
    173 
    174 Locale *LocalePriorityList::orphanLocaleAt(int32_t i) {
    175    if (list == nullptr) { return nullptr; }
    176    LocaleAndWeight &lw = list->array[i];
    177    Locale *l = lw.locale;
    178    lw.locale = nullptr;
    179    return l;
    180 }
    181 
    182 bool LocalePriorityList::add(const Locale &locale, int32_t weight, UErrorCode &errorCode) {
    183    if (U_FAILURE(errorCode)) { return false; }
    184    if (map == nullptr) {
    185        if (weight <= 0) { return true; }  // do not add q=0
    186        map = uhash_open(hashLocale, compareLocales, uhash_compareLong, &errorCode);
    187        if (U_FAILURE(errorCode)) { return false; }
    188    }
    189    LocalPointer<Locale> clone;
    190    UBool found = false;
    191    int32_t index = uhash_getiAndFound(map, &locale, &found);
    192    if (found) {
    193        // Duplicate: Remove the old item and append it anew.
    194        LocaleAndWeight &lw = list->array[index];
    195        clone.adoptInstead(lw.locale);
    196        lw.locale = nullptr;
    197        lw.weight = 0;
    198        ++numRemoved;
    199    }
    200    if (weight <= 0) {  // do not add q=0
    201        if (found) {
    202            // Not strictly necessary but cleaner.
    203            uhash_removei(map, &locale);
    204        }
    205        return true;
    206    }
    207    if (clone.isNull()) {
    208        clone.adoptInstead(locale.clone());
    209        if (clone.isNull() || (clone->isBogus() && !locale.isBogus())) {
    210            errorCode = U_MEMORY_ALLOCATION_ERROR;
    211            return false;
    212        }
    213    }
    214    if (listLength == list->array.getCapacity()) {
    215        int32_t newCapacity = listLength < 50 ? 100 : 4 * listLength;
    216        if (list->array.resize(newCapacity, listLength) == nullptr) {
    217            errorCode = U_MEMORY_ALLOCATION_ERROR;
    218            return false;
    219        }
    220    }
    221    uhash_putiAllowZero(map, clone.getAlias(), listLength, &errorCode);
    222    if (U_FAILURE(errorCode)) { return false; }
    223    LocaleAndWeight &lw = list->array[listLength];
    224    lw.locale = clone.orphan();
    225    lw.weight = weight;
    226    lw.index = listLength++;
    227    if (weight < WEIGHT_ONE) { hasWeights = true; }
    228    U_ASSERT(uhash_count(map) == getLength());
    229    return true;
    230 }
    231 
    232 void LocalePriorityList::sort(UErrorCode &errorCode) {
    233    // Sort by descending weights if there is a mix of weights.
    234    // The comparator forces a stable sort via the item index.
    235    if (U_FAILURE(errorCode) || getLength() <= 1 || !hasWeights) { return; }
    236    uprv_sortArray(list->array.getAlias(), listLength, sizeof(LocaleAndWeight),
    237                   compareLocaleAndWeight, nullptr, false, &errorCode);
    238 }
    239 
    240 U_NAMESPACE_END