| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * |
| * Copyright (C) 2000-2015, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| * |
| ******************************************************************************* |
| * |
| * File reslist.cpp |
| * |
| * Modification History: |
| * |
| * Date Name Description |
| * 02/21/00 weiv Creation. |
| ******************************************************************************* |
| */ |
| |
| // Safer use of UnicodeString. |
| #ifndef UNISTR_FROM_CHAR_EXPLICIT |
| # define UNISTR_FROM_CHAR_EXPLICIT explicit |
| #endif |
| |
| // Less important, but still a good idea. |
| #ifndef UNISTR_FROM_STRING_EXPLICIT |
| # define UNISTR_FROM_STRING_EXPLICIT explicit |
| #endif |
| |
| #include <assert.h> |
| #include <iostream> |
| #include <set> |
| #include <stdio.h> |
| |
| #include "unicode/localpointer.h" |
| #include "reslist.h" |
| #include "unewdata.h" |
| #include "unicode/ures.h" |
| #include "unicode/putil.h" |
| #include "errmsg.h" |
| #include "filterrb.h" |
| #include "toolutil.h" |
| |
| #include "uarrsort.h" |
| #include "uelement.h" |
| #include "uhash.h" |
| #include "uinvchar.h" |
| #include "ustr_imp.h" |
| #include "unicode/utf16.h" |
| #include "uassert.h" |
| |
| /* |
| * Align binary data at a 16-byte offset from the start of the resource bundle, |
| * to be safe for any data type it may contain. |
| */ |
| #define BIN_ALIGNMENT 16 |
| |
| // This numeric constant must be at least 1. |
| // If StringResource.fNumUnitsSaved == 0 then the string occurs only once, |
| // and it makes no sense to move it to the pool bundle. |
| // The larger the threshold for fNumUnitsSaved |
| // the smaller the savings, and the smaller the pool bundle. |
| // We trade some total size reduction to reduce the pool bundle a bit, |
| // so that one can reasonably save data size by |
| // removing bundle files without rebuilding the pool bundle. |
| // This can also help to keep the pool and total (pool+local) string indexes |
| // within 16 bits, that is, within range of Table16 and Array16 containers. |
| #ifndef GENRB_MIN_16BIT_UNITS_SAVED_FOR_POOL_STRING |
| # define GENRB_MIN_16BIT_UNITS_SAVED_FOR_POOL_STRING 10 |
| #endif |
| |
| U_NAMESPACE_USE |
| |
| static UBool gIncludeCopyright = FALSE; |
| static UBool gUsePoolBundle = FALSE; |
| static UBool gIsDefaultFormatVersion = TRUE; |
| static int32_t gFormatVersion = 3; |
| |
| /* How do we store string values? */ |
| enum { |
| STRINGS_UTF16_V1, /* formatVersion 1: int length + UChars + NUL + padding to 4 bytes */ |
| STRINGS_UTF16_V2 /* formatVersion 2 & up: optional length in 1..3 UChars + UChars + NUL */ |
| }; |
| |
| static const int32_t MAX_IMPLICIT_STRING_LENGTH = 40; /* do not store the length explicitly for such strings */ |
| |
| static const ResFile kNoPoolBundle; |
| |
| /* |
| * res_none() returns the address of kNoResource, |
| * for use in non-error cases when no resource is to be added to the bundle. |
| * (NULL is used in error cases.) |
| */ |
| static SResource kNoResource; // TODO: const |
| |
| static UDataInfo dataInfo= { |
| sizeof(UDataInfo), |
| 0, |
| |
| U_IS_BIG_ENDIAN, |
| U_CHARSET_FAMILY, |
| sizeof(UChar), |
| 0, |
| |
| {0x52, 0x65, 0x73, 0x42}, /* dataFormat="ResB" */ |
| {1, 3, 0, 0}, /* formatVersion */ |
| {1, 4, 0, 0} /* dataVersion take a look at version inside parsed resb*/ |
| }; |
| |
| static const UVersionInfo gFormatVersions[4] = { /* indexed by a major-formatVersion integer */ |
| { 0, 0, 0, 0 }, |
| { 1, 3, 0, 0 }, |
| { 2, 0, 0, 0 }, |
| { 3, 0, 0, 0 } |
| }; |
| // Remember to update genrb.h GENRB_VERSION when changing the data format. |
| // (Or maybe we should remove GENRB_VERSION and report the ICU version number?) |
| |
| static uint8_t calcPadding(uint32_t size) { |
| /* returns space we need to pad */ |
| return (uint8_t) ((size % sizeof(uint32_t)) ? (sizeof(uint32_t) - (size % sizeof(uint32_t))) : 0); |
| |
| } |
| |
| void setIncludeCopyright(UBool val){ |
| gIncludeCopyright=val; |
| } |
| |
| UBool getIncludeCopyright(void){ |
| return gIncludeCopyright; |
| } |
| |
| void setFormatVersion(int32_t formatVersion) { |
| gIsDefaultFormatVersion = FALSE; |
| gFormatVersion = formatVersion; |
| } |
| |
| int32_t getFormatVersion() { |
| return gFormatVersion; |
| } |
| |
| void setUsePoolBundle(UBool use) { |
| gUsePoolBundle = use; |
| } |
| |
| // TODO: return const pointer, or find another way to express "none" |
| struct SResource* res_none() { |
| return &kNoResource; |
| } |
| |
| SResource::SResource() |
| : fType(URES_NONE), fWritten(FALSE), fRes(RES_BOGUS), fRes16(-1), fKey(-1), fKey16(-1), |
| line(0), fNext(NULL) { |
| ustr_init(&fComment); |
| } |
| |
| SResource::SResource(SRBRoot *bundle, const char *tag, int8_t type, const UString* comment, |
| UErrorCode &errorCode) |
| : fType(type), fWritten(FALSE), fRes(RES_BOGUS), fRes16(-1), |
| fKey(bundle != NULL ? bundle->addTag(tag, errorCode) : -1), fKey16(-1), |
| line(0), fNext(NULL) { |
| ustr_init(&fComment); |
| if(comment != NULL) { |
| ustr_cpy(&fComment, comment, &errorCode); |
| } |
| } |
| |
| SResource::~SResource() { |
| ustr_deinit(&fComment); |
| } |
| |
| ContainerResource::~ContainerResource() { |
| SResource *current = fFirst; |
| while (current != NULL) { |
| SResource *next = current->fNext; |
| delete current; |
| current = next; |
| } |
| } |
| |
| TableResource::~TableResource() {} |
| |
| // TODO: clarify that containers adopt new items, even in error cases; use LocalPointer |
| void TableResource::add(SResource *res, int linenumber, UErrorCode &errorCode) { |
| if (U_FAILURE(errorCode) || res == NULL || res == &kNoResource) { |
| return; |
| } |
| |
| /* remember this linenumber to report to the user if there is a duplicate key */ |
| res->line = linenumber; |
| |
| /* here we need to traverse the list */ |
| ++fCount; |
| |
| /* is the list still empty? */ |
| if (fFirst == NULL) { |
| fFirst = res; |
| res->fNext = NULL; |
| return; |
| } |
| |
| const char *resKeyString = fRoot->fKeys + res->fKey; |
| |
| SResource *current = fFirst; |
| |
| SResource *prev = NULL; |
| while (current != NULL) { |
| const char *currentKeyString = fRoot->fKeys + current->fKey; |
| int diff; |
| /* |
| * formatVersion 1: compare key strings in native-charset order |
| * formatVersion 2 and up: compare key strings in ASCII order |
| */ |
| if (gFormatVersion == 1 || U_CHARSET_FAMILY == U_ASCII_FAMILY) { |
| diff = uprv_strcmp(currentKeyString, resKeyString); |
| } else { |
| diff = uprv_compareInvCharsAsAscii(currentKeyString, resKeyString); |
| } |
| if (diff < 0) { |
| prev = current; |
| current = current->fNext; |
| } else if (diff > 0) { |
| /* we're either in front of the list, or in the middle */ |
| if (prev == NULL) { |
| /* front of the list */ |
| fFirst = res; |
| } else { |
| /* middle of the list */ |
| prev->fNext = res; |
| } |
| |
| res->fNext = current; |
| return; |
| } else { |
| /* Key already exists! ERROR! */ |
| error(linenumber, "duplicate key '%s' in table, first appeared at line %d", currentKeyString, current->line); |
| errorCode = U_UNSUPPORTED_ERROR; |
| return; |
| } |
| } |
| |
| /* end of list */ |
| prev->fNext = res; |
| res->fNext = NULL; |
| } |
| |
| ArrayResource::~ArrayResource() {} |
| |
| void ArrayResource::add(SResource *res) { |
| if (res != NULL && res != &kNoResource) { |
| if (fFirst == NULL) { |
| fFirst = res; |
| } else { |
| fLast->fNext = res; |
| } |
| fLast = res; |
| ++fCount; |
| } |
| } |
| |
| PseudoListResource::~PseudoListResource() {} |
| |
| void PseudoListResource::add(SResource *res) { |
| if (res != NULL && res != &kNoResource) { |
| res->fNext = fFirst; |
| fFirst = res; |
| ++fCount; |
| } |
| } |
| |
| StringBaseResource::StringBaseResource(SRBRoot *bundle, const char *tag, int8_t type, |
| const UChar *value, int32_t len, |
| const UString* comment, UErrorCode &errorCode) |
| : SResource(bundle, tag, type, comment, errorCode) { |
| if (len == 0 && gFormatVersion > 1) { |
| fRes = URES_MAKE_EMPTY_RESOURCE(type); |
| fWritten = TRUE; |
| return; |
| } |
| |
| fString.setTo(ConstChar16Ptr(value), len); |
| fString.getTerminatedBuffer(); // Some code relies on NUL-termination. |
| if (U_SUCCESS(errorCode) && fString.isBogus()) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| } |
| } |
| |
| StringBaseResource::StringBaseResource(SRBRoot *bundle, int8_t type, |
| const icu::UnicodeString &value, UErrorCode &errorCode) |
| : SResource(bundle, NULL, type, NULL, errorCode), fString(value) { |
| if (value.isEmpty() && gFormatVersion > 1) { |
| fRes = URES_MAKE_EMPTY_RESOURCE(type); |
| fWritten = TRUE; |
| return; |
| } |
| |
| fString.getTerminatedBuffer(); // Some code relies on NUL-termination. |
| if (U_SUCCESS(errorCode) && fString.isBogus()) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| } |
| } |
| |
| // Pool bundle string, alias the buffer. Guaranteed NUL-terminated and not empty. |
| StringBaseResource::StringBaseResource(int8_t type, const UChar *value, int32_t len, |
| UErrorCode &errorCode) |
| : SResource(NULL, NULL, type, NULL, errorCode), fString(TRUE, value, len) { |
| assert(len > 0); |
| assert(!fString.isBogus()); |
| } |
| |
| StringBaseResource::~StringBaseResource() {} |
| |
| static int32_t U_CALLCONV |
| string_hash(const UElement key) { |
| const StringResource *res = static_cast<const StringResource *>(key.pointer); |
| return res->fString.hashCode(); |
| } |
| |
| static UBool U_CALLCONV |
| string_comp(const UElement key1, const UElement key2) { |
| const StringResource *res1 = static_cast<const StringResource *>(key1.pointer); |
| const StringResource *res2 = static_cast<const StringResource *>(key2.pointer); |
| return res1->fString == res2->fString; |
| } |
| |
| StringResource::~StringResource() {} |
| |
| AliasResource::~AliasResource() {} |
| |
| IntResource::IntResource(SRBRoot *bundle, const char *tag, int32_t value, |
| const UString* comment, UErrorCode &errorCode) |
| : SResource(bundle, tag, URES_INT, comment, errorCode) { |
| fValue = value; |
| fRes = URES_MAKE_RESOURCE(URES_INT, value & RES_MAX_OFFSET); |
| fWritten = TRUE; |
| } |
| |
| IntResource::~IntResource() {} |
| |
| IntVectorResource::IntVectorResource(SRBRoot *bundle, const char *tag, |
| const UString* comment, UErrorCode &errorCode) |
| : SResource(bundle, tag, URES_INT_VECTOR, comment, errorCode), |
| fCount(0), fArray(new uint32_t[RESLIST_MAX_INT_VECTOR]) { |
| if (fArray == NULL) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| } |
| |
| IntVectorResource::~IntVectorResource() { |
| delete[] fArray; |
| } |
| |
| void IntVectorResource::add(int32_t value, UErrorCode &errorCode) { |
| if (U_SUCCESS(errorCode)) { |
| fArray[fCount++] = value; |
| } |
| } |
| |
| BinaryResource::BinaryResource(SRBRoot *bundle, const char *tag, |
| uint32_t length, uint8_t *data, const char* fileName, |
| const UString* comment, UErrorCode &errorCode) |
| : SResource(bundle, tag, URES_BINARY, comment, errorCode), |
| fLength(length), fData(NULL), fFileName(NULL) { |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| if (fileName != NULL && *fileName != 0){ |
| fFileName = new char[uprv_strlen(fileName)+1]; |
| if (fFileName == NULL) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| uprv_strcpy(fFileName, fileName); |
| } |
| if (length > 0) { |
| fData = new uint8_t[length]; |
| if (fData == NULL) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| uprv_memcpy(fData, data, length); |
| } else { |
| if (gFormatVersion > 1) { |
| fRes = URES_MAKE_EMPTY_RESOURCE(URES_BINARY); |
| fWritten = TRUE; |
| } |
| } |
| } |
| |
| BinaryResource::~BinaryResource() { |
| delete[] fData; |
| delete[] fFileName; |
| } |
| |
| /* Writing Functions */ |
| |
| void |
| StringResource::handlePreflightStrings(SRBRoot *bundle, UHashtable *stringSet, |
| UErrorCode &errorCode) { |
| assert(fSame == NULL); |
| fSame = static_cast<StringResource *>(uhash_get(stringSet, this)); |
| if (fSame != NULL) { |
| // This is a duplicate of a pool bundle string or of an earlier-visited string. |
| if (++fSame->fNumCopies == 1) { |
| assert(fSame->fWritten); |
| int32_t poolStringIndex = (int32_t)RES_GET_OFFSET(fSame->fRes); |
| if (poolStringIndex >= bundle->fPoolStringIndexLimit) { |
| bundle->fPoolStringIndexLimit = poolStringIndex + 1; |
| } |
| } |
| return; |
| } |
| /* Put this string into the set for finding duplicates. */ |
| fNumCopies = 1; |
| uhash_put(stringSet, this, this, &errorCode); |
| |
| if (bundle->fStringsForm != STRINGS_UTF16_V1) { |
| int32_t len = length(); |
| if (len <= MAX_IMPLICIT_STRING_LENGTH && |
| !U16_IS_TRAIL(fString[0]) && fString.indexOf((UChar)0) < 0) { |
| /* |
| * This string will be stored without an explicit length. |
| * Runtime will detect !U16_IS_TRAIL(s[0]) and call u_strlen(). |
| */ |
| fNumCharsForLength = 0; |
| } else if (len <= 0x3ee) { |
| fNumCharsForLength = 1; |
| } else if (len <= 0xfffff) { |
| fNumCharsForLength = 2; |
| } else { |
| fNumCharsForLength = 3; |
| } |
| bundle->f16BitStringsLength += fNumCharsForLength + len + 1; /* +1 for the NUL */ |
| } |
| } |
| |
| void |
| ContainerResource::handlePreflightStrings(SRBRoot *bundle, UHashtable *stringSet, |
| UErrorCode &errorCode) { |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| current->preflightStrings(bundle, stringSet, errorCode); |
| } |
| } |
| |
| void |
| SResource::preflightStrings(SRBRoot *bundle, UHashtable *stringSet, UErrorCode &errorCode) { |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| if (fRes != RES_BOGUS) { |
| /* |
| * The resource item word was already precomputed, which means |
| * no further data needs to be written. |
| * This might be an integer, or an empty string/binary/etc. |
| */ |
| return; |
| } |
| handlePreflightStrings(bundle, stringSet, errorCode); |
| } |
| |
| void |
| SResource::handlePreflightStrings(SRBRoot * /*bundle*/, UHashtable * /*stringSet*/, |
| UErrorCode & /*errorCode*/) { |
| /* Neither a string nor a container. */ |
| } |
| |
| int32_t |
| SRBRoot::makeRes16(uint32_t resWord) const { |
| if (resWord == 0) { |
| return 0; /* empty string */ |
| } |
| uint32_t type = RES_GET_TYPE(resWord); |
| int32_t offset = (int32_t)RES_GET_OFFSET(resWord); |
| if (type == URES_STRING_V2) { |
| assert(offset > 0); |
| if (offset < fPoolStringIndexLimit) { |
| if (offset < fPoolStringIndex16Limit) { |
| return offset; |
| } |
| } else { |
| offset = offset - fPoolStringIndexLimit + fPoolStringIndex16Limit; |
| if (offset <= 0xffff) { |
| return offset; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| int32_t |
| SRBRoot::mapKey(int32_t oldpos) const { |
| const KeyMapEntry *map = fKeyMap; |
| if (map == NULL) { |
| return oldpos; |
| } |
| int32_t i, start, limit; |
| |
| /* do a binary search for the old, pre-compactKeys() key offset */ |
| start = fUsePoolBundle->fKeysCount; |
| limit = start + fKeysCount; |
| while (start < limit - 1) { |
| i = (start + limit) / 2; |
| if (oldpos < map[i].oldpos) { |
| limit = i; |
| } else { |
| start = i; |
| } |
| } |
| assert(oldpos == map[start].oldpos); |
| return map[start].newpos; |
| } |
| |
| /* |
| * Only called for UTF-16 v1 strings and duplicate UTF-16 v2 strings. |
| * For unique UTF-16 v2 strings, write16() sees fRes != RES_BOGUS |
| * and exits early. |
| */ |
| void |
| StringResource::handleWrite16(SRBRoot * /*bundle*/) { |
| SResource *same; |
| if ((same = fSame) != NULL) { |
| /* This is a duplicate. */ |
| assert(same->fRes != RES_BOGUS && same->fWritten); |
| fRes = same->fRes; |
| fWritten = same->fWritten; |
| } |
| } |
| |
| void |
| ContainerResource::writeAllRes16(SRBRoot *bundle) { |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| bundle->f16BitUnits.append((UChar)current->fRes16); |
| } |
| fWritten = TRUE; |
| } |
| |
| void |
| ArrayResource::handleWrite16(SRBRoot *bundle) { |
| if (fCount == 0 && gFormatVersion > 1) { |
| fRes = URES_MAKE_EMPTY_RESOURCE(URES_ARRAY); |
| fWritten = TRUE; |
| return; |
| } |
| |
| int32_t res16 = 0; |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| current->write16(bundle); |
| res16 |= current->fRes16; |
| } |
| if (fCount <= 0xffff && res16 >= 0 && gFormatVersion > 1) { |
| fRes = URES_MAKE_RESOURCE(URES_ARRAY16, bundle->f16BitUnits.length()); |
| bundle->f16BitUnits.append((UChar)fCount); |
| writeAllRes16(bundle); |
| } |
| } |
| |
| void |
| TableResource::handleWrite16(SRBRoot *bundle) { |
| if (fCount == 0 && gFormatVersion > 1) { |
| fRes = URES_MAKE_EMPTY_RESOURCE(URES_TABLE); |
| fWritten = TRUE; |
| return; |
| } |
| /* Find the smallest table type that fits the data. */ |
| int32_t key16 = 0; |
| int32_t res16 = 0; |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| current->write16(bundle); |
| key16 |= current->fKey16; |
| res16 |= current->fRes16; |
| } |
| if(fCount > (uint32_t)bundle->fMaxTableLength) { |
| bundle->fMaxTableLength = fCount; |
| } |
| if (fCount <= 0xffff && key16 >= 0) { |
| if (res16 >= 0 && gFormatVersion > 1) { |
| /* 16-bit count, key offsets and values */ |
| fRes = URES_MAKE_RESOURCE(URES_TABLE16, bundle->f16BitUnits.length()); |
| bundle->f16BitUnits.append((UChar)fCount); |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| bundle->f16BitUnits.append((UChar)current->fKey16); |
| } |
| writeAllRes16(bundle); |
| } else { |
| /* 16-bit count, 16-bit key offsets, 32-bit values */ |
| fTableType = URES_TABLE; |
| } |
| } else { |
| /* 32-bit count, key offsets and values */ |
| fTableType = URES_TABLE32; |
| } |
| } |
| |
| void |
| PseudoListResource::handleWrite16(SRBRoot * /*bundle*/) { |
| fRes = URES_MAKE_EMPTY_RESOURCE(URES_TABLE); |
| fWritten = TRUE; |
| } |
| |
| void |
| SResource::write16(SRBRoot *bundle) { |
| if (fKey >= 0) { |
| // A tagged resource has a non-negative key index into the parsed key strings. |
| // compactKeys() built a map from parsed key index to the final key index. |
| // After the mapping, negative key indexes are used for shared pool bundle keys. |
| fKey = bundle->mapKey(fKey); |
| // If the key index fits into a Key16 for a Table or Table16, |
| // then set the fKey16 field accordingly. |
| // Otherwise keep it at -1. |
| if (fKey >= 0) { |
| if (fKey < bundle->fLocalKeyLimit) { |
| fKey16 = fKey; |
| } |
| } else { |
| int32_t poolKeyIndex = fKey & 0x7fffffff; |
| if (poolKeyIndex <= 0xffff) { |
| poolKeyIndex += bundle->fLocalKeyLimit; |
| if (poolKeyIndex <= 0xffff) { |
| fKey16 = poolKeyIndex; |
| } |
| } |
| } |
| } |
| /* |
| * fRes != RES_BOGUS: |
| * The resource item word was already precomputed, which means |
| * no further data needs to be written. |
| * This might be an integer, or an empty or UTF-16 v2 string, |
| * an empty binary, etc. |
| */ |
| if (fRes == RES_BOGUS) { |
| handleWrite16(bundle); |
| } |
| // Compute fRes16 for precomputed as well as just-computed fRes. |
| fRes16 = bundle->makeRes16(fRes); |
| } |
| |
| void |
| SResource::handleWrite16(SRBRoot * /*bundle*/) { |
| /* Only a few resource types write 16-bit units. */ |
| } |
| |
| /* |
| * Only called for UTF-16 v1 strings, and for aliases. |
| * For UTF-16 v2 strings, preWrite() sees fRes != RES_BOGUS |
| * and exits early. |
| */ |
| void |
| StringBaseResource::handlePreWrite(uint32_t *byteOffset) { |
| /* Write the UTF-16 v1 string. */ |
| fRes = URES_MAKE_RESOURCE(fType, *byteOffset >> 2); |
| *byteOffset += 4 + (length() + 1) * U_SIZEOF_UCHAR; |
| } |
| |
| void |
| IntVectorResource::handlePreWrite(uint32_t *byteOffset) { |
| if (fCount == 0 && gFormatVersion > 1) { |
| fRes = URES_MAKE_EMPTY_RESOURCE(URES_INT_VECTOR); |
| fWritten = TRUE; |
| } else { |
| fRes = URES_MAKE_RESOURCE(URES_INT_VECTOR, *byteOffset >> 2); |
| *byteOffset += (1 + fCount) * 4; |
| } |
| } |
| |
| void |
| BinaryResource::handlePreWrite(uint32_t *byteOffset) { |
| uint32_t pad = 0; |
| uint32_t dataStart = *byteOffset + sizeof(fLength); |
| |
| if (dataStart % BIN_ALIGNMENT) { |
| pad = (BIN_ALIGNMENT - dataStart % BIN_ALIGNMENT); |
| *byteOffset += pad; /* pad == 4 or 8 or 12 */ |
| } |
| fRes = URES_MAKE_RESOURCE(URES_BINARY, *byteOffset >> 2); |
| *byteOffset += 4 + fLength; |
| } |
| |
| void |
| ContainerResource::preWriteAllRes(uint32_t *byteOffset) { |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| current->preWrite(byteOffset); |
| } |
| } |
| |
| void |
| ArrayResource::handlePreWrite(uint32_t *byteOffset) { |
| preWriteAllRes(byteOffset); |
| fRes = URES_MAKE_RESOURCE(URES_ARRAY, *byteOffset >> 2); |
| *byteOffset += (1 + fCount) * 4; |
| } |
| |
| void |
| TableResource::handlePreWrite(uint32_t *byteOffset) { |
| preWriteAllRes(byteOffset); |
| if (fTableType == URES_TABLE) { |
| /* 16-bit count, 16-bit key offsets, 32-bit values */ |
| fRes = URES_MAKE_RESOURCE(URES_TABLE, *byteOffset >> 2); |
| *byteOffset += 2 + fCount * 6; |
| } else { |
| /* 32-bit count, key offsets and values */ |
| fRes = URES_MAKE_RESOURCE(URES_TABLE32, *byteOffset >> 2); |
| *byteOffset += 4 + fCount * 8; |
| } |
| } |
| |
| void |
| SResource::preWrite(uint32_t *byteOffset) { |
| if (fRes != RES_BOGUS) { |
| /* |
| * The resource item word was already precomputed, which means |
| * no further data needs to be written. |
| * This might be an integer, or an empty or UTF-16 v2 string, |
| * an empty binary, etc. |
| */ |
| return; |
| } |
| handlePreWrite(byteOffset); |
| *byteOffset += calcPadding(*byteOffset); |
| } |
| |
| void |
| SResource::handlePreWrite(uint32_t * /*byteOffset*/) { |
| assert(FALSE); |
| } |
| |
| /* |
| * Only called for UTF-16 v1 strings, and for aliases. For UTF-16 v2 strings, |
| * write() sees fWritten and exits early. |
| */ |
| void |
| StringBaseResource::handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) { |
| /* Write the UTF-16 v1 string. */ |
| int32_t len = length(); |
| udata_write32(mem, len); |
| udata_writeUString(mem, getBuffer(), len + 1); |
| *byteOffset += 4 + (len + 1) * U_SIZEOF_UCHAR; |
| fWritten = TRUE; |
| } |
| |
| void |
| ContainerResource::writeAllRes(UNewDataMemory *mem, uint32_t *byteOffset) { |
| uint32_t i = 0; |
| for (SResource *current = fFirst; current != NULL; ++i, current = current->fNext) { |
| current->write(mem, byteOffset); |
| } |
| assert(i == fCount); |
| } |
| |
| void |
| ContainerResource::writeAllRes32(UNewDataMemory *mem, uint32_t *byteOffset) { |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| udata_write32(mem, current->fRes); |
| } |
| *byteOffset += fCount * 4; |
| } |
| |
| void |
| ArrayResource::handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) { |
| writeAllRes(mem, byteOffset); |
| udata_write32(mem, fCount); |
| *byteOffset += 4; |
| writeAllRes32(mem, byteOffset); |
| } |
| |
| void |
| IntVectorResource::handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) { |
| udata_write32(mem, fCount); |
| for(uint32_t i = 0; i < fCount; ++i) { |
| udata_write32(mem, fArray[i]); |
| } |
| *byteOffset += (1 + fCount) * 4; |
| } |
| |
| void |
| BinaryResource::handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) { |
| uint32_t pad = 0; |
| uint32_t dataStart = *byteOffset + sizeof(fLength); |
| |
| if (dataStart % BIN_ALIGNMENT) { |
| pad = (BIN_ALIGNMENT - dataStart % BIN_ALIGNMENT); |
| udata_writePadding(mem, pad); /* pad == 4 or 8 or 12 */ |
| *byteOffset += pad; |
| } |
| |
| udata_write32(mem, fLength); |
| if (fLength > 0) { |
| udata_writeBlock(mem, fData, fLength); |
| } |
| *byteOffset += 4 + fLength; |
| } |
| |
| void |
| TableResource::handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) { |
| writeAllRes(mem, byteOffset); |
| if(fTableType == URES_TABLE) { |
| udata_write16(mem, (uint16_t)fCount); |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| udata_write16(mem, current->fKey16); |
| } |
| *byteOffset += (1 + fCount)* 2; |
| if ((fCount & 1) == 0) { |
| /* 16-bit count and even number of 16-bit key offsets need padding before 32-bit resource items */ |
| udata_writePadding(mem, 2); |
| *byteOffset += 2; |
| } |
| } else /* URES_TABLE32 */ { |
| udata_write32(mem, fCount); |
| for (SResource *current = fFirst; current != NULL; current = current->fNext) { |
| udata_write32(mem, (uint32_t)current->fKey); |
| } |
| *byteOffset += (1 + fCount)* 4; |
| } |
| writeAllRes32(mem, byteOffset); |
| } |
| |
| void |
| SResource::write(UNewDataMemory *mem, uint32_t *byteOffset) { |
| if (fWritten) { |
| assert(fRes != RES_BOGUS); |
| return; |
| } |
| handleWrite(mem, byteOffset); |
| uint8_t paddingSize = calcPadding(*byteOffset); |
| if (paddingSize > 0) { |
| udata_writePadding(mem, paddingSize); |
| *byteOffset += paddingSize; |
| } |
| fWritten = TRUE; |
| } |
| |
| void |
| SResource::handleWrite(UNewDataMemory * /*mem*/, uint32_t * /*byteOffset*/) { |
| assert(FALSE); |
| } |
| |
| void SRBRoot::write(const char *outputDir, const char *outputPkg, |
| char *writtenFilename, int writtenFilenameLen, |
| UErrorCode &errorCode) { |
| UNewDataMemory *mem = NULL; |
| uint32_t byteOffset = 0; |
| uint32_t top, size; |
| char dataName[1024]; |
| int32_t indexes[URES_INDEX_TOP]; |
| |
| compactKeys(errorCode); |
| /* |
| * Add padding bytes to fKeys so that fKeysTop is 4-aligned. |
| * Safe because the capacity is a multiple of 4. |
| */ |
| while (fKeysTop & 3) { |
| fKeys[fKeysTop++] = (char)0xaa; |
| } |
| /* |
| * In URES_TABLE, use all local key offsets that fit into 16 bits, |
| * and use the remaining 16-bit offsets for pool key offsets |
| * if there are any. |
| * If there are no local keys, then use the whole 16-bit space |
| * for pool key offsets. |
| * Note: This cannot be changed without changing the major formatVersion. |
| */ |
| if (fKeysBottom < fKeysTop) { |
| if (fKeysTop <= 0x10000) { |
| fLocalKeyLimit = fKeysTop; |
| } else { |
| fLocalKeyLimit = 0x10000; |
| } |
| } else { |
| fLocalKeyLimit = 0; |
| } |
| |
| UHashtable *stringSet; |
| if (gFormatVersion > 1) { |
| stringSet = uhash_open(string_hash, string_comp, string_comp, &errorCode); |
| if (U_SUCCESS(errorCode) && |
| fUsePoolBundle != NULL && fUsePoolBundle->fStrings != NULL) { |
| for (SResource *current = fUsePoolBundle->fStrings->fFirst; |
| current != NULL; |
| current = current->fNext) { |
| StringResource *sr = static_cast<StringResource *>(current); |
| sr->fNumCopies = 0; |
| sr->fNumUnitsSaved = 0; |
| uhash_put(stringSet, sr, sr, &errorCode); |
| } |
| } |
| fRoot->preflightStrings(this, stringSet, errorCode); |
| } else { |
| stringSet = NULL; |
| } |
| if (fStringsForm == STRINGS_UTF16_V2 && f16BitStringsLength > 0) { |
| compactStringsV2(stringSet, errorCode); |
| } |
| uhash_close(stringSet); |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| |
| int32_t formatVersion = gFormatVersion; |
| if (fPoolStringIndexLimit != 0) { |
| int32_t sum = fPoolStringIndexLimit + fLocalStringIndexLimit; |
| if ((sum - 1) > RES_MAX_OFFSET) { |
| errorCode = U_BUFFER_OVERFLOW_ERROR; |
| return; |
| } |
| if (fPoolStringIndexLimit < 0x10000 && sum <= 0x10000) { |
| // 16-bit indexes work for all pool + local strings. |
| fPoolStringIndex16Limit = fPoolStringIndexLimit; |
| } else { |
| // Set the pool index threshold so that 16-bit indexes work |
| // for some pool strings and some local strings. |
| fPoolStringIndex16Limit = (int32_t)( |
| ((int64_t)fPoolStringIndexLimit * 0xffff) / sum); |
| } |
| } else if (gIsDefaultFormatVersion && formatVersion == 3 && !fIsPoolBundle) { |
| // If we just default to formatVersion 3 |
| // but there are no pool bundle strings to share |
| // and we do not write a pool bundle, |
| // then write formatVersion 2 which is just as good. |
| formatVersion = 2; |
| } |
| |
| fRoot->write16(this); |
| if (f16BitUnits.isBogus()) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| if (f16BitUnits.length() & 1) { |
| f16BitUnits.append((UChar)0xaaaa); /* pad to multiple of 4 bytes */ |
| } |
| |
| byteOffset = fKeysTop + f16BitUnits.length() * 2; |
| fRoot->preWrite(&byteOffset); |
| |
| /* total size including the root item */ |
| top = byteOffset; |
| |
| if (writtenFilename && writtenFilenameLen) { |
| *writtenFilename = 0; |
| } |
| |
| if (writtenFilename) { |
| int32_t off = 0, len = 0; |
| if (outputDir) { |
| len = (int32_t)uprv_strlen(outputDir); |
| if (len > writtenFilenameLen) { |
| len = writtenFilenameLen; |
| } |
| uprv_strncpy(writtenFilename, outputDir, len); |
| } |
| if (writtenFilenameLen -= len) { |
| off += len; |
| writtenFilename[off] = U_FILE_SEP_CHAR; |
| if (--writtenFilenameLen) { |
| ++off; |
| if(outputPkg != NULL) |
| { |
| uprv_strcpy(writtenFilename+off, outputPkg); |
| off += (int32_t)uprv_strlen(outputPkg); |
| writtenFilename[off] = '_'; |
| ++off; |
| } |
| |
| len = (int32_t)uprv_strlen(fLocale); |
| if (len > writtenFilenameLen) { |
| len = writtenFilenameLen; |
| } |
| uprv_strncpy(writtenFilename + off, fLocale, len); |
| if (writtenFilenameLen -= len) { |
| off += len; |
| len = 5; |
| if (len > writtenFilenameLen) { |
| len = writtenFilenameLen; |
| } |
| uprv_strncpy(writtenFilename + off, ".res", len); |
| } |
| } |
| } |
| } |
| |
| if(outputPkg) |
| { |
| uprv_strcpy(dataName, outputPkg); |
| uprv_strcat(dataName, "_"); |
| uprv_strcat(dataName, fLocale); |
| } |
| else |
| { |
| uprv_strcpy(dataName, fLocale); |
| } |
| |
| uprv_memcpy(dataInfo.formatVersion, gFormatVersions + formatVersion, sizeof(UVersionInfo)); |
| |
| mem = udata_create(outputDir, "res", dataName, |
| &dataInfo, (gIncludeCopyright==TRUE)? U_COPYRIGHT_STRING:NULL, &errorCode); |
| if(U_FAILURE(errorCode)){ |
| return; |
| } |
| |
| /* write the root item */ |
| udata_write32(mem, fRoot->fRes); |
| |
| /* |
| * formatVersion 1.1 (ICU 2.8): |
| * write int32_t indexes[] after root and before the key strings |
| * to make it easier to parse resource bundles in icuswap or from Java etc. |
| */ |
| uprv_memset(indexes, 0, sizeof(indexes)); |
| indexes[URES_INDEX_LENGTH]= fIndexLength; |
| indexes[URES_INDEX_KEYS_TOP]= fKeysTop>>2; |
| indexes[URES_INDEX_RESOURCES_TOP]= (int32_t)(top>>2); |
| indexes[URES_INDEX_BUNDLE_TOP]= indexes[URES_INDEX_RESOURCES_TOP]; |
| indexes[URES_INDEX_MAX_TABLE_LENGTH]= fMaxTableLength; |
| |
| /* |
| * formatVersion 1.2 (ICU 3.6): |
| * write indexes[URES_INDEX_ATTRIBUTES] with URES_ATT_NO_FALLBACK set or not set |
| * the memset() above initialized all indexes[] to 0 |
| */ |
| if (fNoFallback) { |
| indexes[URES_INDEX_ATTRIBUTES]=URES_ATT_NO_FALLBACK; |
| } |
| /* |
| * formatVersion 2.0 (ICU 4.4): |
| * more compact string value storage, optional pool bundle |
| */ |
| if (URES_INDEX_16BIT_TOP < fIndexLength) { |
| indexes[URES_INDEX_16BIT_TOP] = (fKeysTop>>2) + (f16BitUnits.length()>>1); |
| } |
| if (URES_INDEX_POOL_CHECKSUM < fIndexLength) { |
| if (fIsPoolBundle) { |
| indexes[URES_INDEX_ATTRIBUTES] |= URES_ATT_IS_POOL_BUNDLE | URES_ATT_NO_FALLBACK; |
| uint32_t checksum = computeCRC((const char *)(fKeys + fKeysBottom), |
| (uint32_t)(fKeysTop - fKeysBottom), 0); |
| if (f16BitUnits.length() <= 1) { |
| // no pool strings to checksum |
| } else if (U_IS_BIG_ENDIAN) { |
| checksum = computeCRC(reinterpret_cast<const char *>(f16BitUnits.getBuffer()), |
| (uint32_t)f16BitUnits.length() * 2, checksum); |
| } else { |
| // Swap to big-endian so we get the same checksum on all platforms |
| // (except for charset family, due to the key strings). |
| UnicodeString s(f16BitUnits); |
| assert(!s.isBogus()); |
| // .getBuffer(capacity) returns a mutable buffer |
| char16_t* p = s.getBuffer(f16BitUnits.length()); |
| for (int32_t count = f16BitUnits.length(); count > 0; --count) { |
| uint16_t x = *p; |
| *p++ = (uint16_t)((x << 8) | (x >> 8)); |
| } |
| s.releaseBuffer(f16BitUnits.length()); |
| checksum = computeCRC((const char *)s.getBuffer(), |
| (uint32_t)f16BitUnits.length() * 2, checksum); |
| } |
| indexes[URES_INDEX_POOL_CHECKSUM] = (int32_t)checksum; |
| } else if (gUsePoolBundle) { |
| indexes[URES_INDEX_ATTRIBUTES] |= URES_ATT_USES_POOL_BUNDLE; |
| indexes[URES_INDEX_POOL_CHECKSUM] = fUsePoolBundle->fChecksum; |
| } |
| } |
| // formatVersion 3 (ICU 56): |
| // share string values via pool bundle strings |
| indexes[URES_INDEX_LENGTH] |= fPoolStringIndexLimit << 8; // bits 23..0 -> 31..8 |
| indexes[URES_INDEX_ATTRIBUTES] |= (fPoolStringIndexLimit >> 12) & 0xf000; // bits 27..24 -> 15..12 |
| indexes[URES_INDEX_ATTRIBUTES] |= fPoolStringIndex16Limit << 16; |
| |
| /* write the indexes[] */ |
| udata_writeBlock(mem, indexes, fIndexLength*4); |
| |
| /* write the table key strings */ |
| udata_writeBlock(mem, fKeys+fKeysBottom, |
| fKeysTop-fKeysBottom); |
| |
| /* write the v2 UTF-16 strings, URES_TABLE16 and URES_ARRAY16 */ |
| udata_writeBlock(mem, f16BitUnits.getBuffer(), f16BitUnits.length()*2); |
| |
| /* write all of the bundle contents: the root item and its children */ |
| byteOffset = fKeysTop + f16BitUnits.length() * 2; |
| fRoot->write(mem, &byteOffset); |
| assert(byteOffset == top); |
| |
| size = udata_finish(mem, &errorCode); |
| if(top != size) { |
| fprintf(stderr, "genrb error: wrote %u bytes but counted %u\n", |
| (int)size, (int)top); |
| errorCode = U_INTERNAL_PROGRAM_ERROR; |
| } |
| } |
| |
| /* Opening Functions */ |
| |
| TableResource* table_open(struct SRBRoot *bundle, const char *tag, const struct UString* comment, UErrorCode *status) { |
| LocalPointer<TableResource> res(new TableResource(bundle, tag, comment, *status), *status); |
| return U_SUCCESS(*status) ? res.orphan() : NULL; |
| } |
| |
| ArrayResource* array_open(struct SRBRoot *bundle, const char *tag, const struct UString* comment, UErrorCode *status) { |
| LocalPointer<ArrayResource> res(new ArrayResource(bundle, tag, comment, *status), *status); |
| return U_SUCCESS(*status) ? res.orphan() : NULL; |
| } |
| |
| struct SResource *string_open(struct SRBRoot *bundle, const char *tag, const UChar *value, int32_t len, const struct UString* comment, UErrorCode *status) { |
| LocalPointer<SResource> res( |
| new StringResource(bundle, tag, value, len, comment, *status), *status); |
| return U_SUCCESS(*status) ? res.orphan() : NULL; |
| } |
| |
| struct SResource *alias_open(struct SRBRoot *bundle, const char *tag, UChar *value, int32_t len, const struct UString* comment, UErrorCode *status) { |
| LocalPointer<SResource> res( |
| new AliasResource(bundle, tag, value, len, comment, *status), *status); |
| return U_SUCCESS(*status) ? res.orphan() : NULL; |
| } |
| |
| IntVectorResource *intvector_open(struct SRBRoot *bundle, const char *tag, const struct UString* comment, UErrorCode *status) { |
| LocalPointer<IntVectorResource> res( |
| new IntVectorResource(bundle, tag, comment, *status), *status); |
| return U_SUCCESS(*status) ? res.orphan() : NULL; |
| } |
| |
| struct SResource *int_open(struct SRBRoot *bundle, const char *tag, int32_t value, const struct UString* comment, UErrorCode *status) { |
| LocalPointer<SResource> res(new IntResource(bundle, tag, value, comment, *status), *status); |
| return U_SUCCESS(*status) ? res.orphan() : NULL; |
| } |
| |
| struct SResource *bin_open(struct SRBRoot *bundle, const char *tag, uint32_t length, uint8_t *data, const char* fileName, const struct UString* comment, UErrorCode *status) { |
| LocalPointer<SResource> res( |
| new BinaryResource(bundle, tag, length, data, fileName, comment, *status), *status); |
| return U_SUCCESS(*status) ? res.orphan() : NULL; |
| } |
| |
| SRBRoot::SRBRoot(const UString *comment, UBool isPoolBundle, UErrorCode &errorCode) |
| : fRoot(NULL), fLocale(NULL), fIndexLength(0), fMaxTableLength(0), fNoFallback(FALSE), |
| fStringsForm(STRINGS_UTF16_V1), fIsPoolBundle(isPoolBundle), |
| fKeys(NULL), fKeyMap(NULL), |
| fKeysBottom(0), fKeysTop(0), fKeysCapacity(0), |
| fKeysCount(0), fLocalKeyLimit(0), |
| f16BitUnits(), f16BitStringsLength(0), |
| fUsePoolBundle(&kNoPoolBundle), |
| fPoolStringIndexLimit(0), fPoolStringIndex16Limit(0), fLocalStringIndexLimit(0), |
| fWritePoolBundle(NULL) { |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| |
| if (gFormatVersion > 1) { |
| // f16BitUnits must start with a zero for empty resources. |
| // We might be able to omit it if there are no empty 16-bit resources. |
| f16BitUnits.append((UChar)0); |
| } |
| |
| fKeys = (char *) uprv_malloc(sizeof(char) * KEY_SPACE_SIZE); |
| if (isPoolBundle) { |
| fRoot = new PseudoListResource(this, errorCode); |
| } else { |
| fRoot = new TableResource(this, NULL, comment, errorCode); |
| } |
| if (fKeys == NULL || fRoot == NULL || U_FAILURE(errorCode)) { |
| if (U_SUCCESS(errorCode)) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| } |
| return; |
| } |
| |
| fKeysCapacity = KEY_SPACE_SIZE; |
| /* formatVersion 1.1 and up: start fKeysTop after the root item and indexes[] */ |
| if (gUsePoolBundle || isPoolBundle) { |
| fIndexLength = URES_INDEX_POOL_CHECKSUM + 1; |
| } else if (gFormatVersion >= 2) { |
| fIndexLength = URES_INDEX_16BIT_TOP + 1; |
| } else /* formatVersion 1 */ { |
| fIndexLength = URES_INDEX_ATTRIBUTES + 1; |
| } |
| fKeysBottom = (1 /* root */ + fIndexLength) * 4; |
| uprv_memset(fKeys, 0, fKeysBottom); |
| fKeysTop = fKeysBottom; |
| |
| if (gFormatVersion == 1) { |
| fStringsForm = STRINGS_UTF16_V1; |
| } else { |
| fStringsForm = STRINGS_UTF16_V2; |
| } |
| } |
| |
| /* Closing Functions */ |
| |
| void res_close(struct SResource *res) { |
| delete res; |
| } |
| |
| SRBRoot::~SRBRoot() { |
| delete fRoot; |
| uprv_free(fLocale); |
| uprv_free(fKeys); |
| uprv_free(fKeyMap); |
| } |
| |
| /* Misc Functions */ |
| |
| void SRBRoot::setLocale(UChar *locale, UErrorCode &errorCode) { |
| if(U_FAILURE(errorCode)) { |
| return; |
| } |
| |
| uprv_free(fLocale); |
| fLocale = (char*) uprv_malloc(sizeof(char) * (u_strlen(locale)+1)); |
| if(fLocale == NULL) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| |
| u_UCharsToChars(locale, fLocale, u_strlen(locale)+1); |
| } |
| |
| const char * |
| SRBRoot::getKeyString(int32_t key) const { |
| if (key < 0) { |
| return fUsePoolBundle->fKeys + (key & 0x7fffffff); |
| } else { |
| return fKeys + key; |
| } |
| } |
| |
| const char * |
| SResource::getKeyString(const SRBRoot *bundle) const { |
| if (fKey == -1) { |
| return NULL; |
| } |
| return bundle->getKeyString(fKey); |
| } |
| |
| const char * |
| SRBRoot::getKeyBytes(int32_t *pLength) const { |
| *pLength = fKeysTop - fKeysBottom; |
| return fKeys + fKeysBottom; |
| } |
| |
| int32_t |
| SRBRoot::addKeyBytes(const char *keyBytes, int32_t length, UErrorCode &errorCode) { |
| int32_t keypos; |
| |
| // It is not legal to add new key bytes after compactKeys is run! |
| U_ASSERT(fKeyMap == nullptr); |
| |
| if (U_FAILURE(errorCode)) { |
| return -1; |
| } |
| if (length < 0 || (keyBytes == NULL && length != 0)) { |
| errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
| return -1; |
| } |
| if (length == 0) { |
| return fKeysTop; |
| } |
| |
| keypos = fKeysTop; |
| fKeysTop += length; |
| if (fKeysTop >= fKeysCapacity) { |
| /* overflow - resize the keys buffer */ |
| fKeysCapacity += KEY_SPACE_SIZE; |
| fKeys = static_cast<char *>(uprv_realloc(fKeys, fKeysCapacity)); |
| if(fKeys == NULL) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return -1; |
| } |
| } |
| |
| uprv_memcpy(fKeys + keypos, keyBytes, length); |
| |
| return keypos; |
| } |
| |
| int32_t |
| SRBRoot::addTag(const char *tag, UErrorCode &errorCode) { |
| int32_t keypos; |
| |
| if (U_FAILURE(errorCode)) { |
| return -1; |
| } |
| |
| if (tag == NULL) { |
| /* no error: the root table and array items have no keys */ |
| return -1; |
| } |
| |
| keypos = addKeyBytes(tag, (int32_t)(uprv_strlen(tag) + 1), errorCode); |
| if (U_SUCCESS(errorCode)) { |
| ++fKeysCount; |
| } |
| return keypos; |
| } |
| |
| static int32_t |
| compareInt32(int32_t lPos, int32_t rPos) { |
| /* |
| * Compare possibly-negative key offsets. Don't just return lPos - rPos |
| * because that is prone to negative-integer underflows. |
| */ |
| if (lPos < rPos) { |
| return -1; |
| } else if (lPos > rPos) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| static int32_t U_CALLCONV |
| compareKeySuffixes(const void *context, const void *l, const void *r) { |
| const struct SRBRoot *bundle=(const struct SRBRoot *)context; |
| int32_t lPos = ((const KeyMapEntry *)l)->oldpos; |
| int32_t rPos = ((const KeyMapEntry *)r)->oldpos; |
| const char *lStart = bundle->getKeyString(lPos); |
| const char *lLimit = lStart; |
| const char *rStart = bundle->getKeyString(rPos); |
| const char *rLimit = rStart; |
| int32_t diff; |
| while (*lLimit != 0) { ++lLimit; } |
| while (*rLimit != 0) { ++rLimit; } |
| /* compare keys in reverse character order */ |
| while (lStart < lLimit && rStart < rLimit) { |
| diff = (int32_t)(uint8_t)*--lLimit - (int32_t)(uint8_t)*--rLimit; |
| if (diff != 0) { |
| return diff; |
| } |
| } |
| /* sort equal suffixes by descending key length */ |
| diff = (int32_t)(rLimit - rStart) - (int32_t)(lLimit - lStart); |
| if (diff != 0) { |
| return diff; |
| } |
| /* Sort pool bundle keys first (negative oldpos), and otherwise keys in parsing order. */ |
| return compareInt32(lPos, rPos); |
| } |
| |
| static int32_t U_CALLCONV |
| compareKeyNewpos(const void * /*context*/, const void *l, const void *r) { |
| return compareInt32(((const KeyMapEntry *)l)->newpos, ((const KeyMapEntry *)r)->newpos); |
| } |
| |
| static int32_t U_CALLCONV |
| compareKeyOldpos(const void * /*context*/, const void *l, const void *r) { |
| return compareInt32(((const KeyMapEntry *)l)->oldpos, ((const KeyMapEntry *)r)->oldpos); |
| } |
| |
| void SResource::collectKeys(std::function<void(int32_t)> collector) const { |
| collector(fKey); |
| } |
| |
| void ContainerResource::collectKeys(std::function<void(int32_t)> collector) const { |
| collector(fKey); |
| for (SResource* curr = fFirst; curr != NULL; curr = curr->fNext) { |
| curr->collectKeys(collector); |
| } |
| } |
| |
| void |
| SRBRoot::compactKeys(UErrorCode &errorCode) { |
| KeyMapEntry *map; |
| char *keys; |
| int32_t i; |
| |
| // Except for pool bundles, keys might not be used. |
| // Do not add unused keys to the final bundle. |
| std::set<int32_t> keysInUse; |
| if (!fIsPoolBundle) { |
| fRoot->collectKeys([&keysInUse](int32_t key) { |
| if (key >= 0) { |
| keysInUse.insert(key); |
| } |
| }); |
| fKeysCount = static_cast<int32_t>(keysInUse.size()); |
| } |
| |
| int32_t keysCount = fUsePoolBundle->fKeysCount + fKeysCount; |
| if (U_FAILURE(errorCode) || fKeyMap != NULL) { |
| return; |
| } |
| map = (KeyMapEntry *)uprv_malloc(keysCount * sizeof(KeyMapEntry)); |
| if (map == NULL) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| keys = (char *)fUsePoolBundle->fKeys; |
| for (i = 0; i < fUsePoolBundle->fKeysCount; ++i) { |
| map[i].oldpos = |
| (int32_t)(keys - fUsePoolBundle->fKeys) | 0x80000000; /* negative oldpos */ |
| map[i].newpos = 0; |
| while (*keys != 0) { ++keys; } /* skip the key */ |
| ++keys; /* skip the NUL */ |
| } |
| keys = fKeys + fKeysBottom; |
| while (i < keysCount) { |
| int32_t keyOffset = static_cast<int32_t>(keys - fKeys); |
| if (!fIsPoolBundle && keysInUse.count(keyOffset) == 0) { |
| // Mark the unused key as deleted |
| while (*keys != 0) { *keys++ = 1; } |
| *keys++ = 1; |
| } else { |
| map[i].oldpos = keyOffset; |
| map[i].newpos = 0; |
| while (*keys != 0) { ++keys; } /* skip the key */ |
| ++keys; /* skip the NUL */ |
| i++; |
| } |
| } |
| if (keys != fKeys + fKeysTop) { |
| // Throw away any unused keys from the end |
| fKeysTop = static_cast<int32_t>(keys - fKeys); |
| } |
| /* Sort the keys so that each one is immediately followed by all of its suffixes. */ |
| uprv_sortArray(map, keysCount, (int32_t)sizeof(KeyMapEntry), |
| compareKeySuffixes, this, FALSE, &errorCode); |
| /* |
| * Make suffixes point into earlier, longer strings that contain them |
| * and mark the old, now unused suffix bytes as deleted. |
| */ |
| if (U_SUCCESS(errorCode)) { |
| keys = fKeys; |
| for (i = 0; i < keysCount;) { |
| /* |
| * This key is not a suffix of the previous one; |
| * keep this one and delete the following ones that are |
| * suffixes of this one. |
| */ |
| const char *key; |
| const char *keyLimit; |
| int32_t j = i + 1; |
| map[i].newpos = map[i].oldpos; |
| if (j < keysCount && map[j].oldpos < 0) { |
| /* Key string from the pool bundle, do not delete. */ |
| i = j; |
| continue; |
| } |
| key = getKeyString(map[i].oldpos); |
| for (keyLimit = key; *keyLimit != 0; ++keyLimit) {} |
| for (; j < keysCount && map[j].oldpos >= 0; ++j) { |
| const char *k; |
| char *suffix; |
| const char *suffixLimit; |
| int32_t offset; |
| suffix = keys + map[j].oldpos; |
| for (suffixLimit = suffix; *suffixLimit != 0; ++suffixLimit) {} |
| offset = static_cast<int32_t>((keyLimit - key) - (suffixLimit - suffix)); |
| if (offset < 0) { |
| break; /* suffix cannot be longer than the original */ |
| } |
| /* Is it a suffix of the earlier, longer key? */ |
| for (k = keyLimit; suffix < suffixLimit && *--k == *--suffixLimit;) {} |
| if (suffix == suffixLimit && *k == *suffixLimit) { |
| map[j].newpos = map[i].oldpos + offset; /* yes, point to the earlier key */ |
| // Mark the suffix as deleted |
| while (*suffix != 0) { *suffix++ = 1; } |
| *suffix = 1; |
| } else { |
| break; /* not a suffix, restart from here */ |
| } |
| } |
| i = j; |
| } |
| /* |
| * Re-sort by newpos, then modify the key characters array in-place |
| * to squeeze out unused bytes, and readjust the newpos offsets. |
| */ |
| uprv_sortArray(map, keysCount, (int32_t)sizeof(KeyMapEntry), |
| compareKeyNewpos, NULL, FALSE, &errorCode); |
| if (U_SUCCESS(errorCode)) { |
| int32_t oldpos, newpos, limit; |
| oldpos = newpos = fKeysBottom; |
| limit = fKeysTop; |
| /* skip key offsets that point into the pool bundle rather than this new bundle */ |
| for (i = 0; i < keysCount && map[i].newpos < 0; ++i) {} |
| if (i < keysCount) { |
| while (oldpos < limit) { |
| if (keys[oldpos] == 1) { |
| ++oldpos; /* skip unused bytes */ |
| } else { |
| /* adjust the new offsets for keys starting here */ |
| while (i < keysCount && map[i].newpos == oldpos) { |
| map[i++].newpos = newpos; |
| } |
| /* move the key characters to their new position */ |
| keys[newpos++] = keys[oldpos++]; |
| } |
| } |
| U_ASSERT(i == keysCount); |
| } |
| fKeysTop = newpos; |
| /* Re-sort once more, by old offsets for binary searching. */ |
| uprv_sortArray(map, keysCount, (int32_t)sizeof(KeyMapEntry), |
| compareKeyOldpos, NULL, FALSE, &errorCode); |
| if (U_SUCCESS(errorCode)) { |
| /* key size reduction by limit - newpos */ |
| fKeyMap = map; |
| map = NULL; |
| } |
| } |
| } |
| uprv_free(map); |
| } |
| |
| static int32_t U_CALLCONV |
| compareStringSuffixes(const void * /*context*/, const void *l, const void *r) { |
| const StringResource *left = *((const StringResource **)l); |
| const StringResource *right = *((const StringResource **)r); |
| const UChar *lStart = left->getBuffer(); |
| const UChar *lLimit = lStart + left->length(); |
| const UChar *rStart = right->getBuffer(); |
| const UChar *rLimit = rStart + right->length(); |
| int32_t diff; |
| /* compare keys in reverse character order */ |
| while (lStart < lLimit && rStart < rLimit) { |
| diff = (int32_t)*--lLimit - (int32_t)*--rLimit; |
| if (diff != 0) { |
| return diff; |
| } |
| } |
| /* sort equal suffixes by descending string length */ |
| return right->length() - left->length(); |
| } |
| |
| static int32_t U_CALLCONV |
| compareStringLengths(const void * /*context*/, const void *l, const void *r) { |
| const StringResource *left = *((const StringResource **)l); |
| const StringResource *right = *((const StringResource **)r); |
| int32_t diff; |
| /* Make "is suffix of another string" compare greater than a non-suffix. */ |
| diff = (int)(left->fSame != NULL) - (int)(right->fSame != NULL); |
| if (diff != 0) { |
| return diff; |
| } |
| /* sort by ascending string length */ |
| diff = left->length() - right->length(); |
| if (diff != 0) { |
| return diff; |
| } |
| // sort by descending size reduction |
| diff = right->fNumUnitsSaved - left->fNumUnitsSaved; |
| if (diff != 0) { |
| return diff; |
| } |
| // sort lexically |
| return left->fString.compare(right->fString); |
| } |
| |
| void |
| StringResource::writeUTF16v2(int32_t base, UnicodeString &dest) { |
| int32_t len = length(); |
| fRes = URES_MAKE_RESOURCE(URES_STRING_V2, base + dest.length()); |
| fWritten = TRUE; |
| switch(fNumCharsForLength) { |
| case 0: |
| break; |
| case 1: |
| dest.append((UChar)(0xdc00 + len)); |
| break; |
| case 2: |
| dest.append((UChar)(0xdfef + (len >> 16))); |
| dest.append((UChar)len); |
| break; |
| case 3: |
| dest.append((UChar)0xdfff); |
| dest.append((UChar)(len >> 16)); |
| dest.append((UChar)len); |
| break; |
| default: |
| break; /* will not occur */ |
| } |
| dest.append(fString); |
| dest.append((UChar)0); |
| } |
| |
| void |
| SRBRoot::compactStringsV2(UHashtable *stringSet, UErrorCode &errorCode) { |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| // Store the StringResource pointers in an array for |
| // easy sorting and processing. |
| // We enumerate a set of strings, so there are no duplicates. |
| int32_t count = uhash_count(stringSet); |
| LocalArray<StringResource *> array(new StringResource *[count], errorCode); |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| for (int32_t pos = UHASH_FIRST, i = 0; i < count; ++i) { |
| array[i] = (StringResource *)uhash_nextElement(stringSet, &pos)->key.pointer; |
| } |
| /* Sort the strings so that each one is immediately followed by all of its suffixes. */ |
| uprv_sortArray(array.getAlias(), count, (int32_t)sizeof(struct SResource **), |
| compareStringSuffixes, NULL, FALSE, &errorCode); |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| /* |
| * Make suffixes point into earlier, longer strings that contain them. |
| * Temporarily use fSame and fSuffixOffset for suffix strings to |
| * refer to the remaining ones. |
| */ |
| for (int32_t i = 0; i < count;) { |
| /* |
| * This string is not a suffix of the previous one; |
| * write this one and subsume the following ones that are |
| * suffixes of this one. |
| */ |
| StringResource *res = array[i]; |
| res->fNumUnitsSaved = (res->fNumCopies - 1) * res->get16BitStringsLength(); |
| // Whole duplicates of pool strings are already account for in fPoolStringIndexLimit, |
| // see StringResource::handlePreflightStrings(). |
| int32_t j; |
| for (j = i + 1; j < count; ++j) { |
| StringResource *suffixRes = array[j]; |
| /* Is it a suffix of the earlier, longer string? */ |
| if (res->fString.endsWith(suffixRes->fString)) { |
| assert(res->length() != suffixRes->length()); // Set strings are unique. |
| if (suffixRes->fWritten) { |
| // Pool string, skip. |
| } else if (suffixRes->fNumCharsForLength == 0) { |
| /* yes, point to the earlier string */ |
| suffixRes->fSame = res; |
| suffixRes->fSuffixOffset = res->length() - suffixRes->length(); |
| if (res->fWritten) { |
| // Suffix-share res which is a pool string. |
| // Compute the resource word and collect the maximum. |
| suffixRes->fRes = |
| res->fRes + res->fNumCharsForLength + suffixRes->fSuffixOffset; |
| int32_t poolStringIndex = (int32_t)RES_GET_OFFSET(suffixRes->fRes); |
| if (poolStringIndex >= fPoolStringIndexLimit) { |
| fPoolStringIndexLimit = poolStringIndex + 1; |
| } |
| suffixRes->fWritten = TRUE; |
| } |
| res->fNumUnitsSaved += suffixRes->fNumCopies * suffixRes->get16BitStringsLength(); |
| } else { |
| /* write the suffix by itself if we need explicit length */ |
| } |
| } else { |
| break; /* not a suffix, restart from here */ |
| } |
| } |
| i = j; |
| } |
| /* |
| * Re-sort the strings by ascending length (except suffixes last) |
| * to optimize for URES_TABLE16 and URES_ARRAY16: |
| * Keep as many as possible within reach of 16-bit offsets. |
| */ |
| uprv_sortArray(array.getAlias(), count, (int32_t)sizeof(struct SResource **), |
| compareStringLengths, NULL, FALSE, &errorCode); |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| if (fIsPoolBundle) { |
| // Write strings that are sufficiently shared. |
| // Avoid writing other strings. |
| int32_t numStringsWritten = 0; |
| int32_t numUnitsSaved = 0; |
| int32_t numUnitsNotSaved = 0; |
| for (int32_t i = 0; i < count; ++i) { |
| StringResource *res = array[i]; |
| // Maximum pool string index when suffix-sharing the last character. |
| int32_t maxStringIndex = |
| f16BitUnits.length() + res->fNumCharsForLength + res->length() - 1; |
| if (res->fNumUnitsSaved >= GENRB_MIN_16BIT_UNITS_SAVED_FOR_POOL_STRING && |
| maxStringIndex < RES_MAX_OFFSET) { |
| res->writeUTF16v2(0, f16BitUnits); |
| ++numStringsWritten; |
| numUnitsSaved += res->fNumUnitsSaved; |
| } else { |
| numUnitsNotSaved += res->fNumUnitsSaved; |
| res->fRes = URES_MAKE_EMPTY_RESOURCE(URES_STRING); |
| res->fWritten = TRUE; |
| } |
| } |
| if (f16BitUnits.isBogus()) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| } |
| if (getShowWarning()) { // not quiet |
| printf("number of shared strings: %d\n", (int)numStringsWritten); |
| printf("16-bit units for strings: %6d = %6d bytes\n", |
| (int)f16BitUnits.length(), (int)f16BitUnits.length() * 2); |
| printf("16-bit units saved: %6d = %6d bytes\n", |
| (int)numUnitsSaved, (int)numUnitsSaved * 2); |
| printf("16-bit units not saved: %6d = %6d bytes\n", |
| (int)numUnitsNotSaved, (int)numUnitsNotSaved * 2); |
| } |
| } else { |
| assert(fPoolStringIndexLimit <= fUsePoolBundle->fStringIndexLimit); |
| /* Write the non-suffix strings. */ |
| int32_t i; |
| for (i = 0; i < count && array[i]->fSame == NULL; ++i) { |
| StringResource *res = array[i]; |
| if (!res->fWritten) { |
| int32_t localStringIndex = f16BitUnits.length(); |
| if (localStringIndex >= fLocalStringIndexLimit) { |
| fLocalStringIndexLimit = localStringIndex + 1; |
| } |
| res->writeUTF16v2(fPoolStringIndexLimit, f16BitUnits); |
| } |
| } |
| if (f16BitUnits.isBogus()) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| if (fWritePoolBundle != NULL && gFormatVersion >= 3) { |
| PseudoListResource *poolStrings = |
| static_cast<PseudoListResource *>(fWritePoolBundle->fRoot); |
| for (i = 0; i < count && array[i]->fSame == NULL; ++i) { |
| assert(!array[i]->fString.isEmpty()); |
| StringResource *poolString = |
| new StringResource(fWritePoolBundle, array[i]->fString, errorCode); |
| if (poolString == NULL) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| break; |
| } |
| poolStrings->add(poolString); |
| } |
| } |
| /* Write the suffix strings. Make each point to the real string. */ |
| for (; i < count; ++i) { |
| StringResource *res = array[i]; |
| if (res->fWritten) { |
| continue; |
| } |
| StringResource *same = res->fSame; |
| assert(res->length() != same->length()); // Set strings are unique. |
| res->fRes = same->fRes + same->fNumCharsForLength + res->fSuffixOffset; |
| int32_t localStringIndex = (int32_t)RES_GET_OFFSET(res->fRes) - fPoolStringIndexLimit; |
| // Suffixes of pool strings have been set already. |
| assert(localStringIndex >= 0); |
| if (localStringIndex >= fLocalStringIndexLimit) { |
| fLocalStringIndexLimit = localStringIndex + 1; |
| } |
| res->fWritten = TRUE; |
| } |
| } |
| // +1 to account for the initial zero in f16BitUnits |
| assert(f16BitUnits.length() <= (f16BitStringsLength + 1)); |
| } |
| |
| void SResource::applyFilter( |
| const PathFilter& /*filter*/, |
| ResKeyPath& /*path*/, |
| const SRBRoot* /*bundle*/) { |
| // Only a few resource types (tables) are capable of being filtered. |
| } |
| |
| void TableResource::applyFilter( |
| const PathFilter& filter, |
| ResKeyPath& path, |
| const SRBRoot* bundle) { |
| SResource* prev = nullptr; |
| SResource* curr = fFirst; |
| for (; curr != nullptr;) { |
| path.push(curr->getKeyString(bundle)); |
| auto inclusion = filter.match(path); |
| if (inclusion == PathFilter::EInclusion::INCLUDE) { |
| // Include whole subtree |
| // no-op |
| if (isVerbose()) { |
| std::cout << "genrb subtree: " << bundle->fLocale << ": INCLUDE: " << path << std::endl; |
| } |
| } else if (inclusion == PathFilter::EInclusion::EXCLUDE) { |
| // Reject the whole subtree |
| // Remove it from the linked list |
| if (isVerbose()) { |
| std::cout << "genrb subtree: " << bundle->fLocale << ": DELETE: " << path << std::endl; |
| } |
| if (prev == nullptr) { |
| fFirst = curr->fNext; |
| } else { |
| prev->fNext = curr->fNext; |
| } |
| fCount--; |
| delete curr; |
| curr = prev; |
| } else { |
| U_ASSERT(inclusion == PathFilter::EInclusion::PARTIAL); |
| // Recurse into the child |
| curr->applyFilter(filter, path, bundle); |
| } |
| path.pop(); |
| |
| prev = curr; |
| if (curr == nullptr) { |
| curr = fFirst; |
| } else { |
| curr = curr->fNext; |
| } |
| } |
| } |