michael@0: /* michael@0: ******************************************************************************* michael@0: * * michael@0: * Copyright (C) 1999-2012, International Business Machines Corporation * michael@0: * and others. All Rights Reserved. * michael@0: * * michael@0: ******************************************************************************* michael@0: * file name: uresdata.c michael@0: * encoding: US-ASCII michael@0: * tab size: 8 (not used) michael@0: * indentation:4 michael@0: * michael@0: * created on: 1999dec08 michael@0: * created by: Markus W. Scherer michael@0: * Modification History: michael@0: * michael@0: * Date Name Description michael@0: * 06/20/2000 helena OS/400 port changes; mostly typecast. michael@0: * 06/24/02 weiv Added support for resource sharing michael@0: */ michael@0: michael@0: #include "unicode/utypes.h" michael@0: #include "unicode/udata.h" michael@0: #include "unicode/ustring.h" michael@0: #include "unicode/utf16.h" michael@0: #include "cmemory.h" michael@0: #include "cstring.h" michael@0: #include "uarrsort.h" michael@0: #include "udataswp.h" michael@0: #include "ucol_swp.h" michael@0: #include "uinvchar.h" michael@0: #include "uresdata.h" michael@0: #include "uresimp.h" michael@0: #include "uassert.h" michael@0: michael@0: #define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof((array)[0])) michael@0: michael@0: /* michael@0: * Resource access helpers michael@0: */ michael@0: michael@0: /* get a const char* pointer to the key with the keyOffset byte offset from pRoot */ michael@0: #define RES_GET_KEY16(pResData, keyOffset) \ michael@0: ((keyOffset)<(pResData)->localKeyLimit ? \ michael@0: (const char *)(pResData)->pRoot+(keyOffset) : \ michael@0: (pResData)->poolBundleKeys+(keyOffset)-(pResData)->localKeyLimit) michael@0: michael@0: #define RES_GET_KEY32(pResData, keyOffset) \ michael@0: ((keyOffset)>=0 ? \ michael@0: (const char *)(pResData)->pRoot+(keyOffset) : \ michael@0: (pResData)->poolBundleKeys+((keyOffset)&0x7fffffff)) michael@0: michael@0: #define URESDATA_ITEM_NOT_FOUND -1 michael@0: michael@0: /* empty resources, returned when the resource offset is 0 */ michael@0: static const uint16_t gEmpty16=0; michael@0: michael@0: static const struct { michael@0: int32_t length; michael@0: int32_t res; michael@0: } gEmpty32={ 0, 0 }; michael@0: michael@0: static const struct { michael@0: int32_t length; michael@0: UChar nul; michael@0: UChar pad; michael@0: } gEmptyString={ 0, 0, 0 }; michael@0: michael@0: /* michael@0: * All the type-access functions assume that michael@0: * the resource is of the expected type. michael@0: */ michael@0: michael@0: static int32_t michael@0: _res_findTableItem(const ResourceData *pResData, const uint16_t *keyOffsets, int32_t length, michael@0: const char *key, const char **realKey) { michael@0: const char *tableKey; michael@0: int32_t mid, start, limit; michael@0: int result; michael@0: michael@0: /* do a binary search for the key */ michael@0: start=0; michael@0: limit=length; michael@0: while(startuseNativeStrcmp) { michael@0: result = uprv_strcmp(key, tableKey); michael@0: } else { michael@0: result = uprv_compareInvCharsAsAscii(key, tableKey); michael@0: } michael@0: if (result < 0) { michael@0: limit = mid; michael@0: } else if (result > 0) { michael@0: start = mid + 1; michael@0: } else { michael@0: /* We found it! */ michael@0: *realKey=tableKey; michael@0: return mid; michael@0: } michael@0: } michael@0: return URESDATA_ITEM_NOT_FOUND; /* not found or table is empty. */ michael@0: } michael@0: michael@0: static int32_t michael@0: _res_findTable32Item(const ResourceData *pResData, const int32_t *keyOffsets, int32_t length, michael@0: const char *key, const char **realKey) { michael@0: const char *tableKey; michael@0: int32_t mid, start, limit; michael@0: int result; michael@0: michael@0: /* do a binary search for the key */ michael@0: start=0; michael@0: limit=length; michael@0: while(startuseNativeStrcmp) { michael@0: result = uprv_strcmp(key, tableKey); michael@0: } else { michael@0: result = uprv_compareInvCharsAsAscii(key, tableKey); michael@0: } michael@0: if (result < 0) { michael@0: limit = mid; michael@0: } else if (result > 0) { michael@0: start = mid + 1; michael@0: } else { michael@0: /* We found it! */ michael@0: *realKey=tableKey; michael@0: return mid; michael@0: } michael@0: } michael@0: return URESDATA_ITEM_NOT_FOUND; /* not found or table is empty. */ michael@0: } michael@0: michael@0: /* helper for res_load() ---------------------------------------------------- */ michael@0: michael@0: static UBool U_CALLCONV michael@0: isAcceptable(void *context, michael@0: const char *type, const char *name, michael@0: const UDataInfo *pInfo) { michael@0: uprv_memcpy(context, pInfo->formatVersion, 4); michael@0: return (UBool)( michael@0: pInfo->size>=20 && michael@0: pInfo->isBigEndian==U_IS_BIG_ENDIAN && michael@0: pInfo->charsetFamily==U_CHARSET_FAMILY && michael@0: pInfo->sizeofUChar==U_SIZEOF_UCHAR && michael@0: pInfo->dataFormat[0]==0x52 && /* dataFormat="ResB" */ michael@0: pInfo->dataFormat[1]==0x65 && michael@0: pInfo->dataFormat[2]==0x73 && michael@0: pInfo->dataFormat[3]==0x42 && michael@0: (pInfo->formatVersion[0]==1 || pInfo->formatVersion[0]==2)); michael@0: } michael@0: michael@0: /* semi-public functions ---------------------------------------------------- */ michael@0: michael@0: static void michael@0: res_init(ResourceData *pResData, michael@0: UVersionInfo formatVersion, const void *inBytes, int32_t length, michael@0: UErrorCode *errorCode) { michael@0: UResType rootType; michael@0: michael@0: /* get the root resource */ michael@0: pResData->pRoot=(const int32_t *)inBytes; michael@0: pResData->rootRes=(Resource)*pResData->pRoot; michael@0: pResData->p16BitUnits=&gEmpty16; michael@0: michael@0: /* formatVersion 1.1 must have a root item and at least 5 indexes */ michael@0: if(length>=0 && (length/4)<((formatVersion[0]==1 && formatVersion[1]==0) ? 1 : 1+5)) { michael@0: *errorCode=U_INVALID_FORMAT_ERROR; michael@0: res_unload(pResData); michael@0: return; michael@0: } michael@0: michael@0: /* currently, we accept only resources that have a Table as their roots */ michael@0: rootType=(UResType)RES_GET_TYPE(pResData->rootRes); michael@0: if(!URES_IS_TABLE(rootType)) { michael@0: *errorCode=U_INVALID_FORMAT_ERROR; michael@0: res_unload(pResData); michael@0: return; michael@0: } michael@0: michael@0: if(formatVersion[0]==1 && formatVersion[1]==0) { michael@0: pResData->localKeyLimit=0x10000; /* greater than any 16-bit key string offset */ michael@0: } else { michael@0: /* bundles with formatVersion 1.1 and later contain an indexes[] array */ michael@0: const int32_t *indexes=pResData->pRoot+1; michael@0: int32_t indexLength=indexes[URES_INDEX_LENGTH]&0xff; michael@0: if(indexLength<=URES_INDEX_MAX_TABLE_LENGTH) { michael@0: *errorCode=U_INVALID_FORMAT_ERROR; michael@0: res_unload(pResData); michael@0: return; michael@0: } michael@0: if( length>=0 && michael@0: (length<((1+indexLength)<<2) || michael@0: length<(indexes[URES_INDEX_BUNDLE_TOP]<<2)) michael@0: ) { michael@0: *errorCode=U_INVALID_FORMAT_ERROR; michael@0: res_unload(pResData); michael@0: return; michael@0: } michael@0: if(indexes[URES_INDEX_KEYS_TOP]>(1+indexLength)) { michael@0: pResData->localKeyLimit=indexes[URES_INDEX_KEYS_TOP]<<2; michael@0: } michael@0: if(indexLength>URES_INDEX_ATTRIBUTES) { michael@0: int32_t att=indexes[URES_INDEX_ATTRIBUTES]; michael@0: pResData->noFallback=(UBool)(att&URES_ATT_NO_FALLBACK); michael@0: pResData->isPoolBundle=(UBool)((att&URES_ATT_IS_POOL_BUNDLE)!=0); michael@0: pResData->usesPoolBundle=(UBool)((att&URES_ATT_USES_POOL_BUNDLE)!=0); michael@0: } michael@0: if((pResData->isPoolBundle || pResData->usesPoolBundle) && indexLength<=URES_INDEX_POOL_CHECKSUM) { michael@0: *errorCode=U_INVALID_FORMAT_ERROR; michael@0: res_unload(pResData); michael@0: return; michael@0: } michael@0: if( indexLength>URES_INDEX_16BIT_TOP && michael@0: indexes[URES_INDEX_16BIT_TOP]>indexes[URES_INDEX_KEYS_TOP] michael@0: ) { michael@0: pResData->p16BitUnits=(const uint16_t *)(pResData->pRoot+indexes[URES_INDEX_KEYS_TOP]); michael@0: } michael@0: } michael@0: michael@0: if(formatVersion[0]==1 || U_CHARSET_FAMILY==U_ASCII_FAMILY) { michael@0: /* michael@0: * formatVersion 1: compare key strings in native-charset order michael@0: * formatVersion 2 and up: compare key strings in ASCII order michael@0: */ michael@0: pResData->useNativeStrcmp=TRUE; michael@0: } michael@0: } michael@0: michael@0: U_CAPI void U_EXPORT2 michael@0: res_read(ResourceData *pResData, michael@0: const UDataInfo *pInfo, const void *inBytes, int32_t length, michael@0: UErrorCode *errorCode) { michael@0: UVersionInfo formatVersion; michael@0: michael@0: uprv_memset(pResData, 0, sizeof(ResourceData)); michael@0: if(U_FAILURE(*errorCode)) { michael@0: return; michael@0: } michael@0: if(!isAcceptable(formatVersion, NULL, NULL, pInfo)) { michael@0: *errorCode=U_INVALID_FORMAT_ERROR; michael@0: return; michael@0: } michael@0: res_init(pResData, formatVersion, inBytes, length, errorCode); michael@0: } michael@0: michael@0: U_CFUNC void michael@0: res_load(ResourceData *pResData, michael@0: const char *path, const char *name, UErrorCode *errorCode) { michael@0: UVersionInfo formatVersion; michael@0: michael@0: uprv_memset(pResData, 0, sizeof(ResourceData)); michael@0: michael@0: /* load the ResourceBundle file */ michael@0: pResData->data=udata_openChoice(path, "res", name, isAcceptable, formatVersion, errorCode); michael@0: if(U_FAILURE(*errorCode)) { michael@0: return; michael@0: } michael@0: michael@0: /* get its memory and initialize *pResData */ michael@0: res_init(pResData, formatVersion, udata_getMemory(pResData->data), -1, errorCode); michael@0: } michael@0: michael@0: U_CFUNC void michael@0: res_unload(ResourceData *pResData) { michael@0: if(pResData->data!=NULL) { michael@0: udata_close(pResData->data); michael@0: pResData->data=NULL; michael@0: } michael@0: } michael@0: michael@0: static const int8_t gPublicTypes[URES_LIMIT] = { michael@0: URES_STRING, michael@0: URES_BINARY, michael@0: URES_TABLE, michael@0: URES_ALIAS, michael@0: michael@0: URES_TABLE, /* URES_TABLE32 */ michael@0: URES_TABLE, /* URES_TABLE16 */ michael@0: URES_STRING, /* URES_STRING_V2 */ michael@0: URES_INT, michael@0: michael@0: URES_ARRAY, michael@0: URES_ARRAY, /* URES_ARRAY16 */ michael@0: URES_NONE, michael@0: URES_NONE, michael@0: michael@0: URES_NONE, michael@0: URES_NONE, michael@0: URES_INT_VECTOR, michael@0: URES_NONE michael@0: }; michael@0: michael@0: U_CAPI UResType U_EXPORT2 michael@0: res_getPublicType(Resource res) { michael@0: return (UResType)gPublicTypes[RES_GET_TYPE(res)]; michael@0: } michael@0: michael@0: U_CAPI const UChar * U_EXPORT2 michael@0: res_getString(const ResourceData *pResData, Resource res, int32_t *pLength) { michael@0: const UChar *p; michael@0: uint32_t offset=RES_GET_OFFSET(res); michael@0: int32_t length; michael@0: if(RES_GET_TYPE(res)==URES_STRING_V2) { michael@0: int32_t first; michael@0: p=(const UChar *)(pResData->p16BitUnits+offset); michael@0: first=*p; michael@0: if(!U16_IS_TRAIL(first)) { michael@0: length=u_strlen(p); michael@0: } else if(first<0xdfef) { michael@0: length=first&0x3ff; michael@0: ++p; michael@0: } else if(first<0xdfff) { michael@0: length=((first-0xdfef)<<16)|p[1]; michael@0: p+=2; michael@0: } else { michael@0: length=((int32_t)p[1]<<16)|p[2]; michael@0: p+=3; michael@0: } michael@0: } else if(res==offset) /* RES_GET_TYPE(res)==URES_STRING */ { michael@0: const int32_t *p32= res==0 ? &gEmptyString.length : pResData->pRoot+res; michael@0: length=*p32++; michael@0: p=(const UChar *)p32; michael@0: } else { michael@0: p=NULL; michael@0: length=0; michael@0: } michael@0: if(pLength) { michael@0: *pLength=length; michael@0: } michael@0: return p; michael@0: } michael@0: michael@0: U_CAPI const UChar * U_EXPORT2 michael@0: res_getAlias(const ResourceData *pResData, Resource res, int32_t *pLength) { michael@0: const UChar *p; michael@0: uint32_t offset=RES_GET_OFFSET(res); michael@0: int32_t length; michael@0: if(RES_GET_TYPE(res)==URES_ALIAS) { michael@0: const int32_t *p32= offset==0 ? &gEmptyString.length : pResData->pRoot+offset; michael@0: length=*p32++; michael@0: p=(const UChar *)p32; michael@0: } else { michael@0: p=NULL; michael@0: length=0; michael@0: } michael@0: if(pLength) { michael@0: *pLength=length; michael@0: } michael@0: return p; michael@0: } michael@0: michael@0: U_CAPI const uint8_t * U_EXPORT2 michael@0: res_getBinary(const ResourceData *pResData, Resource res, int32_t *pLength) { michael@0: const uint8_t *p; michael@0: uint32_t offset=RES_GET_OFFSET(res); michael@0: int32_t length; michael@0: if(RES_GET_TYPE(res)==URES_BINARY) { michael@0: const int32_t *p32= offset==0 ? (const int32_t*)&gEmpty32 : pResData->pRoot+offset; michael@0: length=*p32++; michael@0: p=(const uint8_t *)p32; michael@0: } else { michael@0: p=NULL; michael@0: length=0; michael@0: } michael@0: if(pLength) { michael@0: *pLength=length; michael@0: } michael@0: return p; michael@0: } michael@0: michael@0: michael@0: U_CAPI const int32_t * U_EXPORT2 michael@0: res_getIntVector(const ResourceData *pResData, Resource res, int32_t *pLength) { michael@0: const int32_t *p; michael@0: uint32_t offset=RES_GET_OFFSET(res); michael@0: int32_t length; michael@0: if(RES_GET_TYPE(res)==URES_INT_VECTOR) { michael@0: p= offset==0 ? (const int32_t *)&gEmpty32 : pResData->pRoot+offset; michael@0: length=*p++; michael@0: } else { michael@0: p=NULL; michael@0: length=0; michael@0: } michael@0: if(pLength) { michael@0: *pLength=length; michael@0: } michael@0: return p; michael@0: } michael@0: michael@0: U_CAPI int32_t U_EXPORT2 michael@0: res_countArrayItems(const ResourceData *pResData, Resource res) { michael@0: uint32_t offset=RES_GET_OFFSET(res); michael@0: switch(RES_GET_TYPE(res)) { michael@0: case URES_STRING: michael@0: case URES_STRING_V2: michael@0: case URES_BINARY: michael@0: case URES_ALIAS: michael@0: case URES_INT: michael@0: case URES_INT_VECTOR: michael@0: return 1; michael@0: case URES_ARRAY: michael@0: case URES_TABLE32: michael@0: return offset==0 ? 0 : *(pResData->pRoot+offset); michael@0: case URES_TABLE: michael@0: return offset==0 ? 0 : *((const uint16_t *)(pResData->pRoot+offset)); michael@0: case URES_ARRAY16: michael@0: case URES_TABLE16: michael@0: return pResData->p16BitUnits[offset]; michael@0: default: michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: U_CAPI Resource U_EXPORT2 michael@0: res_getTableItemByKey(const ResourceData *pResData, Resource table, michael@0: int32_t *indexR, const char **key) { michael@0: uint32_t offset=RES_GET_OFFSET(table); michael@0: int32_t length; michael@0: int32_t idx; michael@0: if(key == NULL || *key == NULL) { michael@0: return RES_BOGUS; michael@0: } michael@0: switch(RES_GET_TYPE(table)) { michael@0: case URES_TABLE: { michael@0: if (offset!=0) { /* empty if offset==0 */ michael@0: const uint16_t *p= (const uint16_t *)(pResData->pRoot+offset); michael@0: length=*p++; michael@0: *indexR=idx=_res_findTableItem(pResData, p, length, *key, key); michael@0: if(idx>=0) { michael@0: const Resource *p32=(const Resource *)(p+length+(~length&1)); michael@0: return p32[idx]; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: case URES_TABLE16: { michael@0: const uint16_t *p=pResData->p16BitUnits+offset; michael@0: length=*p++; michael@0: *indexR=idx=_res_findTableItem(pResData, p, length, *key, key); michael@0: if(idx>=0) { michael@0: return URES_MAKE_RESOURCE(URES_STRING_V2, p[length+idx]); michael@0: } michael@0: break; michael@0: } michael@0: case URES_TABLE32: { michael@0: if (offset!=0) { /* empty if offset==0 */ michael@0: const int32_t *p= pResData->pRoot+offset; michael@0: length=*p++; michael@0: *indexR=idx=_res_findTable32Item(pResData, p, length, *key, key); michael@0: if(idx>=0) { michael@0: return (Resource)p[length+idx]; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: default: michael@0: break; michael@0: } michael@0: return RES_BOGUS; michael@0: } michael@0: michael@0: U_CAPI Resource U_EXPORT2 michael@0: res_getTableItemByIndex(const ResourceData *pResData, Resource table, michael@0: int32_t indexR, const char **key) { michael@0: uint32_t offset=RES_GET_OFFSET(table); michael@0: int32_t length; michael@0: U_ASSERT(indexR>=0); /* to ensure the index is not negative */ michael@0: switch(RES_GET_TYPE(table)) { michael@0: case URES_TABLE: { michael@0: if (offset != 0) { /* empty if offset==0 */ michael@0: const uint16_t *p= (const uint16_t *)(pResData->pRoot+offset); michael@0: length=*p++; michael@0: if(indexRp16BitUnits+offset; michael@0: length=*p++; michael@0: if(indexRpRoot+offset; michael@0: length=*p++; michael@0: if(indexRrootRes, &idx, &realKey); michael@0: } michael@0: michael@0: U_CAPI Resource U_EXPORT2 michael@0: res_getArrayItem(const ResourceData *pResData, Resource array, int32_t indexR) { michael@0: uint32_t offset=RES_GET_OFFSET(array); michael@0: U_ASSERT(indexR>=0); /* to ensure the index is not negative */ michael@0: switch(RES_GET_TYPE(array)) { michael@0: case URES_ARRAY: { michael@0: if (offset!=0) { /* empty if offset==0 */ michael@0: const int32_t *p= pResData->pRoot+offset; michael@0: if(indexR<*p) { michael@0: return (Resource)p[1+indexR]; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: case URES_ARRAY16: { michael@0: const uint16_t *p=pResData->p16BitUnits+offset; michael@0: if(indexR<*p) { michael@0: return URES_MAKE_RESOURCE(URES_STRING_V2, p[1+indexR]); michael@0: } michael@0: break; michael@0: } michael@0: default: michael@0: break; michael@0: } michael@0: return RES_BOGUS; michael@0: } michael@0: michael@0: U_CFUNC Resource michael@0: res_findResource(const ResourceData *pResData, Resource r, char** path, const char** key) { michael@0: /* we pass in a path. CollationElements/Sequence or zoneStrings/3/2 etc. michael@0: * iterates over a path and stops when a scalar resource is found. This michael@0: * CAN be an alias. Path gets set to the part that has not yet been processed. michael@0: */ michael@0: michael@0: char *pathP = *path, *nextSepP = *path; michael@0: char *closeIndex = NULL; michael@0: Resource t1 = r; michael@0: Resource t2; michael@0: int32_t indexR = 0; michael@0: UResType type = (UResType)RES_GET_TYPE(t1); michael@0: michael@0: /* if you come in with an empty path, you'll be getting back the same resource */ michael@0: if(!uprv_strlen(pathP)) { michael@0: return r; michael@0: } michael@0: michael@0: /* one needs to have an aggregate resource in order to search in it */ michael@0: if(!URES_IS_CONTAINER(type)) { michael@0: return RES_BOGUS; michael@0: } michael@0: michael@0: while(nextSepP && *pathP && t1 != RES_BOGUS && URES_IS_CONTAINER(type)) { michael@0: /* Iteration stops if: the path has been consumed, we found a non-existing michael@0: * resource (t1 == RES_BOGUS) or we found a scalar resource (including alias) michael@0: */ michael@0: nextSepP = uprv_strchr(pathP, RES_PATH_SEPARATOR); michael@0: /* if there are more separators, terminate string michael@0: * and set path to the remaining part of the string michael@0: */ michael@0: if(nextSepP != NULL) { michael@0: *nextSepP = 0; /* overwrite the separator with a NUL to terminate the key */ michael@0: *path = nextSepP+1; michael@0: } else { michael@0: *path = uprv_strchr(pathP, 0); michael@0: } michael@0: michael@0: /* if the resource is a table */ michael@0: /* try the key based access */ michael@0: if(URES_IS_TABLE(type)) { michael@0: *key = pathP; michael@0: t2 = res_getTableItemByKey(pResData, t1, &indexR, key); michael@0: if(t2 == RES_BOGUS) { michael@0: /* if we fail to get the resource by key, maybe we got an index */ michael@0: indexR = uprv_strtol(pathP, &closeIndex, 10); michael@0: if(closeIndex != pathP) { michael@0: /* if we indeed have an index, try to get the item by index */ michael@0: t2 = res_getTableItemByIndex(pResData, t1, indexR, key); michael@0: } michael@0: } michael@0: } else if(URES_IS_ARRAY(type)) { michael@0: indexR = uprv_strtol(pathP, &closeIndex, 10); michael@0: if(closeIndex != pathP) { michael@0: t2 = res_getArrayItem(pResData, t1, indexR); michael@0: } else { michael@0: t2 = RES_BOGUS; /* have an array, but don't have a valid index */ michael@0: } michael@0: *key = NULL; michael@0: } else { /* can't do much here, except setting t2 to bogus */ michael@0: t2 = RES_BOGUS; michael@0: } michael@0: t1 = t2; michael@0: type = (UResType)RES_GET_TYPE(t1); michael@0: /* position pathP to next resource key/index */ michael@0: pathP = *path; michael@0: } michael@0: michael@0: return t1; michael@0: } michael@0: michael@0: /* resource bundle swapping ------------------------------------------------- */ michael@0: michael@0: /* michael@0: * Need to always enumerate the entire item tree, michael@0: * track the lowest address of any item to use as the limit for char keys[], michael@0: * track the highest address of any item to return the size of the data. michael@0: * michael@0: * We should have thought of storing those in the data... michael@0: * It is possible to extend the data structure by putting additional values michael@0: * in places that are inaccessible by ordinary enumeration of the item tree. michael@0: * For example, additional integers could be stored at the beginning or michael@0: * end of the key strings; this could be indicated by a minor version number, michael@0: * and the data swapping would have to know about these values. michael@0: * michael@0: * The data structure does not forbid keys to be shared, so we must swap michael@0: * all keys once instead of each key when it is referenced. michael@0: * michael@0: * These swapping functions assume that a resource bundle always has a length michael@0: * that is a multiple of 4 bytes. michael@0: * Currently, this is trivially true because genrb writes bundle tree leaves michael@0: * physically first, before their branches, so that the root table with its michael@0: * array of resource items (uint32_t values) is always last. michael@0: */ michael@0: michael@0: /* definitions for table sorting ------------------------ */ michael@0: michael@0: /* michael@0: * row of a temporary array michael@0: * michael@0: * gets platform-endian key string indexes and sorting indexes; michael@0: * after sorting this array by keys, the actual key/value arrays are permutated michael@0: * according to the sorting indexes michael@0: */ michael@0: typedef struct Row { michael@0: int32_t keyIndex, sortIndex; michael@0: } Row; michael@0: michael@0: static int32_t michael@0: ures_compareRows(const void *context, const void *left, const void *right) { michael@0: const char *keyChars=(const char *)context; michael@0: return (int32_t)uprv_strcmp(keyChars+((const Row *)left)->keyIndex, michael@0: keyChars+((const Row *)right)->keyIndex); michael@0: } michael@0: michael@0: typedef struct TempTable { michael@0: const char *keyChars; michael@0: Row *rows; michael@0: int32_t *resort; michael@0: uint32_t *resFlags; michael@0: int32_t localKeyLimit; michael@0: uint8_t majorFormatVersion; michael@0: } TempTable; michael@0: michael@0: enum { michael@0: STACK_ROW_CAPACITY=200 michael@0: }; michael@0: michael@0: /* The table item key string is not locally available. */ michael@0: static const char *const gUnknownKey=""; michael@0: michael@0: /* resource table key for collation binaries: "%%CollationBin" */ michael@0: static const UChar gCollationBinKey[]={ michael@0: 0x25, 0x25, michael@0: 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, michael@0: 0x42, 0x69, 0x6e, michael@0: 0 michael@0: }; michael@0: michael@0: /* michael@0: * swap one resource item michael@0: */ michael@0: static void michael@0: ures_swapResource(const UDataSwapper *ds, michael@0: const Resource *inBundle, Resource *outBundle, michael@0: Resource res, /* caller swaps res itself */ michael@0: const char *key, michael@0: TempTable *pTempTable, michael@0: UErrorCode *pErrorCode) { michael@0: const Resource *p; michael@0: Resource *q; michael@0: int32_t offset, count; michael@0: michael@0: switch(RES_GET_TYPE(res)) { michael@0: case URES_TABLE16: michael@0: case URES_STRING_V2: michael@0: case URES_INT: michael@0: case URES_ARRAY16: michael@0: /* integer, or points to 16-bit units, nothing to do here */ michael@0: return; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: /* all other types use an offset to point to their data */ michael@0: offset=(int32_t)RES_GET_OFFSET(res); michael@0: if(offset==0) { michael@0: /* special offset indicating an empty item */ michael@0: return; michael@0: } michael@0: if(pTempTable->resFlags[offset>>5]&((uint32_t)1<<(offset&0x1f))) { michael@0: /* we already swapped this resource item */ michael@0: return; michael@0: } else { michael@0: /* mark it as swapped now */ michael@0: pTempTable->resFlags[offset>>5]|=((uint32_t)1<<(offset&0x1f)); michael@0: } michael@0: michael@0: p=inBundle+offset; michael@0: q=outBundle+offset; michael@0: michael@0: switch(RES_GET_TYPE(res)) { michael@0: case URES_ALIAS: michael@0: /* physically same value layout as string, fall through */ michael@0: case URES_STRING: michael@0: count=udata_readInt32(ds, (int32_t)*p); michael@0: /* swap length */ michael@0: ds->swapArray32(ds, p, 4, q, pErrorCode); michael@0: /* swap each UChar (the terminating NUL would not change) */ michael@0: ds->swapArray16(ds, p+1, 2*count, q+1, pErrorCode); michael@0: break; michael@0: case URES_BINARY: michael@0: count=udata_readInt32(ds, (int32_t)*p); michael@0: /* swap length */ michael@0: ds->swapArray32(ds, p, 4, q, pErrorCode); michael@0: /* no need to swap or copy bytes - ures_swap() copied them all */ michael@0: michael@0: /* swap known formats */ michael@0: #if !UCONFIG_NO_COLLATION michael@0: if( key!=NULL && /* the binary is in a table */ michael@0: (key!=gUnknownKey ? michael@0: /* its table key string is "%%CollationBin" */ michael@0: 0==ds->compareInvChars(ds, key, -1, michael@0: gCollationBinKey, LENGTHOF(gCollationBinKey)-1) : michael@0: /* its table key string is unknown but it looks like a collation binary */ michael@0: ucol_looksLikeCollationBinary(ds, p+1, count)) michael@0: ) { michael@0: ucol_swapBinary(ds, p+1, count, q+1, pErrorCode); michael@0: } michael@0: #endif michael@0: break; michael@0: case URES_TABLE: michael@0: case URES_TABLE32: michael@0: { michael@0: const uint16_t *pKey16; michael@0: uint16_t *qKey16; michael@0: michael@0: const int32_t *pKey32; michael@0: int32_t *qKey32; michael@0: michael@0: Resource item; michael@0: int32_t i, oldIndex; michael@0: michael@0: if(RES_GET_TYPE(res)==URES_TABLE) { michael@0: /* get table item count */ michael@0: pKey16=(const uint16_t *)p; michael@0: qKey16=(uint16_t *)q; michael@0: count=ds->readUInt16(*pKey16); michael@0: michael@0: pKey32=qKey32=NULL; michael@0: michael@0: /* swap count */ michael@0: ds->swapArray16(ds, pKey16++, 2, qKey16++, pErrorCode); michael@0: michael@0: offset+=((1+count)+1)/2; michael@0: } else { michael@0: /* get table item count */ michael@0: pKey32=(const int32_t *)p; michael@0: qKey32=(int32_t *)q; michael@0: count=udata_readInt32(ds, *pKey32); michael@0: michael@0: pKey16=qKey16=NULL; michael@0: michael@0: /* swap count */ michael@0: ds->swapArray32(ds, pKey32++, 4, qKey32++, pErrorCode); michael@0: michael@0: offset+=1+count; michael@0: } michael@0: michael@0: if(count==0) { michael@0: break; michael@0: } michael@0: michael@0: p=inBundle+offset; /* pointer to table resources */ michael@0: q=outBundle+offset; michael@0: michael@0: /* recurse */ michael@0: for(i=0; ireadUInt16(pKey16[i]); michael@0: if(keyOffsetlocalKeyLimit) { michael@0: itemKey=(const char *)outBundle+keyOffset; michael@0: } michael@0: } else { michael@0: int32_t keyOffset=udata_readInt32(ds, pKey32[i]); michael@0: if(keyOffset>=0) { michael@0: itemKey=(const char *)outBundle+keyOffset; michael@0: } michael@0: } michael@0: item=ds->readUInt32(p[i]); michael@0: ures_swapResource(ds, inBundle, outBundle, item, itemKey, pTempTable, pErrorCode); michael@0: if(U_FAILURE(*pErrorCode)) { michael@0: udata_printError(ds, "ures_swapResource(table res=%08x)[%d].recurse(%08x) failed\n", michael@0: res, i, item); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if(pTempTable->majorFormatVersion>1 || ds->inCharset==ds->outCharset) { michael@0: /* no need to sort, just swap the offset/value arrays */ michael@0: if(pKey16!=NULL) { michael@0: ds->swapArray16(ds, pKey16, count*2, qKey16, pErrorCode); michael@0: ds->swapArray32(ds, p, count*4, q, pErrorCode); michael@0: } else { michael@0: /* swap key offsets and items as one array */ michael@0: ds->swapArray32(ds, pKey32, count*2*4, qKey32, pErrorCode); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: /* michael@0: * We need to sort tables by outCharset key strings because they michael@0: * sort differently for different charset families. michael@0: * ures_swap() already set pTempTable->keyChars appropriately. michael@0: * First we set up a temporary table with the key indexes and michael@0: * sorting indexes and sort that. michael@0: * Then we permutate and copy/swap the actual values. michael@0: */ michael@0: if(pKey16!=NULL) { michael@0: for(i=0; irows[i].keyIndex=ds->readUInt16(pKey16[i]); michael@0: pTempTable->rows[i].sortIndex=i; michael@0: } michael@0: } else { michael@0: for(i=0; irows[i].keyIndex=udata_readInt32(ds, pKey32[i]); michael@0: pTempTable->rows[i].sortIndex=i; michael@0: } michael@0: } michael@0: uprv_sortArray(pTempTable->rows, count, sizeof(Row), michael@0: ures_compareRows, pTempTable->keyChars, michael@0: FALSE, pErrorCode); michael@0: if(U_FAILURE(*pErrorCode)) { michael@0: udata_printError(ds, "ures_swapResource(table res=%08x).uprv_sortArray(%d items) failed\n", michael@0: res, count); michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * copy/swap/permutate items michael@0: * michael@0: * If we swap in-place, then the permutation must use another michael@0: * temporary array (pTempTable->resort) michael@0: * before the results are copied to the outBundle. michael@0: */ michael@0: /* keys */ michael@0: if(pKey16!=NULL) { michael@0: uint16_t *rKey16; michael@0: michael@0: if(pKey16!=qKey16) { michael@0: rKey16=qKey16; michael@0: } else { michael@0: rKey16=(uint16_t *)pTempTable->resort; michael@0: } michael@0: for(i=0; irows[i].sortIndex; michael@0: ds->swapArray16(ds, pKey16+oldIndex, 2, rKey16+i, pErrorCode); michael@0: } michael@0: if(qKey16!=rKey16) { michael@0: uprv_memcpy(qKey16, rKey16, 2*count); michael@0: } michael@0: } else { michael@0: int32_t *rKey32; michael@0: michael@0: if(pKey32!=qKey32) { michael@0: rKey32=qKey32; michael@0: } else { michael@0: rKey32=pTempTable->resort; michael@0: } michael@0: for(i=0; irows[i].sortIndex; michael@0: ds->swapArray32(ds, pKey32+oldIndex, 4, rKey32+i, pErrorCode); michael@0: } michael@0: if(qKey32!=rKey32) { michael@0: uprv_memcpy(qKey32, rKey32, 4*count); michael@0: } michael@0: } michael@0: michael@0: /* resources */ michael@0: { michael@0: Resource *r; michael@0: michael@0: michael@0: if(p!=q) { michael@0: r=q; michael@0: } else { michael@0: r=(Resource *)pTempTable->resort; michael@0: } michael@0: for(i=0; irows[i].sortIndex; michael@0: ds->swapArray32(ds, p+oldIndex, 4, r+i, pErrorCode); michael@0: } michael@0: if(q!=r) { michael@0: uprv_memcpy(q, r, 4*count); michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: case URES_ARRAY: michael@0: { michael@0: Resource item; michael@0: int32_t i; michael@0: michael@0: count=udata_readInt32(ds, (int32_t)*p); michael@0: /* swap length */ michael@0: ds->swapArray32(ds, p++, 4, q++, pErrorCode); michael@0: michael@0: /* recurse */ michael@0: for(i=0; ireadUInt32(p[i]); michael@0: ures_swapResource(ds, inBundle, outBundle, item, NULL, pTempTable, pErrorCode); michael@0: if(U_FAILURE(*pErrorCode)) { michael@0: udata_printError(ds, "ures_swapResource(array res=%08x)[%d].recurse(%08x) failed\n", michael@0: res, i, item); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: /* swap items */ michael@0: ds->swapArray32(ds, p, 4*count, q, pErrorCode); michael@0: } michael@0: break; michael@0: case URES_INT_VECTOR: michael@0: count=udata_readInt32(ds, (int32_t)*p); michael@0: /* swap length and each integer */ michael@0: ds->swapArray32(ds, p, 4*(1+count), q, pErrorCode); michael@0: break; michael@0: default: michael@0: /* also catches RES_BOGUS */ michael@0: *pErrorCode=U_UNSUPPORTED_ERROR; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: U_CAPI int32_t U_EXPORT2 michael@0: ures_swap(const UDataSwapper *ds, michael@0: const void *inData, int32_t length, void *outData, michael@0: UErrorCode *pErrorCode) { michael@0: const UDataInfo *pInfo; michael@0: const Resource *inBundle; michael@0: Resource rootRes; michael@0: int32_t headerSize, maxTableLength; michael@0: michael@0: Row rows[STACK_ROW_CAPACITY]; michael@0: int32_t resort[STACK_ROW_CAPACITY]; michael@0: TempTable tempTable; michael@0: michael@0: const int32_t *inIndexes; michael@0: michael@0: /* the following integers count Resource item offsets (4 bytes each), not bytes */ michael@0: int32_t bundleLength, indexLength, keysBottom, keysTop, resBottom, top; michael@0: michael@0: /* udata_swapDataHeader checks the arguments */ michael@0: headerSize=udata_swapDataHeader(ds, inData, length, outData, pErrorCode); michael@0: if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) { michael@0: return 0; michael@0: } michael@0: michael@0: /* check data format and format version */ michael@0: pInfo=(const UDataInfo *)((const char *)inData+4); michael@0: if(!( michael@0: pInfo->dataFormat[0]==0x52 && /* dataFormat="ResB" */ michael@0: pInfo->dataFormat[1]==0x65 && michael@0: pInfo->dataFormat[2]==0x73 && michael@0: pInfo->dataFormat[3]==0x42 && michael@0: ((pInfo->formatVersion[0]==1 && pInfo->formatVersion[1]>=1) || /* formatVersion 1.1+ or 2.x */ michael@0: pInfo->formatVersion[0]==2) michael@0: )) { michael@0: udata_printError(ds, "ures_swap(): data format %02x.%02x.%02x.%02x (format version %02x.%02x) is not a resource bundle\n", michael@0: pInfo->dataFormat[0], pInfo->dataFormat[1], michael@0: pInfo->dataFormat[2], pInfo->dataFormat[3], michael@0: pInfo->formatVersion[0], pInfo->formatVersion[1]); michael@0: *pErrorCode=U_UNSUPPORTED_ERROR; michael@0: return 0; michael@0: } michael@0: tempTable.majorFormatVersion=pInfo->formatVersion[0]; michael@0: michael@0: /* a resource bundle must contain at least one resource item */ michael@0: if(length<0) { michael@0: bundleLength=-1; michael@0: } else { michael@0: bundleLength=(length-headerSize)/4; michael@0: michael@0: /* formatVersion 1.1 must have a root item and at least 5 indexes */ michael@0: if(bundleLength<(1+5)) { michael@0: udata_printError(ds, "ures_swap(): too few bytes (%d after header) for a resource bundle\n", michael@0: length-headerSize); michael@0: *pErrorCode=U_INDEX_OUTOFBOUNDS_ERROR; michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: inBundle=(const Resource *)((const char *)inData+headerSize); michael@0: rootRes=ds->readUInt32(*inBundle); michael@0: michael@0: /* formatVersion 1.1 adds the indexes[] array */ michael@0: inIndexes=(const int32_t *)(inBundle+1); michael@0: michael@0: indexLength=udata_readInt32(ds, inIndexes[URES_INDEX_LENGTH])&0xff; michael@0: if(indexLength<=URES_INDEX_MAX_TABLE_LENGTH) { michael@0: udata_printError(ds, "ures_swap(): too few indexes for a 1.1+ resource bundle\n"); michael@0: *pErrorCode=U_INDEX_OUTOFBOUNDS_ERROR; michael@0: return 0; michael@0: } michael@0: keysBottom=1+indexLength; michael@0: keysTop=udata_readInt32(ds, inIndexes[URES_INDEX_KEYS_TOP]); michael@0: if(indexLength>URES_INDEX_16BIT_TOP) { michael@0: resBottom=udata_readInt32(ds, inIndexes[URES_INDEX_16BIT_TOP]); michael@0: } else { michael@0: resBottom=keysTop; michael@0: } michael@0: top=udata_readInt32(ds, inIndexes[URES_INDEX_BUNDLE_TOP]); michael@0: maxTableLength=udata_readInt32(ds, inIndexes[URES_INDEX_MAX_TABLE_LENGTH]); michael@0: michael@0: if(0<=bundleLength && bundleLength(1+indexLength)) { michael@0: tempTable.localKeyLimit=keysTop<<2; michael@0: } else { michael@0: tempTable.localKeyLimit=0; michael@0: } michael@0: michael@0: if(length>=0) { michael@0: Resource *outBundle=(Resource *)((char *)outData+headerSize); michael@0: michael@0: /* track which resources we have already swapped */ michael@0: uint32_t stackResFlags[STACK_ROW_CAPACITY]; michael@0: int32_t resFlagsLength; michael@0: michael@0: /* michael@0: * We need one bit per 4 resource bundle bytes so that we can track michael@0: * every possible Resource for whether we have swapped it already. michael@0: * Multiple Resource words can refer to the same bundle offsets michael@0: * for sharing identical values. michael@0: * We could optimize this by allocating only for locations above michael@0: * where Resource values are stored (above keys & strings). michael@0: */ michael@0: resFlagsLength=(length+31)>>5; /* number of bytes needed */ michael@0: resFlagsLength=(resFlagsLength+3)&~3; /* multiple of 4 bytes for uint32_t */ michael@0: if(resFlagsLength<=sizeof(stackResFlags)) { michael@0: tempTable.resFlags=stackResFlags; michael@0: } else { michael@0: tempTable.resFlags=(uint32_t *)uprv_malloc(resFlagsLength); michael@0: if(tempTable.resFlags==NULL) { michael@0: udata_printError(ds, "ures_swap(): unable to allocate memory for tracking resources\n"); michael@0: *pErrorCode=U_MEMORY_ALLOCATION_ERROR; michael@0: return 0; michael@0: } michael@0: } michael@0: uprv_memset(tempTable.resFlags, 0, resFlagsLength); michael@0: michael@0: /* copy the bundle for binary and inaccessible data */ michael@0: if(inData!=outData) { michael@0: uprv_memcpy(outBundle, inBundle, 4*top); michael@0: } michael@0: michael@0: /* swap the key strings, but not the padding bytes (0xaa) after the last string and its NUL */ michael@0: udata_swapInvStringBlock(ds, inBundle+keysBottom, 4*(keysTop-keysBottom), michael@0: outBundle+keysBottom, pErrorCode); michael@0: if(U_FAILURE(*pErrorCode)) { michael@0: udata_printError(ds, "ures_swap().udata_swapInvStringBlock(keys[%d]) failed\n", 4*(keysTop-keysBottom)); michael@0: return 0; michael@0: } michael@0: michael@0: /* swap the 16-bit units (strings, table16, array16) */ michael@0: if(keysTopswapArray16(ds, inBundle+keysTop, (resBottom-keysTop)*4, outBundle+keysTop, pErrorCode); michael@0: if(U_FAILURE(*pErrorCode)) { michael@0: udata_printError(ds, "ures_swap().swapArray16(16-bit units[%d]) failed\n", 2*(resBottom-keysTop)); michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: /* allocate the temporary table for sorting resource tables */ michael@0: tempTable.keyChars=(const char *)outBundle; /* sort by outCharset */ michael@0: if(tempTable.majorFormatVersion>1 || maxTableLength<=STACK_ROW_CAPACITY) { michael@0: tempTable.rows=rows; michael@0: tempTable.resort=resort; michael@0: } else { michael@0: tempTable.rows=(Row *)uprv_malloc(maxTableLength*sizeof(Row)+maxTableLength*4); michael@0: if(tempTable.rows==NULL) { michael@0: udata_printError(ds, "ures_swap(): unable to allocate memory for sorting tables (max length: %d)\n", michael@0: maxTableLength); michael@0: *pErrorCode=U_MEMORY_ALLOCATION_ERROR; michael@0: if(tempTable.resFlags!=stackResFlags) { michael@0: uprv_free(tempTable.resFlags); michael@0: } michael@0: return 0; michael@0: } michael@0: tempTable.resort=(int32_t *)(tempTable.rows+maxTableLength); michael@0: } michael@0: michael@0: /* swap the resources */ michael@0: ures_swapResource(ds, inBundle, outBundle, rootRes, NULL, &tempTable, pErrorCode); michael@0: if(U_FAILURE(*pErrorCode)) { michael@0: udata_printError(ds, "ures_swapResource(root res=%08x) failed\n", michael@0: rootRes); michael@0: } michael@0: michael@0: if(tempTable.rows!=rows) { michael@0: uprv_free(tempTable.rows); michael@0: } michael@0: if(tempTable.resFlags!=stackResFlags) { michael@0: uprv_free(tempTable.resFlags); michael@0: } michael@0: michael@0: /* swap the root resource and indexes */ michael@0: ds->swapArray32(ds, inBundle, keysBottom*4, outBundle, pErrorCode); michael@0: } michael@0: michael@0: return headerSize+4*top; michael@0: }